@@ -488,6 +488,47 @@ def _request_params_to_args(
488
488
return values , errors
489
489
490
490
491
+ def _get_field_location (field : ModelField , field_alias_omitted : bool ) -> tuple [str , ...]:
492
+ """Get the location tuple for a field based on whether alias is omitted."""
493
+ if field_alias_omitted :
494
+ return ("body" ,)
495
+ return ("body" , field .alias )
496
+
497
+
498
+ def _get_field_value (received_body : dict [str , Any ] | None , field : ModelField ) -> Any | None :
499
+ """Extract field value from received body, returning None if not found or on error."""
500
+ if received_body is None :
501
+ return None
502
+
503
+ try :
504
+ return received_body .get (field .alias )
505
+ except AttributeError :
506
+ return None
507
+
508
+
509
+ def _resolve_field_type (field_type : type ) -> type :
510
+ """Resolve the actual field type, handling Union types by returning the first non-None type."""
511
+ from typing import get_args , get_origin
512
+
513
+ if get_origin (field_type ) is Union :
514
+ union_args = get_args (field_type )
515
+ non_none_types = [arg for arg in union_args if arg is not type (None )]
516
+ if non_none_types :
517
+ return non_none_types [0 ]
518
+ return field_type
519
+
520
+
521
+ def _convert_value_type (value : Any , field_type : type ) -> Any :
522
+ """Convert value between UploadFile and bytes for type compatibility."""
523
+ if isinstance (value , UploadFile ) and field_type is bytes :
524
+ # Convert UploadFile to bytes for backward compatibility
525
+ return value .file
526
+ elif isinstance (value , bytes ) and field_type == UploadFile :
527
+ # Convert bytes to UploadFile if that's what's expected
528
+ return UploadFile (file = value )
529
+ return value
530
+
531
+
491
532
def _request_body_to_args (
492
533
required_params : list [ModelField ],
493
534
received_body : dict [str , Any ] | None ,
@@ -505,53 +546,29 @@ def _request_body_to_args(
505
546
)
506
547
507
548
for field in required_params :
508
- # This sets the location to:
509
- # { "user": { object } } if field.alias == user
510
- # { { object } if field_alias is omitted
511
- loc : tuple [str , ...] = ("body" , field .alias )
512
- if field_alias_omitted :
513
- loc = ("body" ,)
514
-
515
- value : Any | None = None
549
+ loc = _get_field_location (field , field_alias_omitted )
550
+ value = _get_field_value (received_body , field )
516
551
517
- # Now that we know what to look for, try to get the value from the received body
518
- if received_body is not None :
552
+ # Handle AttributeError from _get_field_value
553
+ if received_body is not None and value is None :
519
554
try :
520
- value = received_body .get (field .alias )
555
+ # Double-check with direct access to distinguish None value from AttributeError
556
+ received_body .get (field .alias )
521
557
except AttributeError :
522
558
errors .append (get_missing_field_error (loc ))
523
559
continue
524
560
525
- # Determine if the field is required
561
+ # Handle missing values
526
562
if value is None :
527
563
if field .required :
528
564
errors .append (get_missing_field_error (loc ))
529
565
else :
530
566
values [field .name ] = deepcopy (field .default )
531
567
continue
532
568
533
- # MAINTENANCE: Handle byte and file fields
534
- # Check if we have an UploadFile but the field expects bytes
535
- from typing import get_args , get_origin
536
-
537
- field_type = field .type_
538
-
539
- # Handle Union types (e.g., Union[bytes, None] for optional parameters)
540
- if get_origin (field_type ) is Union :
541
- # Get the non-None types from the Union
542
- union_args = get_args (field_type )
543
- non_none_types = [arg for arg in union_args if arg is not type (None )]
544
- if non_none_types :
545
- field_type = non_none_types [0 ] # Use the first non-None type
546
-
547
- if isinstance (value , UploadFile ) and field_type is bytes :
548
- # Convert UploadFile to bytes for backward compatibility
549
- value = value .file
550
- elif isinstance (value , bytes ) and field_type == UploadFile :
551
- # Convert bytes to UploadFile if that's what's expected
552
- # This shouldn't normally happen in our current implementation,
553
- # but provides a fallback path
554
- value = UploadFile (file = value )
569
+ # Handle type conversions for UploadFile/bytes compatibility
570
+ field_type = _resolve_field_type (field .type_ )
571
+ value = _convert_value_type (value , field_type )
555
572
556
573
# Finally, validate the value
557
574
values [field .name ] = _validate_field (field = field , value = value , loc = loc , existing_errors = errors )
0 commit comments