@@ -366,7 +366,18 @@ def coerce_obj(obj, type_):
366
366
f"Cannot coerce { obj !r} into { type_ } { msg } { self .label_str } "
367
367
) from e
368
368
369
- return expand_and_coerce (object_ , self .pattern )
369
+ # Special handling for MultiInputObjects (which are annoying)
370
+ if isinstance (self .pattern , tuple ) and self .pattern [0 ] == MultiInputObj :
371
+ try :
372
+ self .check_coercible (object_ , self .pattern [1 ][0 ])
373
+ except TypeError :
374
+ pass
375
+ else :
376
+ obj = [object_ ]
377
+ else :
378
+ obj = object_
379
+
380
+ return expand_and_coerce (obj , self .pattern )
370
381
371
382
def check_type (self , type_ : ty .Type [ty .Any ]):
372
383
"""Checks the given type to see whether it matches or is a subtype of the
@@ -413,7 +424,7 @@ def expand_and_check(tp, pattern: ty.Union[type, tuple]):
413
424
f"{ self .pattern } { self .label_str } "
414
425
)
415
426
tp_args = get_args (tp )
416
- self .check_coercible (tp_origin , pattern_origin )
427
+ self .check_type_coercible (tp_origin , pattern_origin )
417
428
if issubclass (pattern_origin , ty .Mapping ):
418
429
return check_mapping (tp_args , pattern_args )
419
430
if issubclass (pattern_origin , tuple ):
@@ -446,7 +457,7 @@ def check_basic(tp, target):
446
457
+ "\n \n " .join (f"{ a } -> { e } " for a , e in zip (tp_args , reasons ))
447
458
)
448
459
if not self .is_subclass (tp , target ):
449
- self .check_coercible (tp , target )
460
+ self .check_type_coercible (tp , target )
450
461
451
462
def check_union (tp , pattern_args ):
452
463
if get_origin (tp ) in UNION_TYPES :
@@ -526,19 +537,46 @@ def check_sequence(tp_args, pattern_args):
526
537
for arg in tp_args :
527
538
expand_and_check (arg , pattern_args [0 ])
528
539
529
- return expand_and_check (type_ , self .pattern )
540
+ # Special handling for MultiInputObjects (which are annoying)
541
+ if isinstance (self .pattern , tuple ) and self .pattern [0 ] == MultiInputObj :
542
+ pattern = (ty .Union , [self .pattern [1 ][0 ], (ty .List , self .pattern [1 ])])
543
+ else :
544
+ pattern = self .pattern
545
+ return expand_and_check (type_ , pattern )
546
+
547
+ def check_coercible (self , source : ty .Any , target : ty .Union [type , ty .Any ]):
548
+ """Checks whether the source object is coercible to the target type given the coercion
549
+ rules defined in the `coercible` and `not_coercible` attrs
550
+
551
+ Parameters
552
+ ----------
553
+ source : object
554
+ the object to be coerced
555
+ target : type or typing.Any
556
+ the target type for the object to be coerced to
557
+
558
+ Raises
559
+ ------
560
+ TypeError
561
+ If the object cannot be coerced into the target type depending on the explicit
562
+ inclusions and exclusions set in the `coercible` and `not_coercible` member attrs
563
+ """
564
+ self .check_type_coercible (type (source ), target , source_repr = repr (source ))
530
565
531
- def check_coercible (
532
- self , source : ty .Union [object , type ], target : ty .Union [type , ty .Any ]
566
+ def check_type_coercible (
567
+ self ,
568
+ source : ty .Union [type , ty .Any ],
569
+ target : ty .Union [type , ty .Any ],
570
+ source_repr : ty .Optional [str ] = None ,
533
571
):
534
- """Checks whether the source object or type is coercible to the target type
572
+ """Checks whether the source type is coercible to the target type
535
573
given the coercion rules defined in the `coercible` and `not_coercible` attrs
536
574
537
575
Parameters
538
576
----------
539
- source : object or type
540
- source object or type to be coerced
541
- target : type or ty .Any
577
+ source : type or typing.Any
578
+ source type to be coerced
579
+ target : type or typing .Any
542
580
target type for the source to be coerced to
543
581
544
582
Raises
@@ -548,10 +586,12 @@ def check_coercible(
548
586
explicit inclusions and exclusions set in the `coercible` and `not_coercible`
549
587
member attrs
550
588
"""
589
+ if source_repr is None :
590
+ source_repr = repr (source )
551
591
# Short-circuit the basic cases where the source and target are the same
552
592
if source is target :
553
593
return
554
- if self .superclass_auto_cast and self .is_subclass (target , type ( source ) ):
594
+ if self .superclass_auto_cast and self .is_subclass (target , source ):
555
595
logger .info (
556
596
"Attempting to coerce %s into %s due to super-to-sub class coercion "
557
597
"being permitted" ,
@@ -563,13 +603,11 @@ def check_coercible(
563
603
if source_origin is not None :
564
604
source = source_origin
565
605
566
- source_check = self .is_subclass if inspect .isclass (source ) else self .is_instance
567
-
568
606
def matches_criteria (criteria ):
569
607
return [
570
608
(src , tgt )
571
609
for src , tgt in criteria
572
- if source_check (source , src ) and self .is_subclass (target , tgt )
610
+ if self . is_subclass (source , src ) and self .is_subclass (target , tgt )
573
611
]
574
612
575
613
def type_name (t ):
@@ -580,7 +618,7 @@ def type_name(t):
580
618
581
619
if not matches_criteria (self .coercible ):
582
620
raise TypeError (
583
- f"Cannot coerce { repr ( source ) } into { target } { self .label_str } as the "
621
+ f"Cannot coerce { source_repr } into { target } { self .label_str } as the "
584
622
"coercion doesn't match any of the explicit inclusion criteria: "
585
623
+ ", " .join (
586
624
f"{ type_name (s )} -> { type_name (t )} " for s , t in self .coercible
@@ -589,7 +627,7 @@ def type_name(t):
589
627
matches_not_coercible = matches_criteria (self .not_coercible )
590
628
if matches_not_coercible :
591
629
raise TypeError (
592
- f"Cannot coerce { repr ( source ) } into { target } { self .label_str } as it is explicitly "
630
+ f"Cannot coerce { source_repr } into { target } { self .label_str } as it is explicitly "
593
631
"excluded by the following coercion criteria: "
594
632
+ ", " .join (
595
633
f"{ type_name (s )} -> { type_name (t )} "
@@ -683,7 +721,7 @@ def is_instance(
683
721
if inspect .isclass (obj ):
684
722
return candidate is type
685
723
if issubtype (type (obj ), candidate ) or (
686
- type (obj ) is dict and candidate is ty .Mapping
724
+ type (obj ) is dict and candidate is ty .Mapping # noqa: E721
687
725
):
688
726
return True
689
727
else :
0 commit comments