from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, Field, model_validator class FeedAcquireRequest(BaseModel): url: str = Field(min_length=1, max_length=2000) max_entries: int = Field(default=2_000, ge=1, le=10_000) class FeedImportRequest(FeedAcquireRequest): name: str = Field(min_length=1, max_length=300) source_name: str = Field( min_length=1, max_length=120, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$", ) description: str | None = Field(default=None, max_length=4000) class FeedEntryPayload(BaseModel): id: str = Field(min_length=1, max_length=2000) title: str = Field(min_length=1, max_length=1000) url: str | None = Field(default=None, max_length=2000) summary: str | None = None content: str | None = None author: str | None = Field(default=None, max_length=500) published_at: datetime | None = None updated_at: datetime | None = None categories: list[str] = Field(default_factory=list, max_length=100) enclosures: list[dict[str, Any]] = Field(default_factory=list, max_length=100) visibility: Literal["public", "tenant", "private"] = "public" metadata: dict[str, Any] = Field(default_factory=dict) class FeedPublicationEntryPayload(FeedEntryPayload): source_kind: Literal["event", "publication", "case", "report"] source_module: str = Field( min_length=1, max_length=100, pattern=r"^[a-z][a-z0-9_]*$", ) source_ref: str = Field(min_length=1, max_length=500) source_revision: str | None = Field(default=None, max_length=200) class FeedDocumentResponse(BaseModel): format: Literal["rss", "atom"] title: str source_url: str description: str | None = None home_url: str | None = None language: str | None = None updated_at: datetime | None = None acquired_at: datetime | None = None fresh_until: datetime | None = None etag: str | None = None last_modified: str | None = None content_type: str | None = None sha256: str entries: list[FeedEntryPayload] metadata: dict[str, Any] = Field(default_factory=dict) class FeedRenderPayload(BaseModel): format: Literal["rss", "atom"] title: str = Field(min_length=1, max_length=1000) feed_url: str = Field(min_length=1, max_length=2000) home_url: str = Field(min_length=1, max_length=2000) description: str | None = None language: str | None = Field(default=None, max_length=100) entries: list[FeedPublicationEntryPayload] = Field( default_factory=list, max_length=10_000, ) audience: Literal["public", "tenant", "private"] = "public" class SnapshotCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=300) source_name: str = Field( min_length=1, max_length=120, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$", ) description: str | None = Field(default=None, max_length=4000) format: Literal["json", "csv"] = "json" rows: list[dict[str, Any]] | None = Field(default=None, max_length=10_000) csv_text: str | None = Field(default=None, max_length=5_000_000) delimiter: Literal[",", ";", "\t", "|"] = "," @model_validator(mode="after") def validate_payload(self) -> "SnapshotCreateRequest": if self.format == "json" and self.rows is None: raise ValueError("JSON snapshots require rows.") if self.format == "json" and self.csv_text is not None: raise ValueError("JSON snapshots cannot include CSV text.") if self.format == "csv" and not self.csv_text: raise ValueError("CSV snapshots require CSV text.") if self.format == "csv" and self.rows is not None: raise ValueError("CSV snapshots cannot include JSON rows.") return self class ManagedFileSourceCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=300) source_name: str = Field( min_length=1, max_length=120, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$", ) description: str | None = Field(default=None, max_length=4000) file_asset_id: str = Field(min_length=1, max_length=36) file_version_id: str | None = Field(default=None, min_length=1, max_length=36) delimiter: Literal[",", ";", "\t", "|"] = "," sheet_name: str | None = Field(default=None, min_length=1, max_length=255) class SqlSourceCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=300) source_name: str = Field( min_length=1, max_length=120, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$", ) description: str | None = Field(default=None, max_length=4000) configuration_id: str = Field(min_length=1, max_length=36) schema_name: str | None = Field( default=None, pattern=r"^[A-Za-z_][A-Za-z0-9_$-]{0,127}$", ) table_name: str = Field( min_length=1, max_length=128, pattern=r"^[A-Za-z_][A-Za-z0-9_$-]{0,127}$", ) class TabularColumnResponse(BaseModel): name: str data_type: str nullable: bool class TabularPushdownResponse(BaseModel): projections: bool pagination: bool filters: list[str] aggregations: list[str] sorting: list[str] class TabularHealthResponse(BaseModel): status: Literal["healthy", "warning", "error", "unknown"] code: str summary: str checked_at: str | None details: dict[str, Any] class TabularPreviewDiagnosticResponse(BaseModel): severity: Literal["info", "warning", "error"] code: str message: str details: dict[str, Any] class TabularSourceResponse(BaseModel): ref: str provider: str source_name: str name: str description: str | None columns: list[TabularColumnResponse] schema_version: str fingerprint: str row_count: int | None byte_count: int | None updated_at: str | None capabilities: list[str] metadata: dict[str, Any] source_mode: Literal["live", "cached", "file_backed", "static"] pushdown: TabularPushdownResponse health: TabularHealthResponse class TabularSourceListResponse(BaseModel): sources: list[TabularSourceResponse] class TabularSourcePreviewResponse(BaseModel): source: TabularSourceResponse rows: list[dict[str, Any]] total_rows: int truncated: bool returned_bytes: int elapsed_ms: int effective_row_limit: int effective_byte_limit: int effective_timeout_ms: int diagnostics: list[TabularPreviewDiagnosticResponse] class TabularSourceDeleteResponse(BaseModel): deleted: bool source_ref: str class SanctionsSourceResponse(BaseModel): provider_id: str publisher: str jurisdiction: str list_type: str source_id: str source_url: str | None parser_version: str licence_notes: str trust_notes: str class SanctionsSourceListResponse(BaseModel): sources: list[SanctionsSourceResponse] class SanctionsSnapshotResponse(BaseModel): ref: str provider_id: str publisher: str jurisdiction: str list_type: str source_id: str source_version: str publication_at: datetime | None effective_at: datetime | None acquired_at: datetime content_type: str byte_count: int sha256: str parser_version: str raw_evidence_ref: str connector_run_id: str signature_evidence: dict[str, Any] licence_notes: str | None trust_notes: str | None transport_evidence: dict[str, Any] class SanctionsSnapshotListResponse(BaseModel): snapshots: list[SanctionsSnapshotResponse] class SanctionsAcquisitionRunResponse(BaseModel): id: str provider_id: str source_id: str status: str attempt_count: int request_evidence: dict[str, Any] response_evidence: dict[str, Any] started_at: datetime finished_at: datetime | None snapshot_id: str | None error: str | None class SanctionsAcquisitionRunListResponse(BaseModel): runs: list[SanctionsAcquisitionRunResponse] class SanctionsRefreshResponse(BaseModel): run_id: str provider_id: str status: str snapshot: SanctionsSnapshotResponse | None error: str | None __all__ = [ "FeedAcquireRequest", "FeedDocumentResponse", "FeedEntryPayload", "FeedImportRequest", "FeedRenderPayload", "ManagedFileSourceCreateRequest", "SnapshotCreateRequest", "SqlSourceCreateRequest", "SanctionsAcquisitionRunListResponse", "SanctionsAcquisitionRunResponse", "SanctionsRefreshResponse", "SanctionsSnapshotListResponse", "SanctionsSnapshotResponse", "SanctionsSourceListResponse", "SanctionsSourceResponse", "TabularColumnResponse", "TabularHealthResponse", "TabularPreviewDiagnosticResponse", "TabularPushdownResponse", "TabularSourceDeleteResponse", "TabularSourceListResponse", "TabularSourcePreviewResponse", "TabularSourceResponse", ]