2020 List ,
2121 Literal ,
2222 Mapping ,
23+ NoReturn ,
2324 Optional ,
2425 Sequence ,
2526 Tuple ,
@@ -155,15 +156,7 @@ def get_type_hints(obj: Any) -> Dict[str, Any]:
155156 return get_type_hints_og (obj )
156157
157158
158- def unionize (* args : GenericType ) -> Type :
159- """Unionize the types.
160-
161- Args:
162- args: The types to unionize.
163-
164- Returns:
165- The unionized types.
166- """
159+ def _unionize (args : list [GenericType ]) -> Type :
167160 if not args :
168161 return Any # pyright: ignore [reportReturnType]
169162 if len (args ) == 1 :
@@ -175,6 +168,18 @@ def unionize(*args: GenericType) -> Type:
175168 return Union [unionize (* first_half ), unionize (* second_half )] # pyright: ignore [reportReturnType]
176169
177170
171+ def unionize (* args : GenericType ) -> Type :
172+ """Unionize the types.
173+
174+ Args:
175+ args: The types to unionize.
176+
177+ Returns:
178+ The unionized types.
179+ """
180+ return _unionize ([arg for arg in args if arg is not NoReturn ])
181+
182+
178183def is_none (cls : GenericType ) -> bool :
179184 """Check if a class is None.
180185
@@ -560,7 +565,12 @@ def does_obj_satisfy_typed_dict(obj: Any, cls: GenericType) -> bool:
560565
561566
562567def _isinstance (
563- obj : Any , cls : GenericType , * , nested : int = 0 , treat_var_as_type : bool = True
568+ obj : Any ,
569+ cls : GenericType ,
570+ * ,
571+ nested : int = 0 ,
572+ treat_var_as_type : bool = True ,
573+ treat_mutable_obj_as_immutable : bool = False ,
564574) -> bool :
565575 """Check if an object is an instance of a class.
566576
@@ -569,6 +579,7 @@ def _isinstance(
569579 cls: The class to check against.
570580 nested: How many levels deep to check.
571581 treat_var_as_type: Whether to treat Var as the type it represents, i.e. _var_type.
582+ treat_mutable_obj_as_immutable: Whether to treat mutable objects as immutable. Useful if a component declares a mutable object as a prop, but the value is not expected to change.
572583
573584 Returns:
574585 Whether the object is an instance of the class.
@@ -585,7 +596,13 @@ def _isinstance(
585596 obj ._var_value , cls , nested = nested , treat_var_as_type = True
586597 )
587598 if isinstance (obj , Var ):
588- return treat_var_as_type and _issubclass (obj ._var_type , cls )
599+ return treat_var_as_type and typehint_issubclass (
600+ obj ._var_type ,
601+ cls ,
602+ treat_mutable_superclasss_as_immutable = treat_mutable_obj_as_immutable ,
603+ treat_literals_as_union_of_types = True ,
604+ treat_any_as_subtype_of_everything = True ,
605+ )
589606
590607 if cls is None or cls is type (None ):
591608 return obj is None
@@ -618,12 +635,18 @@ def _isinstance(
618635 args = get_args (cls )
619636
620637 if not args :
638+ if treat_mutable_obj_as_immutable :
639+ if origin is dict :
640+ origin = Mapping
641+ elif origin is list or origin is set :
642+ origin = Sequence
621643 # cls is a simple generic class
622644 return isinstance (obj , origin )
623645
624646 if nested > 0 and args :
625647 if origin is list :
626- return isinstance (obj , list ) and all (
648+ expected_class = Sequence if treat_mutable_obj_as_immutable else list
649+ return isinstance (obj , expected_class ) and all (
627650 _isinstance (
628651 item ,
629652 args [0 ],
@@ -657,7 +680,12 @@ def _isinstance(
657680 )
658681 )
659682 if origin in (dict , Mapping , Breakpoints ):
660- return isinstance (obj , Mapping ) and all (
683+ expected_class = (
684+ dict
685+ if origin is dict and not treat_mutable_obj_as_immutable
686+ else Mapping
687+ )
688+ return isinstance (obj , expected_class ) and all (
661689 _isinstance (
662690 key , args [0 ], nested = nested - 1 , treat_var_as_type = treat_var_as_type
663691 )
@@ -670,7 +698,8 @@ def _isinstance(
670698 for key , value in obj .items ()
671699 )
672700 if origin is set :
673- return isinstance (obj , set ) and all (
701+ expected_class = Sequence if treat_mutable_obj_as_immutable else set
702+ return isinstance (obj , expected_class ) and all (
674703 _isinstance (
675704 item ,
676705 args [0 ],
@@ -910,20 +939,32 @@ def safe_issubclass(cls: Type, cls_check: Type | tuple[Type, ...]):
910939 return False
911940
912941
913- def typehint_issubclass (possible_subclass : Any , possible_superclass : Any ) -> bool :
942+ def typehint_issubclass (
943+ possible_subclass : Any ,
944+ possible_superclass : Any ,
945+ * ,
946+ treat_mutable_superclasss_as_immutable : bool = False ,
947+ treat_literals_as_union_of_types : bool = True ,
948+ treat_any_as_subtype_of_everything : bool = False ,
949+ ) -> bool :
914950 """Check if a type hint is a subclass of another type hint.
915951
916952 Args:
917953 possible_subclass: The type hint to check.
918954 possible_superclass: The type hint to check against.
955+ treat_mutable_superclasss_as_immutable: Whether to treat target classes as immutable.
956+ treat_literals_as_union_of_types: Whether to treat literals as a union of their types.
957+ treat_any_as_subtype_of_everything: Whether to treat Any as a subtype of everything. This is the default behavior in Python.
919958
920959 Returns:
921960 Whether the type hint is a subclass of the other type hint.
922961 """
923962 if possible_superclass is Any :
924963 return True
925964 if possible_subclass is Any :
926- return False
965+ return treat_any_as_subtype_of_everything
966+ if possible_subclass is NoReturn :
967+ return True
927968
928969 provided_type_origin = get_origin (possible_subclass )
929970 accepted_type_origin = get_origin (possible_superclass )
@@ -932,6 +973,19 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
932973 # In this case, we are dealing with a non-generic type, so we can use issubclass
933974 return issubclass (possible_subclass , possible_superclass )
934975
976+ if treat_literals_as_union_of_types and is_literal (possible_superclass ):
977+ args = get_args (possible_superclass )
978+ return any (
979+ typehint_issubclass (
980+ possible_subclass ,
981+ type (arg ),
982+ treat_mutable_superclasss_as_immutable = treat_mutable_superclasss_as_immutable ,
983+ treat_literals_as_union_of_types = treat_literals_as_union_of_types ,
984+ treat_any_as_subtype_of_everything = treat_any_as_subtype_of_everything ,
985+ )
986+ for arg in args
987+ )
988+
935989 # Remove this check when Python 3.10 is the minimum supported version
936990 if hasattr (types , "UnionType" ):
937991 provided_type_origin = (
@@ -948,29 +1002,67 @@ def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> boo
9481002 if accepted_type_origin is Union :
9491003 if provided_type_origin is not Union :
9501004 return any (
951- typehint_issubclass (possible_subclass , accepted_arg )
1005+ typehint_issubclass (
1006+ possible_subclass ,
1007+ accepted_arg ,
1008+ treat_mutable_superclasss_as_immutable = treat_mutable_superclasss_as_immutable ,
1009+ treat_literals_as_union_of_types = treat_literals_as_union_of_types ,
1010+ treat_any_as_subtype_of_everything = treat_any_as_subtype_of_everything ,
1011+ )
9521012 for accepted_arg in accepted_args
9531013 )
9541014 return all (
9551015 any (
956- typehint_issubclass (provided_arg , accepted_arg )
1016+ typehint_issubclass (
1017+ provided_arg ,
1018+ accepted_arg ,
1019+ treat_mutable_superclasss_as_immutable = treat_mutable_superclasss_as_immutable ,
1020+ treat_literals_as_union_of_types = treat_literals_as_union_of_types ,
1021+ treat_any_as_subtype_of_everything = treat_any_as_subtype_of_everything ,
1022+ )
9571023 for accepted_arg in accepted_args
9581024 )
9591025 for provided_arg in provided_args
9601026 )
1027+ if provided_type_origin is Union :
1028+ return all (
1029+ typehint_issubclass (
1030+ provided_arg ,
1031+ possible_superclass ,
1032+ treat_mutable_superclasss_as_immutable = treat_mutable_superclasss_as_immutable ,
1033+ treat_literals_as_union_of_types = treat_literals_as_union_of_types ,
1034+ treat_any_as_subtype_of_everything = treat_any_as_subtype_of_everything ,
1035+ )
1036+ for provided_arg in provided_args
1037+ )
1038+
1039+ provided_type_origin = provided_type_origin or possible_subclass
1040+ accepted_type_origin = accepted_type_origin or possible_superclass
1041+
1042+ if treat_mutable_superclasss_as_immutable :
1043+ if accepted_type_origin is dict :
1044+ accepted_type_origin = Mapping
1045+ elif accepted_type_origin is list or accepted_type_origin is set :
1046+ accepted_type_origin = Sequence
9611047
9621048 # Check if the origin of both types is the same (e.g., list for list[int])
963- # This probably should be issubclass instead of ==
964- if ( provided_type_origin or possible_subclass ) != (
965- accepted_type_origin or possible_superclass
1049+ if not safe_issubclass (
1050+ provided_type_origin or possible_subclass , # pyright: ignore [reportArgumentType]
1051+ accepted_type_origin or possible_superclass , # pyright: ignore [reportArgumentType]
9661052 ):
9671053 return False
9681054
9691055 # Ensure all specific types are compatible with accepted types
9701056 # Note this is not necessarily correct, as it doesn't check against contravariance and covariance
9711057 # It also ignores when the length of the arguments is different
9721058 return all (
973- typehint_issubclass (provided_arg , accepted_arg )
1059+ typehint_issubclass (
1060+ provided_arg ,
1061+ accepted_arg ,
1062+ treat_mutable_superclasss_as_immutable = treat_mutable_superclasss_as_immutable ,
1063+ treat_literals_as_union_of_types = treat_literals_as_union_of_types ,
1064+ treat_any_as_subtype_of_everything = treat_any_as_subtype_of_everything ,
1065+ )
9741066 for provided_arg , accepted_arg in zip (
9751067 provided_args , accepted_args , strict = False
9761068 )
0 commit comments