feat(14-04): add analysis API routes and typed schemas
- POST /analysis/connections/{id}/estimate: scope estimate without bytes (T-14-04)
- POST /analysis/connections/{id}/jobs: enqueue job, returns job_id (ANALYZE-01)
- GET /analysis/jobs, GET /analysis/jobs/{id}: job status list and detail
- POST /analysis/jobs/{id}/cancel: batch cancel (queued items immediate; running cooperative)
- POST /analysis/jobs/{id}/items/{item_id}/cancel|skip|retry: per-item controls (ANALYZE-05)
- AnalysisEstimateRequest/Out, AnalysisEnqueueRequest/Out, AnalysisJobOut: typed schemas
- AnalysisJobOut: both simple (waiting/working/done/skipped/failed) and per-stage counts
- All routes use get_regular_user — admin blocked (T-14-01)
- Response schemas exclude credentials_enc, object_key, raw provider URLs (T-14-02)
- 14/14 test_cloud_analysis_api.py pass; 7 new analysis security tests pass (33/33 total)
This commit is contained in:
@@ -255,3 +255,143 @@ class CacheSettingsUpdateRequest(BaseModel):
|
||||
ge=1,
|
||||
description="Preferred byte ceiling for the local byte cache",
|
||||
)
|
||||
|
||||
|
||||
# ── Phase 14 analysis request schemas ─────────────────────────────────────────
|
||||
|
||||
class AnalysisEstimateRequest(BaseModel):
|
||||
"""Request body for POST /analysis/connections/{id}/estimate.
|
||||
|
||||
scope: "file" | "selection" | "folder" | "connection"
|
||||
provider_item_ids: Required for file/selection/folder scope. Omitted for connection scope.
|
||||
recursive: Expand folder subtree recursively (folder scope only; connection always recursive).
|
||||
"""
|
||||
|
||||
scope: str = Field(..., description="file | selection | folder | connection")
|
||||
provider_item_ids: Optional[List[str]] = Field(
|
||||
default=None,
|
||||
description="Provider item IDs for file/selection/folder scope",
|
||||
)
|
||||
recursive: bool = Field(
|
||||
default=False,
|
||||
description="Expand folder children recursively (folder scope)",
|
||||
)
|
||||
|
||||
|
||||
class AnalysisEnqueueRequest(BaseModel):
|
||||
"""Request body for POST /analysis/connections/{id}/jobs.
|
||||
|
||||
scope: "file" | "selection" | "folder" | "connection"
|
||||
provider_item_ids: Required for file/selection/folder scope.
|
||||
recursive: Expand folder subtree recursively.
|
||||
failure_behavior: "pause_batch" (default) | "continue_item" (D-11).
|
||||
"""
|
||||
|
||||
scope: str = Field(..., description="file | selection | folder | connection")
|
||||
provider_item_ids: Optional[List[str]] = Field(default=None)
|
||||
recursive: bool = Field(default=False)
|
||||
failure_behavior: str = Field(
|
||||
default="pause_batch",
|
||||
description="pause_batch | continue_item",
|
||||
)
|
||||
|
||||
|
||||
# ── Phase 14 analysis response schemas ────────────────────────────────────────
|
||||
|
||||
class AnalysisEstimateOut(BaseModel):
|
||||
"""Estimate response — no credentials, bytes, or object_key (T-14-02).
|
||||
|
||||
supported_count: Files that can be analysed.
|
||||
unsupported_count: Items that cannot be analysed (unsupported type, folder).
|
||||
total_provider_bytes: Sum of provider_size across supported items.
|
||||
recursive: Whether the estimate was recursive.
|
||||
is_partial: True when metadata expansion was incomplete.
|
||||
scope_kind: Echo of the requested scope.
|
||||
"""
|
||||
|
||||
supported_count: int = 0
|
||||
unsupported_count: int = 0
|
||||
total_provider_bytes: int = 0
|
||||
recursive: bool = False
|
||||
is_partial: bool = False
|
||||
scope_kind: str
|
||||
|
||||
|
||||
class AnalysisJobOut(BaseModel):
|
||||
"""Job status response — no credentials or object_key (T-14-02).
|
||||
|
||||
Exposes both simple and detailed aggregate counts. Caller requests detail
|
||||
via ?detail=true query parameter.
|
||||
|
||||
Simple (default): waiting_count, working_count, done_count,
|
||||
skipped_count, failed_count, total_count.
|
||||
Detailed: queued_count, downloading_count, extracting_count,
|
||||
classifying_count, indexed_count, already_current_count,
|
||||
cancelled_count, failed_count, unsupported_count.
|
||||
"""
|
||||
|
||||
job_id: str
|
||||
connection_id: str
|
||||
scope_kind: str
|
||||
status: str
|
||||
failure_behavior: str
|
||||
recursive: bool = False
|
||||
total_count: int = 0
|
||||
|
||||
# Simple labels (always present)
|
||||
waiting_count: int = 0
|
||||
working_count: int = 0
|
||||
done_count: int = 0
|
||||
skipped_count: int = 0
|
||||
failed_count: int = 0
|
||||
|
||||
# Detailed labels (always present — 0 when detail=false, per-stage values when detail=true)
|
||||
queued_count: int = 0
|
||||
downloading_count: int = 0
|
||||
extracting_count: int = 0
|
||||
classifying_count: int = 0
|
||||
indexed_count: int = 0
|
||||
already_current_count: int = 0
|
||||
cancelled_count: int = 0
|
||||
unsupported_count: int = 0
|
||||
|
||||
created_at: Optional[datetime] = None
|
||||
started_at: Optional[datetime] = None
|
||||
finished_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class AnalysisEnqueueOut(BaseModel):
|
||||
"""Enqueue response — returns a stable job_id for status polling (ANALYZE-01).
|
||||
|
||||
No credentials or object_key in response (T-14-02).
|
||||
"""
|
||||
|
||||
job_id: str
|
||||
status: str
|
||||
total_count: int = 0
|
||||
queued_count: int = 0
|
||||
already_current_count: int = 0
|
||||
unsupported_count: int = 0
|
||||
|
||||
|
||||
class AnalysisJobItemOut(BaseModel):
|
||||
"""Per-item job status — no credentials, object_key, or raw provider data (T-14-02)."""
|
||||
|
||||
id: str
|
||||
cloud_item_id: str
|
||||
provider_item_id: str
|
||||
status: str
|
||||
error_code: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
retry_count: int = 0
|
||||
created_at: Optional[datetime] = None
|
||||
finished_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class AnalysisControlOut(BaseModel):
|
||||
"""Generic control result for cancel/skip/retry operations."""
|
||||
|
||||
kind: str # "cancelled" | "skipped" | "retried"
|
||||
reason: Optional[str] = None
|
||||
job_id: Optional[str] = None
|
||||
item_id: Optional[str] = None
|
||||
|
||||
Reference in New Issue
Block a user