11import logging
2- from typing import Any
32
43from adrf .views import APIView
54from django .conf import settings
65from django .core .cache import cache as django_cache
76from django .utils .decorators import method_decorator
87from django .views .decorators .cache import cache_page
8+ from drf_spectacular .extensions import OpenApiSerializerExtension
99from drf_spectacular .utils import OpenApiParameter , OpenApiResponse , extend_schema
10- from pydantic import BaseModel , Field
11- from rest_framework import status
10+ from rest_framework import serializers , status
1211from rest_framework .exceptions import NotFound
1312from rest_framework .pagination import LimitOffsetPagination
1413from rest_framework .request import Request
2423THROTTLE_RATES : dict [str , str ] = settings .REST_FRAMEWORK .get ("DEFAULT_THROTTLE_RATES" , {}) # type: ignore[assignment]
2524
2625
27- class ResumeListResponse (BaseModel ):
28- """Paginated list of resumes."""
26+ class QueueMetricsSerializer (serializers .Serializer ):
27+ stream_length = serializers .IntegerField (help_text = "Total items in Redis stream" )
28+ queue_length = serializers .IntegerField (help_text = "Pending items in queue" )
29+ scheduled_retries = serializers .IntegerField (help_text = "Items scheduled for retry" )
30+ active_jobs = serializers .IntegerField (help_text = "Currently processing jobs" )
31+ redis_memory_usage = serializers .IntegerField (help_text = "Redis memory usage in bytes" )
2932
30- count : int = Field (description = "Total number of resumes" )
31- next : str | None = Field (default = None , description = "Next page URL" )
32- previous : str | None = Field (default = None , description = "Previous page URL" )
33- results : list [ResumeResponse ] = Field (description = "Resumes for current page" )
33+
34+ class ProcessingConfigSerializer (serializers .Serializer ):
35+ text_llm_provider = serializers .CharField (help_text = "LLM provider for text extraction" )
36+ text_llm_model = serializers .CharField (help_text = "LLM model for text extraction" )
37+ ocr_llm_provider = serializers .CharField (help_text = "LLM provider for OCR" )
38+ ocr_llm_model = serializers .CharField (help_text = "LLM model for OCR" )
39+ generate_review = serializers .BooleanField (help_text = "Whether AI review is enabled" )
40+ store_in_db = serializers .BooleanField (help_text = "Whether to store in database" )
41+
42+
43+ class HealthResponseSerializer (serializers .Serializer ):
44+ status = serializers .CharField (help_text = "Service status: ok, degraded, or down" )
45+ service = serializers .CharField (help_text = "Service name" )
46+ queue = QueueMetricsSerializer ()
47+ processing_config = ProcessingConfigSerializer ()
48+
49+
50+ class FileTypeConfigSerializer (serializers .Serializer ):
51+ """Configuration for a single file type."""
52+
53+ media_type = serializers .CharField (help_text = "MIME type" )
54+ category = serializers .CharField (help_text = "File category (document, image, etc)" )
55+ max_size_mb = serializers .IntegerField (help_text = "Maximum file size in MB" )
56+ parser = serializers .CharField (help_text = "Parser used for this file type" )
57+
58+
59+ class FileConfigResponseSerializer (serializers .Serializer ):
60+ """Dictionary of file type configurations keyed by extension."""
61+
62+ def to_representation (self , instance : dict ) -> dict :
63+ """Return the dict as-is since keys are dynamic extensions."""
64+ return instance
65+
66+
67+ class FileConfigResponseExtension (OpenApiSerializerExtension ):
68+ """Extension to generate proper additionalProperties schema for FileConfigResponse."""
69+
70+ target_class = "processor.views.FileConfigResponseSerializer"
71+
72+ def map_serializer (self , auto_schema , direction ):
73+ """Generate object schema with additionalProperties pointing to FileTypeConfig."""
74+ return {
75+ "type" : "object" ,
76+ "additionalProperties" : auto_schema .resolve_serializer (FileTypeConfigSerializer , direction ).ref ,
77+ }
78+
79+
80+ class ResumeListResponse (serializers .Serializer ):
81+ count = serializers .IntegerField (help_text = "Total number of resumes" )
82+ next = serializers .CharField (allow_null = True , help_text = "Next page URL" )
83+ previous = serializers .CharField (allow_null = True , help_text = "Previous page URL" )
84+ results = serializers .ListField (help_text = "Resumes for current page" )
3485
3586
3687class ResumeCollectionView (APIView ):
@@ -135,7 +186,7 @@ class HealthView(APIView):
135186 """Service health status endpoint."""
136187
137188 @extend_schema (
138- responses = {200 : OpenApiResponse ( description = "Health status including queue stats and configuration" ) },
189+ responses = {200 : HealthResponseSerializer },
139190 description = "Get service health status. Returns system status, queue statistics, and processing configuration." ,
140191 )
141192 async def get (self , request : Request ) -> Response :
@@ -159,11 +210,11 @@ class FileConfigView(APIView):
159210 """File upload configuration endpoint."""
160211
161212 @extend_schema (
162- responses = {200 : OpenApiResponse ( description = "File upload configuration" ) },
163- description = "Get file upload configuration. Returns allowed extensions, MIME types, max sizes, and categories." ,
213+ responses = {200 : FileConfigResponseSerializer },
214+ description = "Get file upload configuration. Returns allowed extensions, MIME types, max sizes, and categories. Keys are file extensions. " ,
164215 )
165216 @method_decorator (cache_page (60 * 60 * 24 ))
166- def get (self , request : Any ) -> Response :
217+ def get (self , request : Request ) -> Response :
167218 cache_key = "file_config_v1"
168219 cached = django_cache .get (cache_key )
169220 if cached :
0 commit comments