From c31b062a6b49a5a3a94bf1ed41f00d589936f9cc Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 26 Apr 2026 09:24:45 +0200 Subject: [PATCH] Pydantic models out of app.py into the new schemas.py --- src/repo_registry/web_api/app.py | 530 ++------------------------- src/repo_registry/web_api/schemas.py | 498 +++++++++++++++++++++++++ 2 files changed, 533 insertions(+), 495 deletions(-) create mode 100644 src/repo_registry/web_api/schemas.py diff --git a/src/repo_registry/web_api/app.py b/src/repo_registry/web_api/app.py index 06b87a2..1daeb72 100644 --- a/src/repo_registry/web_api/app.py +++ b/src/repo_registry/web_api/app.py @@ -2,16 +2,49 @@ from __future__ import annotations from dataclasses import asdict from pathlib import Path -from typing import Any from fastapi import Depends, FastAPI, HTTPException, Query -from pydantic import BaseModel, Field +from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict from repo_registry.core.service import RegistryService from repo_registry.llm_extraction import LLMCandidateExtractor, create_llm_connect_adapter from repo_registry.repo_ingestion.git import GitIngestionService from repo_registry.storage.sqlite import NotFoundError, RegistryStore +from repo_registry.web_api.schemas import ( + AbilityCreate, + AbilitySummaryResponse, + AbilityUpdate, + AnalysisRunCreate, + AnalysisRunResponse, + CandidateAbilityMerge, + CandidateCapabilityMerge, + CandidateCapabilityRelink, + CandidateEdit, + CandidateEvidenceMerge, + CandidateFeatureMerge, + CandidateGraphApproval, + CandidateGraphResponse, + CandidateLeafRelink, + CandidateRejection, + CapabilityCreate, + CapabilitySummaryResponse, + CapabilityUpdate, + ContentChunkResponse, + EvidenceCreate, + EvidenceUpdate, + FeatureCreate, + FeatureUpdate, + IdResponse, + ObservedFactResponse, + RepositoryAbilityMapResponse, + RepositoryCreate, + RepositoryResponse, + RepositoryUpdate, + ReviewDecisionResponse, + ScanSummaryResponse, + SearchResultResponse, +) class Settings(BaseSettings): @@ -46,499 +79,6 @@ def get_service(settings: Settings = Depends(get_settings)) -> RegistryService: ) -class RepositoryCreate(BaseModel): - url: str - name: str | None = None - description: str | None = None - branch: str = "main" - - model_config = { - "json_schema_extra": { - "examples": [ - { - "url": "https://github.com/example/repository.git", - "name": "Example Repository", - "description": "Optional human-readable repository summary.", - "branch": "main", - } - ] - } - } - - -class RepositoryUpdate(BaseModel): - name: str | None = None - description: str | None = None - branch: str | None = None - - model_config = { - "json_schema_extra": { - "examples": [ - { - "name": "Renamed Repository", - "description": "Updated curator-facing summary.", - "branch": "main", - } - ] - } - } - - -class AbilityCreate(BaseModel): - name: str - description: str = "" - confidence: float = Field(default=1.0, ge=0.0, le=1.0) - - model_config = { - "json_schema_extra": { - "examples": [ - { - "name": "Business Email Routing", - "description": "Route inbound messages to the right team.", - "confidence": 0.92, - } - ] - } - } - - -class AbilityUpdate(BaseModel): - name: str | None = None - description: str | None = None - confidence: float | None = Field(default=None, ge=0.0, le=1.0) - - -class CapabilityCreate(BaseModel): - ability_id: int - name: str - description: str = "" - inputs: list[str] = Field(default_factory=list) - outputs: list[str] = Field(default_factory=list) - confidence: float = Field(default=1.0, ge=0.0, le=1.0) - - model_config = { - "json_schema_extra": { - "examples": [ - { - "ability_id": 1, - "name": "Classify Incoming Email", - "description": "Classify messages by intent.", - "inputs": ["subject", "body"], - "outputs": ["intent", "confidence"], - "confidence": 0.88, - } - ] - } - } - - -class CapabilityUpdate(BaseModel): - name: str | None = None - description: str | None = None - inputs: list[str] | None = None - outputs: list[str] | None = None - confidence: float | None = Field(default=None, ge=0.0, le=1.0) - - -class FeatureCreate(BaseModel): - capability_id: int - name: str - type: str - location: str = "" - confidence: float = Field(default=1.0, ge=0.0, le=1.0) - - model_config = { - "json_schema_extra": { - "examples": [ - { - "capability_id": 1, - "name": "POST /api/classify-email", - "type": "REST endpoint", - "location": "src/routes/classify_email.py", - "confidence": 0.84, - } - ] - } - } - - -class FeatureUpdate(BaseModel): - name: str | None = None - type: str | None = None - location: str | None = None - confidence: float | None = Field(default=None, ge=0.0, le=1.0) - - -class EvidenceCreate(BaseModel): - capability_id: int - type: str - reference: str - strength: str = "medium" - - model_config = { - "json_schema_extra": { - "examples": [ - { - "capability_id": 1, - "type": "unit_test", - "reference": "tests/test_email_classification.py", - "strength": "strong", - } - ] - } - } - - -class EvidenceUpdate(BaseModel): - type: str | None = None - reference: str | None = None - strength: str | None = None - - -class AnalysisRunCreate(BaseModel): - source_path: str | None = None - - model_config = { - "json_schema_extra": { - "examples": [ - {}, - {"source_path": "/path/to/local/repository"}, - ] - } - } - - -class CandidateGraphApproval(BaseModel): - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [{"notes": "Approved after curator review."}] - } - } - - -class CandidateRejection(BaseModel): - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [{"notes": "Rejected because the claim is too generic."}] - } - } - - -class CandidateEdit(BaseModel): - name: str - description: str = "" - confidence: float = Field(default=0.5, ge=0.0, le=1.0) - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [ - { - "name": "Service Health Monitoring", - "description": "Expose health state for operational checks.", - "confidence": 0.9, - "notes": "Renamed from generated review seed.", - } - ] - } - } - - -class CandidateCapabilityRelink(BaseModel): - target_ability_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [ - {"target_ability_id": 2, "notes": "Move under operational ability."} - ] - } - } - - -class CandidateLeafRelink(BaseModel): - target_capability_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [ - { - "target_capability_id": 3, - "notes": "Evidence supports a different capability.", - } - ] - } - } - - -class CandidateAbilityMerge(BaseModel): - target_ability_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [ - {"target_ability_id": 2, "notes": "Duplicate ability wording."} - ] - } - } - - -class CandidateCapabilityMerge(BaseModel): - target_capability_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [ - {"target_capability_id": 3, "notes": "Duplicate capability."} - ] - } - } - - -class CandidateFeatureMerge(BaseModel): - target_feature_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [{"target_feature_id": 4, "notes": "Duplicate route."}] - } - } - - -class CandidateEvidenceMerge(BaseModel): - target_evidence_id: int - notes: str = "" - - model_config = { - "json_schema_extra": { - "examples": [{"target_evidence_id": 5, "notes": "Duplicate evidence."}] - } - } - - -class RepositoryResponse(BaseModel): - id: int - name: str - url: str - description: str | None - branch: str - status: str - - -class RepositorySnapshotResponse(BaseModel): - id: int - repository_id: int - commit_hash: str - branch: str - source_path: str - file_count: int - - -class AnalysisRunResponse(BaseModel): - id: int - repository_id: int - snapshot_id: int | None - status: str - started_at: str - completed_at: str | None - error_message: str | None - scanner_version: str - - -class ReviewDecisionResponse(BaseModel): - id: int - repository_id: int - analysis_run_id: int | None - action: str - notes: str - created_at: str - - -class ObservedFactResponse(BaseModel): - id: int - repository_id: int - analysis_run_id: int - snapshot_id: int | None - kind: str - path: str - name: str - value: str - metadata: dict[str, Any] - - -class ContentChunkResponse(BaseModel): - id: int - repository_id: int - analysis_run_id: int - snapshot_id: int | None - path: str - kind: str - start_line: int - end_line: int - text: str - - -class ScanSummaryResponse(BaseModel): - analysis_run: AnalysisRunResponse - snapshot: RepositorySnapshotResponse | None - facts: list[ObservedFactResponse] - - -class SourceReferenceResponse(BaseModel): - fact_id: int | None - path: str - kind: str - name: str - line: int | None = None - - -class CandidateEvidenceResponse(BaseModel): - id: int - type: str - reference: str - strength: str - status: str - source_refs: list[SourceReferenceResponse] - - -class CandidateFeatureResponse(BaseModel): - id: int - name: str - type: str - location: str - confidence: float - status: str - source_refs: list[SourceReferenceResponse] - confidence_label: str - - -class CandidateCapabilityResponse(BaseModel): - id: int - name: str - description: str - inputs: list[str] - outputs: list[str] - confidence: float - status: str - source_refs: list[SourceReferenceResponse] - confidence_label: str - features: list[CandidateFeatureResponse] - evidence: list[CandidateEvidenceResponse] - - -class CandidateAbilityResponse(BaseModel): - id: int - name: str - description: str - confidence: float - status: str - source_refs: list[SourceReferenceResponse] - confidence_label: str - capabilities: list[CandidateCapabilityResponse] - - -class CandidateGraphResponse(BaseModel): - repository: RepositoryResponse - analysis_run: AnalysisRunResponse - abilities: list[CandidateAbilityResponse] - - -class EvidenceResponse(BaseModel): - id: int - type: str - reference: str - strength: str - source_refs: list[SourceReferenceResponse] - - -class FeatureResponse(BaseModel): - id: int - name: str - type: str - location: str - confidence: float - confidence_label: str - source_refs: list[SourceReferenceResponse] - - -class CapabilityResponse(BaseModel): - id: int - name: str - description: str - inputs: list[str] - outputs: list[str] - confidence: float - confidence_label: str - features: list[FeatureResponse] - evidence: list[EvidenceResponse] - - -class AbilityResponse(BaseModel): - id: int - name: str - description: str - confidence: float - confidence_label: str - capabilities: list[CapabilityResponse] - - -class RepositoryAbilityMapResponse(BaseModel): - repository: RepositoryResponse - abilities: list[AbilityResponse] - - -class IdResponse(BaseModel): - id: int - - -class SearchResultResponse(BaseModel): - repository_id: int - repository_name: str - match_type: str - match_name: str - confidence: float - confidence_label: str - match_description: str - matched_field: str - ability_id: int | None = None - ability_name: str | None = None - capability_id: int | None = None - capability_name: str | None = None - evidence_level: str | None = None - source_reference: str | None = None - - -class AbilitySummaryResponse(BaseModel): - id: int - repository_id: int - repository_name: str - name: str - description: str - confidence: float - confidence_label: str - - -class CapabilitySummaryResponse(BaseModel): - id: int - repository_id: int - repository_name: str - ability_id: int - ability_name: str - name: str - description: str - confidence: float - confidence_label: str - - API_DESCRIPTION = ( "Register repositories, analyze their observable implementation facts, " "curate reviewable ability graphs, and search approved repository abilities." diff --git a/src/repo_registry/web_api/schemas.py b/src/repo_registry/web_api/schemas.py new file mode 100644 index 0000000..164728e --- /dev/null +++ b/src/repo_registry/web_api/schemas.py @@ -0,0 +1,498 @@ +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, Field + + +class RepositoryCreate(BaseModel): + url: str + name: str | None = None + description: str | None = None + branch: str = "main" + + model_config = { + "json_schema_extra": { + "examples": [ + { + "url": "https://github.com/example/repository.git", + "name": "Example Repository", + "description": "Optional human-readable repository summary.", + "branch": "main", + } + ] + } + } + + +class RepositoryUpdate(BaseModel): + name: str | None = None + description: str | None = None + branch: str | None = None + + model_config = { + "json_schema_extra": { + "examples": [ + { + "name": "Renamed Repository", + "description": "Updated curator-facing summary.", + "branch": "main", + } + ] + } + } + + +class AbilityCreate(BaseModel): + name: str + description: str = "" + confidence: float = Field(default=1.0, ge=0.0, le=1.0) + + model_config = { + "json_schema_extra": { + "examples": [ + { + "name": "Business Email Routing", + "description": "Route inbound messages to the right team.", + "confidence": 0.92, + } + ] + } + } + + +class AbilityUpdate(BaseModel): + name: str | None = None + description: str | None = None + confidence: float | None = Field(default=None, ge=0.0, le=1.0) + + +class CapabilityCreate(BaseModel): + ability_id: int + name: str + description: str = "" + inputs: list[str] = Field(default_factory=list) + outputs: list[str] = Field(default_factory=list) + confidence: float = Field(default=1.0, ge=0.0, le=1.0) + + model_config = { + "json_schema_extra": { + "examples": [ + { + "ability_id": 1, + "name": "Classify Incoming Email", + "description": "Classify messages by intent.", + "inputs": ["subject", "body"], + "outputs": ["intent", "confidence"], + "confidence": 0.88, + } + ] + } + } + + +class CapabilityUpdate(BaseModel): + name: str | None = None + description: str | None = None + inputs: list[str] | None = None + outputs: list[str] | None = None + confidence: float | None = Field(default=None, ge=0.0, le=1.0) + + +class FeatureCreate(BaseModel): + capability_id: int + name: str + type: str + location: str = "" + confidence: float = Field(default=1.0, ge=0.0, le=1.0) + + model_config = { + "json_schema_extra": { + "examples": [ + { + "capability_id": 1, + "name": "POST /api/classify-email", + "type": "REST endpoint", + "location": "src/routes/classify_email.py", + "confidence": 0.84, + } + ] + } + } + + +class FeatureUpdate(BaseModel): + name: str | None = None + type: str | None = None + location: str | None = None + confidence: float | None = Field(default=None, ge=0.0, le=1.0) + + +class EvidenceCreate(BaseModel): + capability_id: int + type: str + reference: str + strength: str = "medium" + + model_config = { + "json_schema_extra": { + "examples": [ + { + "capability_id": 1, + "type": "unit_test", + "reference": "tests/test_email_classification.py", + "strength": "strong", + } + ] + } + } + + +class EvidenceUpdate(BaseModel): + type: str | None = None + reference: str | None = None + strength: str | None = None + + +class AnalysisRunCreate(BaseModel): + source_path: str | None = None + + model_config = { + "json_schema_extra": { + "examples": [ + {}, + {"source_path": "/path/to/local/repository"}, + ] + } + } + + +class CandidateGraphApproval(BaseModel): + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [{"notes": "Approved after curator review."}] + } + } + + +class CandidateRejection(BaseModel): + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [{"notes": "Rejected because the claim is too generic."}] + } + } + + +class CandidateEdit(BaseModel): + name: str + description: str = "" + confidence: float = Field(default=0.5, ge=0.0, le=1.0) + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [ + { + "name": "Service Health Monitoring", + "description": "Expose health state for operational checks.", + "confidence": 0.9, + "notes": "Renamed from generated review seed.", + } + ] + } + } + + +class CandidateCapabilityRelink(BaseModel): + target_ability_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [ + {"target_ability_id": 2, "notes": "Move under operational ability."} + ] + } + } + + +class CandidateLeafRelink(BaseModel): + target_capability_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [ + { + "target_capability_id": 3, + "notes": "Evidence supports a different capability.", + } + ] + } + } + + +class CandidateAbilityMerge(BaseModel): + target_ability_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [ + {"target_ability_id": 2, "notes": "Duplicate ability wording."} + ] + } + } + + +class CandidateCapabilityMerge(BaseModel): + target_capability_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [ + {"target_capability_id": 3, "notes": "Duplicate capability."} + ] + } + } + + +class CandidateFeatureMerge(BaseModel): + target_feature_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [{"target_feature_id": 4, "notes": "Duplicate route."}] + } + } + + +class CandidateEvidenceMerge(BaseModel): + target_evidence_id: int + notes: str = "" + + model_config = { + "json_schema_extra": { + "examples": [{"target_evidence_id": 5, "notes": "Duplicate evidence."}] + } + } + + +class RepositoryResponse(BaseModel): + id: int + name: str + url: str + description: str | None + branch: str + status: str + + +class RepositorySnapshotResponse(BaseModel): + id: int + repository_id: int + commit_hash: str + branch: str + source_path: str + file_count: int + + +class AnalysisRunResponse(BaseModel): + id: int + repository_id: int + snapshot_id: int | None + status: str + started_at: str + completed_at: str | None + error_message: str | None + scanner_version: str + + +class ReviewDecisionResponse(BaseModel): + id: int + repository_id: int + analysis_run_id: int | None + action: str + notes: str + created_at: str + + +class ObservedFactResponse(BaseModel): + id: int + repository_id: int + analysis_run_id: int + snapshot_id: int | None + kind: str + path: str + name: str + value: str + metadata: dict[str, Any] + + +class ContentChunkResponse(BaseModel): + id: int + repository_id: int + analysis_run_id: int + snapshot_id: int | None + path: str + kind: str + start_line: int + end_line: int + text: str + + +class ScanSummaryResponse(BaseModel): + analysis_run: AnalysisRunResponse + snapshot: RepositorySnapshotResponse | None + facts: list[ObservedFactResponse] + + +class SourceReferenceResponse(BaseModel): + fact_id: int | None + path: str + kind: str + name: str + line: int | None = None + + +class CandidateEvidenceResponse(BaseModel): + id: int + type: str + reference: str + strength: str + status: str + source_refs: list[SourceReferenceResponse] + + +class CandidateFeatureResponse(BaseModel): + id: int + name: str + type: str + location: str + confidence: float + status: str + source_refs: list[SourceReferenceResponse] + confidence_label: str + + +class CandidateCapabilityResponse(BaseModel): + id: int + name: str + description: str + inputs: list[str] + outputs: list[str] + confidence: float + status: str + source_refs: list[SourceReferenceResponse] + confidence_label: str + features: list[CandidateFeatureResponse] + evidence: list[CandidateEvidenceResponse] + + +class CandidateAbilityResponse(BaseModel): + id: int + name: str + description: str + confidence: float + status: str + source_refs: list[SourceReferenceResponse] + confidence_label: str + capabilities: list[CandidateCapabilityResponse] + + +class CandidateGraphResponse(BaseModel): + repository: RepositoryResponse + analysis_run: AnalysisRunResponse + abilities: list[CandidateAbilityResponse] + + +class EvidenceResponse(BaseModel): + id: int + type: str + reference: str + strength: str + source_refs: list[SourceReferenceResponse] + + +class FeatureResponse(BaseModel): + id: int + name: str + type: str + location: str + confidence: float + confidence_label: str + source_refs: list[SourceReferenceResponse] + + +class CapabilityResponse(BaseModel): + id: int + name: str + description: str + inputs: list[str] + outputs: list[str] + confidence: float + confidence_label: str + features: list[FeatureResponse] + evidence: list[EvidenceResponse] + + +class AbilityResponse(BaseModel): + id: int + name: str + description: str + confidence: float + confidence_label: str + capabilities: list[CapabilityResponse] + + +class RepositoryAbilityMapResponse(BaseModel): + repository: RepositoryResponse + abilities: list[AbilityResponse] + + +class IdResponse(BaseModel): + id: int + + +class SearchResultResponse(BaseModel): + repository_id: int + repository_name: str + match_type: str + match_name: str + confidence: float + confidence_label: str + match_description: str + matched_field: str + ability_id: int | None = None + ability_name: str | None = None + capability_id: int | None = None + capability_name: str | None = None + evidence_level: str | None = None + source_reference: str | None = None + + +class AbilitySummaryResponse(BaseModel): + id: int + repository_id: int + repository_name: str + name: str + description: str + confidence: float + confidence_label: str + + +class CapabilitySummaryResponse(BaseModel): + id: int + repository_id: int + repository_name: str + ability_id: int + ability_name: str + name: str + description: str + confidence: float + confidence_label: str