Browse Source

fix init_subclass for pydantic v2, add some missing type hints, format black

Benedikt Bartscher 1 year ago
parent
commit
37c360e786

+ 5 - 1
reflex/components/component.py

@@ -283,7 +283,11 @@ class Component(BaseComponent, ABC):
                 continue
 
             # unwrap Optional from field_type
-            field_type = typing.get_args(field_type)[0] if types.is_optional(field_type) else field_type
+            field_type = (
+                typing.get_args(field_type)[0]
+                if types.is_optional(field_type)
+                else field_type
+            )
 
             # Check whether the key is a component prop.
             if types._issubclass(field_type, Var):

+ 0 - 4
reflex/components/radix/themes/base.py

@@ -100,13 +100,9 @@ class RadixThemesComponent(Component):
         """
         component = super().create(*children, **props)
         if component.library is None:
-<<<<<<< HEAD
-            component.library = RadixThemesComponent.model_fields["library"].default
-=======
             component.library = RadixThemesComponent.model_fields[
                 "library"
             ].default
->>>>>>> f7035d9b (optionalize some Component props)
         component.alias = "RadixThemes" + (
             component.tag or component.__class__.__name__
         )

+ 6 - 2
reflex/components/radix/themes/components/dropdown_menu.py

@@ -113,7 +113,9 @@ class DropdownMenuContent(RadixThemesComponent):
     avoid_collisions: Optional[Var[bool]] = None
 
     # The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { "top": 20, "left": 20 }. Defaults to 0.
-    collision_padding: Optional[Var[Union[float, int, Dict[str, Union[float, int]]]]] = None
+    collision_padding: Optional[
+        Var[Union[float, int, Dict[str, Union[float, int]]]]
+    ] = None
 
     # The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. Defaults to 0.
     arrow_padding: Optional[Var[Union[float, int]]] = None
@@ -204,7 +206,9 @@ class DropdownMenuSubContent(RadixThemesComponent):
     avoid_collisions: Optional[Var[bool]] = None
 
     # The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { "top": 20, "left": 20 }. Defaults to 0.
-    collision_padding: Optional[Var[Union[float, int, Dict[str, Union[float, int]]]]] = None
+    collision_padding: Optional[
+        Var[Union[float, int, Dict[str, Union[float, int]]]]
+    ] = None
 
     # The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. Defaults to 0.
     arrow_padding: Optional[Var[Union[float, int]]] = None

+ 3 - 1
reflex/components/radix/themes/components/tooltip.py

@@ -60,7 +60,9 @@ class Tooltip(RadixThemesComponent):
     avoid_collisions: Optional[Var[bool]] = None
 
     # The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { "top": 20, "left": 20 }. Defaults to 0.
-    collision_padding: Optional[Var[Union[float, int, Dict[str, Union[float, int]]]]] = None
+    collision_padding: Optional[
+        Var[Union[float, int, Dict[str, Union[float, int]]]]
+    ] = None
 
     # The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. Defaults to 0.
     arrow_padding: Optional[Var[Union[float, int]]] = None

+ 20 - 8
reflex/state.py

@@ -195,7 +195,7 @@ def _split_substate_key(substate_key: str) -> tuple[str, str]:
     return token, state_name
 
 
-class BaseState(Base, ABC, extra=pydantic.Extra.allow):
+class BaseState(Base, ABC, extra="allow"):
     """The state of the app."""
 
     # A map from the var name to the var.
@@ -288,7 +288,11 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         kwargs["parent_state"] = parent_state
 
         for prop_name, prop in self.base_vars.items():
-            if prop_name not in kwargs and self.model_fields[prop_name].is_required():
+            if (
+                prop_name not in kwargs
+                and prop_name in self.model_fields
+                and self.model_fields[prop_name].is_required()
+            ):
                 kwargs[prop_name] = prop.get_default_value()
 
         super().__init__(*args, **kwargs)
@@ -362,7 +366,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         ]
 
     @classmethod
-    def __init_subclass__(cls, **kwargs):
+    def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
         """Do some magic for the subclass initialization.
 
         Args:
@@ -371,7 +375,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         Raises:
             ValueError: If a substate class shadows another.
         """
-        super().__init_subclass__(**kwargs)
+        #  super().__init_subclass__(**kwargs)
         # Event handlers should not shadow builtin state methods.
         cls._check_overridden_methods()
 
@@ -486,6 +490,12 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             cls.event_handlers[name] = handler
             setattr(cls, name, handler)
 
+        # Inherited vars are handled in __getattribute__, not by pydantic
+        for prop in cls.model_fields.copy():
+            if prop in cls.inherited_vars or prop in cls.inherited_backend_vars:
+                del cls.model_fields[prop]
+        cls.model_rebuild(force=True)
+
         cls._init_var_dependency_dicts()
 
     @staticmethod
@@ -994,9 +1004,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         # If the state hasn't been initialized yet, return the default value.
         if not super().__getattribute__("__dict__"):
             return super().__getattribute__(name)
-        private_attrs = super().__getattribute__("__pydantic_private__")
-        if private_attrs is None:
-            return super().__getattribute__(name)
 
         inherited_vars = {
             **super().__getattribute__("inherited_vars"),
@@ -1009,6 +1016,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             if parent_state is not None:
                 return getattr(parent_state, name)
 
+        private_attrs = super().__getattribute__("__pydantic_private__")
+        if private_attrs is None:
+            return super().__getattribute__(name)
         backend_vars = private_attrs["_backend_vars"]
         if name in backend_vars:
             value = backend_vars[name]
@@ -1088,7 +1098,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         # on the browser also resets the values on the backend.
         fields = self.get_fields()
         for prop_name in self.base_vars:
-            field = fields[prop_name]
+            if not (field := fields.get(prop_name)):
+                # inherited values are reset in the parent state
+                continue
             if isinstance(field.default, ClientStorageBase) or (
                 isinstance(field.annotation, type)
                 and issubclass(field.annotation, ClientStorageBase)

+ 5 - 2
tests/states/mutation.py

@@ -37,7 +37,7 @@ class DictMutationTestState(BaseState):
         self.details.pop("age")
 
     # dict in list
-    address: List[Dict[str,str]] = [{"home": "home address"}, {"work": "work address"}]
+    address: List[Dict[str, str]] = [{"home": "home address"}, {"work": "work address"}]
 
     def remove_home_address(self):
         """Remove the home address from dict in the list."""
@@ -48,7 +48,10 @@ class DictMutationTestState(BaseState):
         self.address[0]["street"] = "street address"
 
     # nested dict
-    friend_in_nested_dict: Dict[str, Union[str, Dict[str,str]]] = {"name": "Nikhil", "friend": {"name": "Alek"}}
+    friend_in_nested_dict: Dict[str, Union[str, Dict[str, str]]] = {
+        "name": "Nikhil",
+        "friend": {"name": "Alek"},
+    }
 
     def change_friend_name(self):
         """Change the friend's name in the nested dict."""