@@ -419,7 +419,6 @@ def evaluate_style_namespaces(style: ComponentStyle) -> dict:
419419
420420# Map from component to styling.
421421ComponentStyle = dict [str | type [BaseComponent ] | Callable | ComponentNamespace , Any ]
422- ComponentChild = types .PrimitiveType | Var | BaseComponent
423422ComponentChildTypes = (* types .PrimitiveTypes , Var , BaseComponent , type (None ))
424423
425424
@@ -480,7 +479,21 @@ def _components_from(
480479 return ()
481480
482481
483- def _deterministic_hash (value : object ) -> int :
482+ def _hash_str (value : str ) -> str :
483+ return md5 (f'"{ value } "' .encode (), usedforsecurity = False ).hexdigest ()
484+
485+
486+ def _hash_sequence (value : Sequence ) -> str :
487+ return _hash_str (str ([_deterministic_hash (v ) for v in value ]))
488+
489+
490+ def _hash_dict (value : dict ) -> str :
491+ return _hash_sequence (
492+ sorted ([(k , _deterministic_hash (v )) for k , v in value .items ()])
493+ )
494+
495+
496+ def _deterministic_hash (value : object ) -> str :
484497 """Hash a rendered dictionary.
485498
486499 Args:
@@ -492,37 +505,28 @@ def _deterministic_hash(value: object) -> int:
492505 Raises:
493506 TypeError: If the value is not hashable.
494507 """
495- if isinstance (value , BaseComponent ):
496- # If the value is a component, hash its rendered code.
497- rendered_code = value .render ()
498- return _deterministic_hash (rendered_code )
499- if isinstance (value , Var ):
500- return _deterministic_hash ((value ._js_expr , value ._get_all_var_data ()))
501- if isinstance (value , VarData ):
502- return _deterministic_hash (dataclasses .asdict (value ))
503- if isinstance (value , dict ):
504- # Sort the dictionary to ensure consistent hashing.
505- return _deterministic_hash (
506- tuple (sorted ((k , _deterministic_hash (v )) for k , v in value .items ()))
507- )
508- if isinstance (value , int ):
508+ if value is None :
509+ # Hash None as a special case.
510+ return "None"
511+ if isinstance (value , (int , float , enum .Enum )):
509512 # Hash numbers and booleans directly.
510- return int (value )
511- if isinstance (value , float ):
512- return _deterministic_hash (str (value ))
513+ return str (value )
513514 if isinstance (value , str ):
514- return int (md5 (f'"{ value } "' .encode ()).hexdigest (), 16 )
515+ return _hash_str (value )
516+ if isinstance (value , dict ):
517+ return _hash_dict (value )
515518 if isinstance (value , (tuple , list )):
516519 # Hash tuples by hashing each element.
517- return _deterministic_hash (
518- "[" + "," .join (map (str , map (_deterministic_hash , value ))) + "]"
520+ return _hash_sequence (value )
521+ if isinstance (value , Var ):
522+ return _hash_str (
523+ str ((value ._js_expr , _deterministic_hash (value ._get_all_var_data ())))
519524 )
520- if isinstance (value , enum .Enum ):
521- # Hash enums by their name.
522- return _deterministic_hash (str (value ))
523- if value is None :
524- # Hash None as a special case.
525- return _deterministic_hash ("None" )
525+ if isinstance (value , VarData ):
526+ return _hash_dict (dataclasses .asdict (value ))
527+ if isinstance (value , BaseComponent ):
528+ # If the value is a component, hash its rendered code.
529+ return _hash_dict (value .render ())
526530
527531 msg = (
528532 f"Cannot hash value `{ value } ` of type `{ type (value ).__name__ } `. "
@@ -1038,9 +1042,13 @@ def _get_component_prop_names(cls) -> set[str]:
10381042 name
10391043 for name in cls .get_fields ()
10401044 if name in cls .get_props ()
1041- and types ._issubclass (
1042- types .value_inside_optional (types .get_field_type (cls , name )), Component
1045+ and isinstance (
1046+ field_type := types .value_inside_optional (
1047+ types .get_field_type (cls , name )
1048+ ),
1049+ type ,
10431050 )
1051+ and issubclass (field_type , Component )
10441052 }
10451053
10461054 def _get_components_in_props (self ) -> Sequence [BaseComponent ]:
@@ -1508,8 +1516,9 @@ def _iter_parent_classes_with_method(cls, method: str) -> Sequence[type[Componen
15081516 Returns:
15091517 A sequence of parent classes that define the method (differently than the base).
15101518 """
1519+ current_class_method = getattr (Component , method , None )
15111520 seen_methods = (
1512- {getattr ( Component , method ) } if hasattr ( Component , method ) else set ()
1521+ {current_class_method } if current_class_method is not None else set ()
15131522 )
15141523 clzs : list [type [Component ]] = []
15151524 for clz in cls .mro ():
@@ -1807,7 +1816,7 @@ def extract_var_hooks(hook: Var):
18071816
18081817 # Add the hook code from add_hooks for each parent class (this is reversed to preserve
18091818 # the order of the hooks in the final output)
1810- for clz in reversed (tuple ( self ._iter_parent_classes_with_method ("add_hooks" ) )):
1819+ for clz in reversed (self ._iter_parent_classes_with_method ("add_hooks" )):
18111820 for hook in clz .add_hooks (self ):
18121821 if isinstance (hook , Var ):
18131822 extract_var_hooks (hook )
@@ -2457,7 +2466,7 @@ def _get_tag_name(cls, component: Component) -> str | None:
24572466 return None
24582467
24592468 # Compute the hash based on the rendered code.
2460- code_hash = _deterministic_hash (rendered_code )
2469+ code_hash = _hash_str ( _deterministic_hash (rendered_code ) )
24612470
24622471 # Format the tag name including the hash.
24632472 return format .format_state_name (
0 commit comments