Files
kite/backend/api/documents/shared.py
T
curo1305andClaude Sonnet 4.6 81337bd9e3 feat(08-05): decompose documents API monolith into documents/ package — CODE-02 CODE-08
- 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>
2026-06-12 10:27:48 +02:00

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