Coverage for src/secchi/schemas.py: 96%
67 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
1"""Pydantic schemas for Secchi's persisted and external data boundaries.
3The application domain remains dataclass-based. These models deliberately sit
4at the edges of the application so cache files, reports, and MCP responses have
5an explicit, validated contract without coupling the UI and services to a
6serialization library.
7"""
9from __future__ import annotations
11from datetime import datetime
12from typing import Any
14from pydantic import BaseModel, ConfigDict, Field, field_validator
16from secchi.schema import (
17 CACHE_SCHEMA_VERSION,
18 COMPARISON_SCHEMA_VERSION,
19 PACKAGE_EXPORT_SCHEMA_VERSION,
20 PROJECT_EXPORT_SCHEMA_VERSION,
21)
24class SignalWarningSchema(BaseModel):
25 """A non-fatal signal-fetch warning exposed to users and agents."""
27 model_config = ConfigDict(extra="forbid")
29 source: str
30 message: str
33class CacheEnvelope(BaseModel):
34 """Versioned package cache envelope.
36 Version ``0`` represents the legacy unversioned cache format. It remains
37 readable so upgrading Secchi does not discard a user's same-day cache.
38 """
40 model_config = ConfigDict(extra="ignore")
42 schema_version: int = CACHE_SCHEMA_VERSION
43 fetched_at: datetime
44 package: dict[str, Any]
46 @field_validator("schema_version")
47 @classmethod
48 def validate_supported_version(cls, value: int) -> int:
49 if value not in (0, CACHE_SCHEMA_VERSION):
50 raise ValueError(f"unsupported cache schema version: {value}")
51 return value
54class PackageExport(BaseModel):
55 """Stable JSON contract returned by package reports and MCP."""
57 model_config = ConfigDict(extra="forbid", populate_by_name=True)
59 schema_version: int = PACKAGE_EXPORT_SCHEMA_VERSION
60 schema_name: str = Field("secchi.package-intelligence", alias="schema")
61 generated_by: str = "Secchi"
62 project: str
63 package: str
64 registry: str
65 exported_at: datetime
66 package_info: dict[str, Any] | None = None
67 derived: dict[str, Any] | None = None
68 warnings: list[SignalWarningSchema] = Field(default_factory=list)
70 @field_validator("schema_version")
71 @classmethod
72 def validate_version(cls, value: int) -> int:
73 if value != PACKAGE_EXPORT_SCHEMA_VERSION:
74 raise ValueError(f"unsupported package export schema version: {value}")
75 return value
78class ProjectExport(BaseModel):
79 """Stable JSON contract for project-wide reports."""
81 model_config = ConfigDict(extra="forbid", populate_by_name=True)
83 schema_version: int = PROJECT_EXPORT_SCHEMA_VERSION
84 schema_name: str = Field("secchi.project-intelligence", alias="schema")
85 generated_by: str = "Secchi"
86 project: dict[str, Any]
87 generated_at: datetime
88 summary: dict[str, Any]
89 sources: list[dict[str, Any]]
91 @field_validator("schema_version")
92 @classmethod
93 def validate_version(cls, value: int) -> int:
94 if value != PROJECT_EXPORT_SCHEMA_VERSION:
95 raise ValueError(f"unsupported project export schema version: {value}")
96 return value
99class ComparisonExport(BaseModel):
100 """Stable JSON contract for agent-readable package comparisons."""
102 model_config = ConfigDict(extra="forbid", populate_by_name=True)
104 schema_version: int = COMPARISON_SCHEMA_VERSION
105 schema_name: str = Field("secchi.package-comparison", alias="schema")
106 generated_by: str = "Secchi"
107 recommendation_basis: str
108 winner: dict[str, Any] | None = None
109 candidates: list[dict[str, Any]]
111 @field_validator("schema_version")
112 @classmethod
113 def validate_version(cls, value: int) -> int:
114 if value != COMPARISON_SCHEMA_VERSION:
115 raise ValueError(f"unsupported comparison schema version: {value}")
116 return value