Files
state-hub/api/schemas/service.py
tegwick 77689fbfb2 STATE-WP-0062 T2: /services catalog API over the two-dimension model
Add a local /services router (source of truth for the catalog itself):
- GET /services/catalog with hosting_type / development_type / maturity_level /
  status filters (eager-loads all four extensions)
- GET /services/{slug}
- POST /services/catalog upsert-by-slug, applying the dimension extensions;
  first_party.repo_slug resolves to a managed_repos FK.

Extensions are read/written via session.get (not the relationship attribute) to
avoid async lazy-load. /tpsc/* is left intact for dependency snapshots. 7 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:56:19 +02:00

117 lines
3.7 KiB
Python

"""Schemas for the two-dimension service catalog (STATE-WP-0062)."""
import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict
# ── Extension read models ────────────────────────────────────────────────────
class ServiceThirdPartyRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
pricing_model: str
upstream_packages: list | None = None
upstream_contacts: list | None = None
source_url: str | None = None
support_url: str | None = None
license: str | None = None
class ServiceFirstPartyRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
repo_id: uuid.UUID | None = None
owning_domain: str | None = None
class ServiceCloudRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
gdpr_maturity: str
gdpr_notes: str | None = None
dpa_available: bool
tos_url: str | None = None
privacy_policy_url: str | None = None
data_processing_regions: list | None = None
data_retention_notes: str | None = None
class ServiceSelfHostedRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
helix_instance: str | None = None
host_node: str | None = None
deployment_ref: str | None = None
runbook_ref: str | None = None
upstream_oss_project: str | None = None
class ServiceCatalogRead(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
slug: str
name: str
owner_or_provider: str | None = None
category: str | None = None
description: str | None = None
website_url: str | None = None
status: str
hosting_type: str
development_type: str
maturity_level: int | None = None
created_at: datetime
updated_at: datetime
third_party: ServiceThirdPartyRead | None = None
first_party: ServiceFirstPartyRead | None = None
cloud: ServiceCloudRead | None = None
self_hosted: ServiceSelfHostedRead | None = None
# ── Write (upsert) models ────────────────────────────────────────────────────
class ServiceThirdPartyIn(BaseModel):
pricing_model: str = "unknown"
upstream_packages: list | None = None
upstream_contacts: list | None = None
source_url: str | None = None
support_url: str | None = None
license: str | None = None
class ServiceFirstPartyIn(BaseModel):
repo_id: uuid.UUID | None = None
repo_slug: str | None = None
owning_domain: str | None = None
class ServiceCloudIn(BaseModel):
gdpr_maturity: str = "unknown"
gdpr_notes: str | None = None
dpa_available: bool = False
tos_url: str | None = None
privacy_policy_url: str | None = None
data_processing_regions: list | None = None
data_retention_notes: str | None = None
class ServiceSelfHostedIn(BaseModel):
helix_instance: str | None = None
host_node: str | None = None
deployment_ref: str | None = None
runbook_ref: str | None = None
upstream_oss_project: str | None = None
class ServiceUpsert(BaseModel):
slug: str
name: str
owner_or_provider: str | None = None
category: str | None = None
description: str | None = None
website_url: str | None = None
status: str = "active"
hosting_type: str # self_hosted | cloud_hosted
development_type: str # first_party | third_party
maturity_level: int | None = None
third_party: ServiceThirdPartyIn | None = None
first_party: ServiceFirstPartyIn | None = None
cloud: ServiceCloudIn | None = None
self_hosted: ServiceSelfHostedIn | None = None