77from collections .abc import Callable , Mapping , Sequence
88from enum import Enum , StrEnum , auto
99from http import HTTPMethod , HTTPStatus
10- from typing import Annotated , TypeIs , assert_never
10+ from typing import Annotated , NotRequired , TypedDict , TypeIs , assert_never
1111from zoneinfo import ZoneInfo
1212
1313from beartype import beartype
1616 BaseModel ,
1717 BeforeValidator ,
1818 ConfigDict ,
19+ TypeAdapter ,
1920 ValidationError ,
2021 model_validator ,
2122)
@@ -244,6 +245,59 @@ def to_vumark_database(self) -> VuMarkDatabase:
244245 )
245246
246247
248+ class OAuth2ClientCredentialBody (BaseModel ):
249+ """A request to register an OAuth2 client credential."""
250+
251+ model_config = ConfigDict (strict = True , extra = "forbid" )
252+
253+ client_id : str
254+ client_secret : str
255+ scopes : tuple [str , ...]
256+
257+
258+ class ImageTargetBody (TypedDict ):
259+ """An image target sent to the storage service."""
260+
261+ name : str
262+ width : float
263+ image_base64 : str
264+ active_flag : bool
265+ processing_time_seconds : float
266+ application_metadata : str | None
267+ target_id : str
268+ last_modified_date : NotRequired [str ]
269+ delete_date_optional : NotRequired [str | None ]
270+ upload_date : NotRequired [str ]
271+ tracking_rating : NotRequired [int ]
272+ current_month_recos : NotRequired [int ]
273+ previous_month_recos : NotRequired [int ]
274+ total_recos : NotRequired [int ]
275+ reco_rating : NotRequired [str ]
276+
277+
278+ class ImageTargetUpdateBody (TypedDict ):
279+ """Fields which may update a stored image target."""
280+
281+ name : NotRequired [str ]
282+ width : NotRequired [float ]
283+ active_flag : NotRequired [bool ]
284+ application_metadata : NotRequired [str | None ]
285+ image : NotRequired [str ]
286+
287+
288+ class RecognitionCountsBody (TypedDict ):
289+ """Recognition counts to update on a stored target."""
290+
291+ current_month_recos : NotRequired [int ]
292+ previous_month_recos : NotRequired [int ]
293+ total_recos : NotRequired [int ]
294+
295+
296+ _IMAGE_TARGET_ADAPTER = TypeAdapter (type = ImageTargetBody )
297+ _IMAGE_TARGET_UPDATE_ADAPTER = TypeAdapter (type = ImageTargetUpdateBody )
298+ _RECOGNITION_COUNTS_ADAPTER = TypeAdapter (type = RecognitionCountsBody )
299+
300+
247301@beartype
248302class _InvalidRequestBodyError (Exception ):
249303 """A request body which cannot be used to create a resource.
@@ -685,11 +739,13 @@ def get_oauth2_client_credentials() -> Response:
685739@beartype
686740def put_oauth2_client_credential () -> Response :
687741 """Add or replace an OAuth2 client credential."""
688- value = json .loads (s = request .data )
742+ value = OAuth2ClientCredentialBody .model_validate_json (
743+ json_data = request .data ,
744+ )
689745 credential = OAuth2ClientCredential (
690- client_id = value [ " client_id" ], # pyrefly: ignore [unknown-argument-type]
691- client_secret = value [ " client_secret" ], # pyrefly: ignore [unknown-argument-type]
692- scopes = tuple ( value [ " scopes" ]), # pyrefly: ignore [unknown-argument-type]
746+ client_id = value . client_id ,
747+ client_secret = value . client_secret ,
748+ scopes = value . scopes ,
693749 )
694750 TARGET_MANAGER .add_oauth2_client_credential (credential = credential )
695751 return Response (response = "" , status = HTTPStatus .NO_CONTENT )
@@ -719,19 +775,22 @@ def create_target(database_name: str) -> Response:
719775 :status 201: The target has been created.
720776 :status 404: There is no cloud database with the given name.
721777 """
722- request_json = json .loads (s = request .data )
778+ request_body = _IMAGE_TARGET_ADAPTER .validate_json (
779+ request .data ,
780+ strict = True ,
781+ )
723782 settings = TargetManagerSettings .model_validate (obj = {})
724783
725- image_bytes = base64 .b64decode (s = request_json ["image_base64" ]) # pyrefly: ignore [unknown-argument-type]
784+ image_bytes = base64 .b64decode (s = request_body ["image_base64" ])
726785 target_tracking_rater = settings .target_rater .to_target_rater ()
727786 target = ImageTarget (
728- name = request_json ["name" ], # pyrefly: ignore [unknown-argument-type]
729- width = request_json ["width" ], # pyrefly: ignore [unknown-argument-type]
787+ name = request_body ["name" ],
788+ width = request_body ["width" ],
730789 image_value = image_bytes ,
731- active_flag = request_json ["active_flag" ], # pyrefly: ignore [unknown-argument-type]
732- processing_time_seconds = request_json ["processing_time_seconds" ], # pyrefly: ignore [unknown-argument-type]
733- application_metadata = request_json ["application_metadata" ], # pyrefly: ignore [unknown-argument-type]
734- target_id = request_json ["target_id" ], # pyrefly: ignore [unknown-argument-type]
790+ active_flag = request_body ["active_flag" ],
791+ processing_time_seconds = request_body ["processing_time_seconds" ],
792+ application_metadata = request_body ["application_metadata" ],
793+ target_id = request_body ["target_id" ],
735794 target_tracking_rater = target_tracking_rater ,
736795 )
737796 with TARGET_MANAGER .lock :
@@ -807,7 +866,10 @@ def delete_target(database_name: str, target_id: str) -> Response:
807866@beartype
808867def update_target (database_name : str , target_id : str ) -> Response :
809868 """Update a target."""
810- request_json = json .loads (s = request .data )
869+ request_body = _IMAGE_TARGET_UPDATE_ADAPTER .validate_json (
870+ request .data ,
871+ strict = True ,
872+ )
811873
812874 with TARGET_MANAGER .lock :
813875 database = _find_cloud_database (database_name = database_name )
@@ -816,26 +878,25 @@ def update_target(database_name: str, target_id: str) -> Response:
816878
817879 target = database .get_target (target_id = target_id )
818880
819- name = request_json .get ("name" , target .name ) # pyrefly: ignore [unknown-variable-type]
820- active_flag = request_json .get ("active_flag" , target .active_flag ) # pyrefly: ignore [unknown-variable-type]
881+ name = request_body .get ("name" , target .name )
882+ active_flag = request_body .get ("active_flag" , target .active_flag )
821883
822884 gmt = ZoneInfo (key = "GMT" )
823885 last_modified_date = datetime .datetime .now (tz = gmt )
824886
825- width = request_json .get ("width" , target .width ) # pyrefly: ignore [unknown-variable-type]
826- application_metadata = request_json .get ( # pyrefly: ignore [unknown-variable-type]
827- "application_metadata" ,
828- target .application_metadata ,
887+ width = request_body .get ("width" , target .width )
888+ application_metadata = request_body .get (
889+ "application_metadata" , target .application_metadata
829890 )
830891 image_value = target .image_value
831- if "image" in request_json :
832- image_value = base64 .b64decode (s = request_json ["image" ]) # pyrefly: ignore [unknown-argument-type]
892+ if "image" in request_body :
893+ image_value = base64 .b64decode (s = request_body ["image" ])
833894 new_target = copy .replace (
834895 target ,
835- name = name , # pyrefly: ignore [unknown-argument-type]
836- width = width , # pyrefly: ignore [unknown-argument-type]
837- active_flag = active_flag , # pyrefly: ignore [unknown-argument-type]
838- application_metadata = application_metadata , # pyrefly: ignore [unknown-argument-type]
896+ name = name ,
897+ width = width ,
898+ active_flag = active_flag ,
899+ application_metadata = application_metadata ,
839900 image_value = image_value ,
840901 last_modified_date = last_modified_date ,
841902 )
@@ -880,7 +941,10 @@ def set_target_recognition_counts(
880941
881942 :status 200: The recognition counts have been set.
882943 """
883- request_json = json .loads (s = request .data )
944+ request_body = _RECOGNITION_COUNTS_ADAPTER .validate_json (
945+ request .data ,
946+ strict = True ,
947+ )
884948
885949 with TARGET_MANAGER .lock :
886950 database = _find_cloud_database (database_name = database_name )
@@ -891,15 +955,13 @@ def set_target_recognition_counts(
891955
892956 new_target = copy .replace (
893957 target ,
894- current_month_recos = request_json .get ( # pyrefly: ignore [unknown-argument-type]
895- "current_month_recos" ,
896- target .current_month_recos ,
958+ current_month_recos = request_body .get (
959+ "current_month_recos" , target .current_month_recos
897960 ),
898- previous_month_recos = request_json .get ( # pyrefly: ignore [unknown-argument-type]
899- "previous_month_recos" ,
900- target .previous_month_recos ,
961+ previous_month_recos = request_body .get (
962+ "previous_month_recos" , target .previous_month_recos
901963 ),
902- total_recos = request_json .get ("total_recos" , target .total_recos ), # pyrefly: ignore [unknown-argument-type]
964+ total_recos = request_body .get ("total_recos" , target .total_recos ),
903965 )
904966
905967 database .targets .remove (target )
0 commit comments