From 33714aa4a38a07605110681244486d8d9c8fa65f Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 28 Feb 2025 16:56:58 -0800 Subject: [PATCH] serialize uuid and allow overwriting serializers (#4888) * serialize uuid and allow overwriting serializers * add frame info * reword place info --- reflex/utils/serializers.py | 42 +++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/reflex/utils/serializers.py b/reflex/utils/serializers.py index 0e906532c30..0455e61df25 100644 --- a/reflex/utils/serializers.py +++ b/reflex/utils/serializers.py @@ -5,6 +5,7 @@ import contextlib import dataclasses import functools +import inspect import json import warnings from datetime import date, datetime, time, timedelta @@ -21,13 +22,14 @@ get_type_hints, overload, ) +from uuid import UUID from pydantic import BaseModel as BaseModelV2 from pydantic.v1 import BaseModel as BaseModelV1 from reflex.base import Base 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. # The serializer should convert the type to a JSON object. @@ -47,6 +49,7 @@ def serializer( fn: None = None, to: Type[SerializedType] | None = None, + overwrite: bool | None = None, ) -> Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: ... @@ -54,18 +57,21 @@ def serializer( def serializer( fn: SERIALIZED_FUNCTION, to: Type[SerializedType] | None = None, + overwrite: bool | None = None, ) -> SERIALIZED_FUNCTION: ... def serializer( fn: SERIALIZED_FUNCTION | None = None, to: Any = None, + overwrite: bool | None = None, ) -> SERIALIZED_FUNCTION | Callable[[SERIALIZED_FUNCTION], SERIALIZED_FUNCTION]: """Decorator to add a serializer for a given type. Args: 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. + overwrite: Whether to overwrite the existing serializer. Returns: The decorated function. @@ -85,9 +91,24 @@ def wrapper(fn: SERIALIZED_FUNCTION) -> SERIALIZED_FUNCTION: # Make sure the type is not already registered. 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") @@ -351,6 +372,19 @@ def serialize_enum(en: Enum) -> str: 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) def serialize_color(color: Color) -> str: """Serialize a color.