diff --git a/reflex/base.py b/reflex/base.py index e5bac421b..c76320468 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -8,7 +8,6 @@ import pydantic from pydantic import BaseModel # from pydantic.fields import ModelField - from reflex import constants @@ -62,9 +61,7 @@ class Base(pydantic.BaseModel): Returns: The object as a json string. """ - from reflex.utils.format import json_dumps - - return json_dumps(self.model_dump()) + return self.model_dump_json() def set(self, **kwargs): """Set multiple fields and return the object. diff --git a/reflex/state.py b/reflex/state.py index 060996af0..a660f18f4 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -150,6 +150,7 @@ RESERVED_BACKEND_VAR_NAMES = { "_substate_var_dependencies", "_always_dirty_computed_vars", "_always_dirty_substates", + "_abc_impl", # pydantic v2 adds this } @@ -223,6 +224,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(): + kwargs[prop_name] = prop.get_default_value() + super().__init__(*args, **kwargs) # Setup the substates. @@ -266,7 +272,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Returns: The string representation of the state. """ - return f"{self.__class__.__name__}({self.dict()})" + return f"{type(self).__name__}({self.dict()})" @classmethod def __pydantic_init_subclass__(cls, **kwargs): @@ -279,7 +285,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): ValueError: If a substate class shadows another. """ is_testing_env = constants.PYTEST_CURRENT_TEST in os.environ - #super().__init_subclass__(**kwargs) + # super().__init_subclass__(**kwargs) # Event handlers should not shadow builtin state methods. cls._check_overridden_methods() @@ -312,21 +318,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): # Track this new subclass in the parent state's subclasses set. parent_state.class_subclasses.add(cls) - cls.new_backend_vars = { - name: value - for name, value in cls.__dict__.items() + new_backend_vars = { + name: value.default + for name, value in cls.__private_attributes__.items() if types.is_backend_variable(name) and name not in cls.inherited_backend_vars + and name not in RESERVED_BACKEND_VAR_NAMES and not isinstance(value, FunctionType) } - cls.backend_vars = {**cls.inherited_backend_vars, **cls.new_backend_vars} + cls.backend_vars = {**cls.inherited_backend_vars, **new_backend_vars} # Set the base and computed vars. cls.base_vars = { - field_name: BaseVar(_var_name=field_name, _var_type=field.annotation)._var_set_state( - cls - ) + field_name: BaseVar( + _var_name=field_name, _var_type=field.annotation + )._var_set_state(cls) for field_name, field in cls.get_fields().items() if field_name not in cls.get_skip_vars() } @@ -359,6 +366,11 @@ 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._init_var_dependency_dicts() @classmethod @@ -567,7 +579,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): ) cls._set_var(prop) cls._create_setter(prop) - cls._set_default_value(prop) @classmethod def add_var(cls, name: str, type_: Any, default_value: Any = None): @@ -631,27 +642,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): cls.event_handlers[setter_name] = event_handler setattr(cls, setter_name, event_handler) - @classmethod - def _set_default_value(cls, prop: BaseVar): - """Set the default value for the var. - - Args: - prop: The var to set the default value for. - """ - # Get the pydantic field for the var. - field = cls.get_fields()[prop._var_name] - if field.is_required(): - default_value = prop.get_default_value() - if default_value is not None: - field.default = default_value - if ( - not field.is_required() - and field.default is None - and not types.is_optional(prop._var_type) - ): - # Ensure frontend uses null coalescing when accessing. - prop._var_type = Optional[prop._var_type] - @staticmethod def _get_base_functions() -> dict[str, FunctionType]: """Get all functions of the state class excluding dunder methods. @@ -834,6 +824,9 @@ 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"), @@ -842,7 +835,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): if name in inherited_vars: return getattr(super().__getattribute__("parent_state"), name) - backend_vars = super().__getattribute__("_backend_vars") + backend_vars = private_attrs["_backend_vars"] if name in backend_vars: value = backend_vars[name] else: diff --git a/reflex/utils/types.py b/reflex/utils/types.py index efdd0ee85..019e2d36a 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -122,10 +122,7 @@ def get_attribute_access_type(cls: GenericType, name: str) -> GenericType | None # pydantic models field = cls.__fields__[name] type_ = field.annotation - raise RuntimeError("Pydantic V2 field type says what?") - if isinstance(type_, ModelField): - type_ = type_.type_ - if not field.required and field.default is None: + if not field.is_required() and field.default is None: # Ensure frontend uses null coalescing when accessing. type_ = Optional[type_] return type_