Browse Source

serialize uuid and allow overwriting serializers (#4888)

* serialize uuid and allow overwriting serializers

* add frame info

* reword place info
Khaleel Al-Adhami 2 months ago
parent
commit
33714aa4a3
1 changed files with 38 additions and 4 deletions
  1. 38 4
      reflex/utils/serializers.py

+ 38 - 4
reflex/utils/serializers.py

@@ -5,6 +5,7 @@ from __future__ import annotations
 import contextlib
 import contextlib
 import dataclasses
 import dataclasses
 import functools
 import functools
+import inspect
 import json
 import json
 import warnings
 import warnings
 from datetime import date, datetime, time, timedelta
 from datetime import date, datetime, time, timedelta
@@ -21,13 +22,14 @@ from typing import (
     get_type_hints,
     get_type_hints,
     overload,
     overload,
 )
 )
+from uuid import UUID
 
 
 from pydantic import BaseModel as BaseModelV2
 from pydantic import BaseModel as BaseModelV2
 from pydantic.v1 import BaseModel as BaseModelV1
 from pydantic.v1 import BaseModel as BaseModelV1
 
 
 from reflex.base import Base
 from reflex.base import Base
 from reflex.constants.colors import Color, format_color
 from reflex.constants.colors import Color, format_color
-from reflex.utils import types
+from reflex.utils import console, types
 
 
 # Mapping from type to a serializer.
 # Mapping from type to a serializer.
 # The serializer should convert the type to a JSON object.
 # The serializer should convert the type to a JSON object.
@@ -47,6 +49,7 @@ SERIALIZED_FUNCTION = TypeVar("SERIALIZED_FUNCTION", bound=Serializer)
 def serializer(
 def serializer(
     fn: None = None,
     fn: None = None,
     to: Type[SerializedType] | None = None,
     to: Type[SerializedType] | None = None,
+    overwrite: bool | None = None,
 ) -> Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: ...
 ) -> Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: ...
 
 
 
 
@@ -54,18 +57,21 @@ def serializer(
 def serializer(
 def serializer(
     fn: SERIALIZED_FUNCTION,
     fn: SERIALIZED_FUNCTION,
     to: Type[SerializedType] | None = None,
     to: Type[SerializedType] | None = None,
+    overwrite: bool | None = None,
 ) -> SERIALIZED_FUNCTION: ...
 ) -> SERIALIZED_FUNCTION: ...
 
 
 
 
 def serializer(
 def serializer(
     fn: SERIALIZED_FUNCTION | None = None,
     fn: SERIALIZED_FUNCTION | None = None,
     to: Any = None,
     to: Any = None,
+    overwrite: bool | None = None,
 ) -> SERIALIZED_FUNCTION | Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]:
 ) -> SERIALIZED_FUNCTION | Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]:
     """Decorator to add a serializer for a given type.
     """Decorator to add a serializer for a given type.
 
 
     Args:
     Args:
         fn: The function to decorate.
         fn: The function to decorate.
         to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string.
         to: The type returned by the serializer. If this is `str`, then any Var created from this type will be treated as a string.
+        overwrite: Whether to overwrite the existing serializer.
 
 
     Returns:
     Returns:
         The decorated function.
         The decorated function.
@@ -85,9 +91,24 @@ def serializer(
 
 
         # Make sure the type is not already registered.
         # Make sure the type is not already registered.
         registered_fn = SERIALIZERS.get(type_)
         registered_fn = SERIALIZERS.get(type_)
-        if registered_fn is not None and registered_fn != fn:
-            raise ValueError(
-                f"Serializer for type {type_} is already registered as {registered_fn.__qualname__}."
+        if registered_fn is not None and registered_fn != fn and overwrite is not True:
+            message = f"Overwriting serializer for type {type_} from {registered_fn.__module__}:{registered_fn.__qualname__} to {fn.__module__}:{fn.__qualname__}."
+            if overwrite is False:
+                raise ValueError(message)
+            caller_frame = next(
+                filter(
+                    lambda frame: frame.filename != __file__,
+                    inspect.getouterframes(inspect.currentframe()),
+                ),
+                None,
+            )
+            file_info = (
+                f"(at {caller_frame.filename}:{caller_frame.lineno})"
+                if caller_frame
+                else ""
+            )
+            console.warn(
+                f"{message} Call rx.serializer with `overwrite=True` if this is intentional. {file_info}"
             )
             )
 
 
         to_type = to or type_hints.get("return")
         to_type = to or type_hints.get("return")
@@ -351,6 +372,19 @@ def serialize_enum(en: Enum) -> str:
     return en.value
     return en.value
 
 
 
 
+@serializer(to=str)
+def serialize_uuid(uuid: UUID) -> str:
+    """Serialize a UUID to a JSON string.
+
+    Args:
+        uuid: The UUID to serialize.
+
+    Returns:
+        The serialized UUID.
+    """
+    return str(uuid)
+
+
 @serializer(to=str)
 @serializer(to=str)
 def serialize_color(color: Color) -> str:
 def serialize_color(color: Color) -> str:
     """Serialize a color.
     """Serialize a color.