|
1 | 1 | """Define the base Reflex class.""" |
2 | 2 |
|
3 | | -from __future__ import annotations |
| 3 | +from importlib.util import find_spec |
4 | 4 |
|
5 | | -import os |
6 | | -from typing import TYPE_CHECKING, Any |
| 5 | +if find_spec("pydantic") and find_spec("pydantic.v1"): |
| 6 | + from pydantic.v1 import BaseModel |
7 | 7 |
|
8 | | -import pydantic.v1.main as pydantic_main |
9 | | -from pydantic.v1 import BaseModel |
10 | | -from pydantic.v1.fields import ModelField |
| 8 | + class Base(BaseModel): |
| 9 | + """The base class subclassed by all Reflex classes. |
11 | 10 |
|
| 11 | + This class wraps Pydantic and provides common methods such as |
| 12 | + serialization and setting fields. |
12 | 13 |
|
13 | | -def validate_field_name(bases: list[type[BaseModel]], field_name: str) -> None: |
14 | | - """Ensure that the field's name does not shadow an existing attribute of the model. |
15 | | -
|
16 | | - Args: |
17 | | - bases: List of base models to check for shadowed attrs. |
18 | | - field_name: name of attribute |
19 | | -
|
20 | | - Raises: |
21 | | - VarNameError: If state var field shadows another in its parent state |
22 | | - """ |
23 | | - from reflex.utils.exceptions import VarNameError |
24 | | - |
25 | | - # can't use reflex.config.environment here cause of circular import |
26 | | - reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true" |
27 | | - base = None |
28 | | - try: |
29 | | - for base in bases: |
30 | | - if not reload and getattr(base, field_name, None): |
31 | | - pass |
32 | | - except TypeError as te: |
33 | | - msg = ( |
34 | | - f'State var "{field_name}" in {base} has been shadowed by a substate var; ' |
35 | | - f'use a different field name instead".' |
36 | | - ) |
37 | | - raise VarNameError(msg) from te |
38 | | - |
39 | | - |
40 | | -# monkeypatch pydantic validate_field_name method to skip validating |
41 | | -# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True) |
42 | | -pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage] |
43 | | - |
44 | | -if TYPE_CHECKING: |
45 | | - from reflex.vars import Var |
46 | | - |
47 | | - |
48 | | -class Base(BaseModel): |
49 | | - """The base class subclassed by all Reflex classes. |
50 | | -
|
51 | | - This class wraps Pydantic and provides common methods such as |
52 | | - serialization and setting fields. |
53 | | -
|
54 | | - Any data structure that needs to be transferred between the |
55 | | - frontend and backend should subclass this class. |
56 | | - """ |
57 | | - |
58 | | - class Config: |
59 | | - """Pydantic config.""" |
60 | | - |
61 | | - arbitrary_types_allowed = True |
62 | | - use_enum_values = True |
63 | | - extra = "allow" |
64 | | - |
65 | | - def json(self) -> str: |
66 | | - """Convert the object to a json string. |
67 | | -
|
68 | | - Returns: |
69 | | - The object as a json string. |
| 14 | + Any data structure that needs to be transferred between the |
| 15 | + frontend and backend should subclass this class. |
70 | 16 | """ |
71 | | - from reflex.utils.serializers import serialize |
72 | | - |
73 | | - return self.__config__.json_dumps( |
74 | | - self.dict(), |
75 | | - default=serialize, |
76 | | - ) |
77 | | - |
78 | | - def set(self, **kwargs: Any): |
79 | | - """Set multiple fields and return the object. |
80 | | -
|
81 | | - Args: |
82 | | - **kwargs: The fields and values to set. |
83 | | -
|
84 | | - Returns: |
85 | | - The object with the fields set. |
86 | | - """ |
87 | | - for key, value in kwargs.items(): |
88 | | - setattr(self, key, value) |
89 | | - return self |
90 | | - |
91 | | - @classmethod |
92 | | - def get_fields(cls) -> dict[str, ModelField]: |
93 | | - """Get the fields of the object. |
94 | 17 |
|
95 | | - Returns: |
96 | | - The fields of the object. |
97 | | - """ |
98 | | - return cls.__fields__ |
99 | | - |
100 | | - @classmethod |
101 | | - def add_field(cls, var: Var, default_value: Any): |
102 | | - """Add a pydantic field after class definition. |
103 | | -
|
104 | | - Used by State.add_var() to correctly handle the new variable. |
105 | | -
|
106 | | - Args: |
107 | | - var: The variable to add a pydantic field for. |
108 | | - default_value: The default value of the field |
109 | | - """ |
110 | | - var_name = var._var_field_name |
111 | | - new_field = ModelField.infer( |
112 | | - name=var_name, |
113 | | - value=default_value, |
114 | | - annotation=var._var_type, |
115 | | - class_validators=None, |
116 | | - config=cls.__config__, |
117 | | - ) |
118 | | - cls.__fields__.update({var_name: new_field}) |
119 | | - |
120 | | - def get_value(self, key: str) -> Any: |
121 | | - """Get the value of a field. |
122 | | -
|
123 | | - Args: |
124 | | - key: The key of the field. |
125 | | -
|
126 | | - Returns: |
127 | | - The value of the field. |
128 | | - """ |
129 | | - if isinstance(key, str): |
130 | | - # Seems like this function signature was wrong all along? |
131 | | - # If the user wants a field that we know of, get it and pass it off to _get_value |
132 | | - return getattr(self, key) |
133 | | - return key |
| 18 | + class Config: |
| 19 | + """Pydantic config.""" |
| 20 | + |
| 21 | + arbitrary_types_allowed = True |
| 22 | + use_enum_values = True |
| 23 | + extra = "allow" |
| 24 | + |
| 25 | + def __init__(self, *args, **kwargs): |
| 26 | + """Initialize the base class. |
| 27 | +
|
| 28 | + Args: |
| 29 | + *args: Positional arguments. |
| 30 | + **kwargs: Keyword arguments. |
| 31 | + """ |
| 32 | + from reflex.utils import console |
| 33 | + |
| 34 | + console.deprecate( |
| 35 | + "rx.Base", |
| 36 | + "The `rx.Base` class is deprecated. You can use `pydantic.BaseModel` directly instead or a dataclass if possible.", |
| 37 | + deprecation_version="0.8.0", |
| 38 | + removal_version="0.9.0", |
| 39 | + ) |
| 40 | + super().__init__(*args, **kwargs) |
| 41 | + |
| 42 | + def json(self) -> str: |
| 43 | + """Convert the object to a json string. |
| 44 | +
|
| 45 | + Returns: |
| 46 | + The object as a json string. |
| 47 | + """ |
| 48 | + from reflex.utils.serializers import serialize |
| 49 | + |
| 50 | + return self.__config__.json_dumps( |
| 51 | + self.dict(), |
| 52 | + default=serialize, |
| 53 | + ) |
| 54 | +else: |
| 55 | + |
| 56 | + class PydanticNotFoundFallback: |
| 57 | + """Fallback base class for environments without Pydantic.""" |
| 58 | + |
| 59 | + def __init__(self, *args, **kwargs): |
| 60 | + """Initialize the base class. |
| 61 | +
|
| 62 | + Args: |
| 63 | + *args: Positional arguments. |
| 64 | + **kwargs: Keyword arguments. |
| 65 | +
|
| 66 | + Raises: |
| 67 | + ImportError: As Pydantic is not installed. |
| 68 | + """ |
| 69 | + msg = "Pydantic is not installed. Please install it to use rx.Base." |
| 70 | + raise ImportError(msg) |
| 71 | + |
| 72 | + Base = PydanticNotFoundFallback # pyright: ignore[reportAssignmentType] |
0 commit comments