|
9 | 9 | import inspect |
10 | 10 | import json |
11 | 11 | from collections.abc import Callable, Sequence |
| 12 | +from importlib.util import find_spec |
12 | 13 | from types import MethodType |
13 | 14 | from typing import TYPE_CHECKING, Any, SupportsIndex, TypeVar |
14 | 15 |
|
15 | | -import pydantic |
16 | 16 | import wrapt |
17 | | -from pydantic import BaseModel as BaseModelV2 |
18 | | -from pydantic.v1 import BaseModel as BaseModelV1 |
19 | | -from sqlalchemy.orm import DeclarativeBase |
20 | 17 |
|
21 | 18 | from reflex.base import Base |
22 | 19 | from reflex.utils import prerequisites |
23 | 20 | from reflex.utils.exceptions import ImmutableStateError |
24 | | -from reflex.utils.serializers import serializer |
| 21 | +from reflex.utils.serializers import can_serialize, serialize, serializer |
25 | 22 | from reflex.vars.base import Var |
26 | 23 |
|
27 | 24 | if TYPE_CHECKING: |
@@ -339,6 +336,34 @@ def mark_dirty(self): |
339 | 336 | raise NotImplementedError(msg) |
340 | 337 |
|
341 | 338 |
|
| 339 | +if find_spec("pydantic"): |
| 340 | + import pydantic |
| 341 | + |
| 342 | + NEVER_WRAP_BASE_ATTRS = set(Base.__dict__) - {"set"} | set( |
| 343 | + pydantic.BaseModel.__dict__ |
| 344 | + ) |
| 345 | +else: |
| 346 | + NEVER_WRAP_BASE_ATTRS = {} |
| 347 | + |
| 348 | +MUTABLE_TYPES = ( |
| 349 | + list, |
| 350 | + dict, |
| 351 | + set, |
| 352 | + Base, |
| 353 | +) |
| 354 | + |
| 355 | +if find_spec("sqlalchemy"): |
| 356 | + from sqlalchemy.orm import DeclarativeBase |
| 357 | + |
| 358 | + MUTABLE_TYPES += (DeclarativeBase,) |
| 359 | + |
| 360 | +if find_spec("pydantic"): |
| 361 | + from pydantic import BaseModel as BaseModelV2 |
| 362 | + from pydantic.v1 import BaseModel as BaseModelV1 |
| 363 | + |
| 364 | + MUTABLE_TYPES += (BaseModelV1, BaseModelV2) |
| 365 | + |
| 366 | + |
342 | 367 | class MutableProxy(wrapt.ObjectProxy): |
343 | 368 | """A proxy for a mutable object that tracks changes.""" |
344 | 369 |
|
@@ -371,11 +396,6 @@ class MutableProxy(wrapt.ObjectProxy): |
371 | 396 | "setdefault", |
372 | 397 | } |
373 | 398 |
|
374 | | - # These internal attributes on rx.Base should NOT be wrapped in a MutableProxy. |
375 | | - __never_wrap_base_attrs__ = set(Base.__dict__) - {"set"} | set( |
376 | | - pydantic.BaseModel.__dict__ |
377 | | - ) |
378 | | - |
379 | 399 | # Dynamically generated classes for tracking dataclass mutations. |
380 | 400 | __dataclass_proxies__: dict[type, type] = {} |
381 | 401 |
|
@@ -539,7 +559,7 @@ def __getattr__(self, __name: str) -> Any: |
539 | 559 |
|
540 | 560 | if ( |
541 | 561 | isinstance(self.__wrapped__, Base) |
542 | | - and __name not in self.__never_wrap_base_attrs__ |
| 562 | + and __name not in NEVER_WRAP_BASE_ATTRS |
543 | 563 | and hasattr(value, "__func__") |
544 | 564 | ): |
545 | 565 | # Wrap methods called on Base subclasses, which might do _anything_ |
@@ -669,7 +689,10 @@ def serialize_mutable_proxy(mp: MutableProxy): |
669 | 689 | Returns: |
670 | 690 | The wrapped object. |
671 | 691 | """ |
672 | | - return mp.__wrapped__ |
| 692 | + obj = mp.__wrapped__ |
| 693 | + if can_serialize(type(obj)): |
| 694 | + return serialize(obj) |
| 695 | + return obj |
673 | 696 |
|
674 | 697 |
|
675 | 698 | _orig_json_encoder_default = json.JSONEncoder.default |
@@ -739,18 +762,6 @@ def _mark_dirty( |
739 | 762 | ) |
740 | 763 |
|
741 | 764 |
|
742 | | -# These types will be wrapped in MutableProxy |
743 | | -MUTABLE_TYPES = ( |
744 | | - list, |
745 | | - dict, |
746 | | - set, |
747 | | - Base, |
748 | | - DeclarativeBase, |
749 | | - BaseModelV2, |
750 | | - BaseModelV1, |
751 | | -) |
752 | | - |
753 | | - |
754 | 765 | @functools.lru_cache(maxsize=1024) |
755 | 766 | def is_mutable_type(type_: type) -> bool: |
756 | 767 | """Check if a type is mutable and should be wrapped. |
|
0 commit comments