Coverage for postrfp/shared/serial/refmodels.py: 100%
102 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
1from typing import Any, Optional, Literal, Annotated
3from pydantic import BaseModel, Field, ConfigDict, StringConstraints
5from postrfp.shared.serial.common import Pagination, TimestampedId
8# COMMON TYPES
10AUTH_POLICY_TYPE = Annotated[
11 str,
12 Field(description="Authorization policy - CEL expression"),
13 StringConstraints(max_length=4000),
14]
17# UNIFIED MODELS
20class ContentSchema(TimestampedId):
21 """Unified content schema model - works for create, update, and response"""
23 # Required/core fields
24 name: str
25 description: Optional[str] = None
26 spec_doc: dict[str, Any]
27 auth_policy: Optional[AUTH_POLICY_TYPE] = None
28 tags: list[str] = Field(default_factory=list)
30 # Optional for input, present in responses
31 org_id: Optional[str] = None
33 model_config = ConfigDict(from_attributes=True)
36class TagSummary(BaseModel):
37 """Tag reference - can be minimal for lists or full for details"""
39 id: int
40 name: Optional[str] = None
41 description: Optional[str] = None
43 model_config = ConfigDict(from_attributes=True)
46class SubjectSummary(BaseModel):
47 """Subject reference - can be minimal for lists or full for details"""
49 id: int
50 parent_id: Optional[int] = None
51 name: Optional[str] = None
52 code: Optional[str] = None
53 description: Optional[str] = None
54 subject_type: Optional[str] = None
56 model_config = ConfigDict(from_attributes=True)
59class ContentDocument(TimestampedId):
60 """Unified content model - adapts based on usage context"""
62 # Required/core fields
63 title: str
64 schema_id: int
66 # Optional fields
67 content_doc: Optional[dict[str, Any]] = None # Full content only in detail views
68 primary_subject_id: Optional[int] = None
69 auth_policy: Optional[AUTH_POLICY_TYPE] = None
71 # Flexible references - can be IDs (input) or objects (output)
72 tags: list[TagSummary] = Field(default_factory=list)
73 subjects: list[SubjectSummary] = Field(default_factory=list)
75 # Response-only fields
76 author_org_id: Optional[str] = None
77 last_updated_by_id: Optional[str] = None
79 model_config = ConfigDict(from_attributes=True)
82class ContentRelationships(BaseModel):
83 """Content relationship data using the unified Content model"""
85 children: list[ContentDocument] = Field(default_factory=list)
86 parents: list[ContentDocument] = Field(default_factory=list)
87 references: list[ContentDocument] = Field(default_factory=list)
88 related: list[ContentDocument] = Field(default_factory=list)
89 superseded_by: list[ContentDocument] = Field(default_factory=list)
90 supersedes: list[ContentDocument] = Field(default_factory=list)
91 derived_from: list[ContentDocument] = Field(default_factory=list)
92 derived: list[ContentDocument] = Field(default_factory=list)
95class ContentRelationshipDoc(BaseModel):
96 """Content with its relationships"""
98 content_id: int
99 content_title: str
100 relationships: ContentRelationships
103# PERMISSION MODELS
106class PolicyRequest(BaseModel):
107 """Request to update/delete a policy"""
109 entity_type: Literal["Content", "ContentSpec", "Subject"]
110 entity_id: int
111 policy: Optional[str] = None # None for delete operations
114class PolicyResponse(PolicyRequest):
115 """Response from policy operations"""
117 success: bool
118 entity_name: Optional[str] = None
119 error: Optional[str] = None
120 operation_performed: Optional[str] = None
123class PermissionUpdated(BaseModel):
124 """Response for individual permission update operations"""
126 operation_performed: str
127 permission_name: str
128 entity_type: str
129 entity_name: str
130 target_org: str
132 model_config = ConfigDict(from_attributes=True)
135class SubjectDocument(TimestampedId):
136 """Unified subject model - works for create, update, and response"""
138 # Required/core fields
139 name: str
140 subject_type: str # Using string to handle enum serialization
142 # Optional fields
143 code: Optional[str] = None
144 description: Optional[str] = None
145 auth_policy: Optional[AUTH_POLICY_TYPE] = None
146 parent_id: Optional[int] = None
147 managing_org_id: Optional[str] = None
148 subject_metadata: Optional[dict[str, Any]] = None
150 model_config = ConfigDict(from_attributes=True)
153class ContentQElementPairDocument(BaseModel):
154 """Mapping between a question element and a content field via JSON Pointer"""
156 # Optional for input, required for responses
157 id: Optional[int] = None
159 # Required fields
160 question_element_id: int
161 content_reference: str = Field(
162 description="JSON Pointer expression to locate field in content document (e.g., '$.sla.uptime')"
163 )
165 # Response-only field (parent relationship)
166 content_map_id: Optional[int] = None
168 model_config = ConfigDict(from_attributes=True)
171class ContentSpecMapDocument(BaseModel):
172 """Mapping between question elements and content fields"""
174 # Optional for input, required for responses
175 id: Optional[int] = Field(
176 None, description="Unique ID of the content spec map, assigned by the system"
177 )
179 # Required fields
180 name: str
181 description: str
182 content_spec_id: int
184 content_spec_name: Optional[str] = Field(
185 None, description="Name of the associated content spec, not required for input"
186 )
188 # Nested pairs - can be provided on create/update
189 pairs: list[ContentQElementPairDocument] = Field(default_factory=list)
191 model_config = ConfigDict(from_attributes=True)
194# GENERIC RESPONSE TYPES
197class ListResponse(BaseModel):
198 """Generic list response that works with any item type"""
200 items: (
201 list[ContentDocument]
202 | list[SubjectSummary]
203 | list[ContentSchema]
204 | list[ContentSpecMapDocument]
205 )
206 pagination: Pagination
208 model_config = ConfigDict(from_attributes=True)
211class DeletionResponse(BaseModel):
212 """Standard deletion response"""
214 success: bool
215 message: str
217 model_config = ConfigDict(from_attributes=True)
220class SchemaPointersResponse(BaseModel):
221 """Response containing valid JSON Pointer paths for a schema"""
223 content_spec_id: int
224 content_spec_name: str
225 pointers: list[str] = Field(
226 description="List of valid JSON Pointer paths that can be used in ContentQElementPair mappings"
227 )
229 model_config = ConfigDict(from_attributes=True)