- upload.py: request_upload_url, upload_document, confirm_upload (3 routes) - crud.py: list_documents, get_document, patch_document, delete_document, classify_document (5 routes) - content.py: stream_document_content + _parse_range (1 route) - shared.py: _CLOUD_PROVIDERS, UploadUrlRequest, DocumentPatch (single definitions) - __init__.py: router aggregator prefix=/api/documents, 9 routes total - documents.py monolith deleted; all URL paths unchanged - list_documents registered on parent router to avoid FastAPI empty-path/prefix check - get_storage_backend_for_document re-exported for test monkeypatching compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
"""Shared constants and Pydantic request models for the documents API package.
|
|
|
|
CODE-08: Single definition of _CLOUD_PROVIDERS, UploadUrlRequest, and DocumentPatch.
|
|
These are imported by upload.py and crud.py — never duplicated.
|
|
|
|
T-05-06-01: _CLOUD_PROVIDERS is an allowlist frozenset; target_backend validated
|
|
against it (never against user-supplied strings).
|
|
T-05-09-01: DocumentPatch fields declared explicitly — mass assignment prevented.
|
|
T-05-09-02: filename_no_path_separators validator preserved verbatim (path traversal
|
|
defense at the API boundary — D-11 analysis: stays in Pydantic model).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
# Valid cloud backend slugs (T-05-06-01: validated against allowlist, not user-supplied string)
|
|
_CLOUD_PROVIDERS = frozenset({"google_drive", "onedrive", "nextcloud", "webdav"})
|
|
|
|
|
|
class UploadUrlRequest(BaseModel):
|
|
filename: str
|
|
content_type: str
|
|
|
|
|
|
class DocumentPatch(BaseModel):
|
|
"""Pydantic model for PATCH /api/documents/{doc_id}.
|
|
|
|
Optional fields — model_fields_set distinguishes "not provided" from "set to null".
|
|
At least one field must be present in model_fields_set (enforced in the handler).
|
|
|
|
T-05-09-01: explicit field declaration prevents mass assignment.
|
|
T-05-09-02: only filename and folder_id are accepted — no other fields can be set.
|
|
"""
|
|
filename: Optional[str] = Field(None, min_length=1, max_length=255)
|
|
folder_id: Optional[uuid.UUID] = None
|
|
|
|
@field_validator("filename")
|
|
@classmethod
|
|
def filename_no_path_separators(cls, v: Optional[str]) -> Optional[str]:
|
|
if v is not None and ("/" in v or "\\" in v):
|
|
raise ValueError("filename must not contain path separators")
|
|
return v
|