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

1"""Pydantic schemas for Secchi's persisted and external data boundaries. 

2 

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""" 

8 

9from __future__ import annotations 

10 

11from datetime import datetime 

12from typing import Any 

13 

14from pydantic import BaseModel, ConfigDict, Field, field_validator 

15 

16from secchi.schema import ( 

17 CACHE_SCHEMA_VERSION, 

18 COMPARISON_SCHEMA_VERSION, 

19 PACKAGE_EXPORT_SCHEMA_VERSION, 

20 PROJECT_EXPORT_SCHEMA_VERSION, 

21) 

22 

23 

24class SignalWarningSchema(BaseModel): 

25 """A non-fatal signal-fetch warning exposed to users and agents.""" 

26 

27 model_config = ConfigDict(extra="forbid") 

28 

29 source: str 

30 message: str 

31 

32 

33class CacheEnvelope(BaseModel): 

34 """Versioned package cache envelope. 

35 

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 """ 

39 

40 model_config = ConfigDict(extra="ignore") 

41 

42 schema_version: int = CACHE_SCHEMA_VERSION 

43 fetched_at: datetime 

44 package: dict[str, Any] 

45 

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 

52 

53 

54class PackageExport(BaseModel): 

55 """Stable JSON contract returned by package reports and MCP.""" 

56 

57 model_config = ConfigDict(extra="forbid", populate_by_name=True) 

58 

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) 

69 

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 

76 

77 

78class ProjectExport(BaseModel): 

79 """Stable JSON contract for project-wide reports.""" 

80 

81 model_config = ConfigDict(extra="forbid", populate_by_name=True) 

82 

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]] 

90 

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 

97 

98 

99class ComparisonExport(BaseModel): 

100 """Stable JSON contract for agent-readable package comparisons.""" 

101 

102 model_config = ConfigDict(extra="forbid", populate_by_name=True) 

103 

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]] 

110 

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