Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 53 additions & 85 deletions src/backend/base/langflow/serialization/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,15 @@

def _serialize_str(obj: str, max_length: int | None, _) -> str:
"""Truncate long strings with ellipsis if max_length provided."""
if max_length is None or len(obj) <= max_length:
return obj
return obj[:max_length] + "..."
return obj if max_length is None or len(obj) <= max_length else obj[:max_length] + "..."


def _serialize_bytes(obj: bytes, max_length: int | None, _) -> str:
"""Decode bytes to string and truncate if max_length provided."""
if max_length is not None:
return (
obj[:max_length].decode("utf-8", errors="ignore") + "..."
if len(obj) > max_length
else obj.decode("utf-8", errors="ignore")
)
return obj.decode("utf-8", errors="ignore")
decoded = obj.decode("utf-8", errors="ignore")
if max_length is None or len(decoded) <= max_length:
return decoded
return decoded[:max_length] + "..."


def _serialize_datetime(obj: datetime, *_) -> str:
Expand Down Expand Up @@ -70,8 +65,7 @@

def _serialize_pydantic(obj: BaseModel, max_length: int | None, max_items: int | None) -> Any:
"""Handle modern Pydantic models."""
serialized = obj.model_dump()
return {k: serialize(v, max_length, max_items) for k, v in serialized.items()}
return {k: serialize(v, max_length, max_items) for k, v in obj.model_dump().items()}


def _serialize_pydantic_v1(obj: BaseModelV1, max_length: int | None, max_items: int | None) -> Any:
Expand All @@ -89,15 +83,15 @@
def _serialize_list_tuple(obj: list | tuple, max_length: int | None, max_items: int | None) -> list:
"""Truncate long lists and process items recursively."""
if max_items is not None and len(obj) > max_items:
truncated = list(obj)[:max_items]
truncated.append(f"... [truncated {len(obj) - max_items} items]")
obj = truncated
return [serialize(item, max_length, max_items) for item in list(obj)[:max_items]] + [
f"... [truncated {len(obj) - max_items} items]"
]
return [serialize(item, max_length, max_items) for item in obj]


def _serialize_primitive(obj: Any, *_) -> Any:
"""Handle primitive types without conversion."""
if obj is None or isinstance(obj, int | float | bool | complex):
if obj is None or isinstance(obj, (int, float, bool, complex)):

Check failure on line 94 in src/backend/base/langflow/serialization/serialization.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (UP038)

src/backend/base/langflow/serialization/serialization.py:94:23: UP038 Use `X | Y` in `isinstance` call instead of `(X, Y)`
return obj
return UNSERIALIZABLE_SENTINEL

Expand All @@ -120,10 +114,7 @@
"""Serialize pandas DataFrame to a dictionary format."""
if max_items is not None and len(obj) > max_items:
obj = obj.head(max_items)

data = obj.to_dict(orient="records")

return serialize(data, max_length, max_items)
return serialize(obj.to_dict(orient="records"), max_length, max_items)


def _serialize_series(obj: pd.Series, max_length: int | None, max_items: int | None) -> dict:
Expand Down Expand Up @@ -157,66 +148,49 @@

def _serialize_dispatcher(obj: Any, max_length: int | None, max_items: int | None) -> Any | _UnserializableSentinel:
"""Dispatch object to appropriate serializer."""
# Handle primitive types first
if obj is None:
return obj
primitive = _serialize_primitive(obj, max_length, max_items)
if primitive is not UNSERIALIZABLE_SENTINEL:
return primitive

match obj:
case str():
return _serialize_str(obj, max_length, max_items)
case bytes():
return _serialize_bytes(obj, max_length, max_items)
case datetime():
return _serialize_datetime(obj, max_length, max_items)
case Decimal():
return _serialize_decimal(obj, max_length, max_items)
case UUID():
return _serialize_uuid(obj, max_length, max_items)
case Document():
return _serialize_document(obj, max_length, max_items)
case AsyncIterator() | Generator() | Iterator():
return _serialize_iterator(obj, max_length, max_items)
case BaseModel():
return _serialize_pydantic(obj, max_length, max_items)
case BaseModelV1():
return _serialize_pydantic_v1(obj, max_length, max_items)
case dict():
return _serialize_dict(obj, max_length, max_items)
case pd.DataFrame():
return _serialize_dataframe(obj, max_length, max_items)
case pd.Series():
return _serialize_series(obj, max_length, max_items)
case list() | tuple():
return _serialize_list_tuple(obj, max_length, max_items)
case object() if _is_numpy_type(obj):
return _serialize_numpy_type(obj, max_length, max_items)
case object() if not isinstance(obj, type): # Match any instance that's not a class
return _serialize_instance(obj, max_length, max_items)
case object() if hasattr(obj, "_name_"): # Enum case
return f"{obj.__class__.__name__}.{obj._name_}"
case object() if hasattr(obj, "__name__") and hasattr(obj, "__bound__"): # TypeVar case
return repr(obj)
case object() if hasattr(obj, "__origin__") or hasattr(obj, "__parameters__"): # Type alias/generic case
return repr(obj)
case _:
# Handle numpy numeric types (int, float, bool, complex)
if hasattr(obj, "dtype"):
if np.issubdtype(obj.dtype, np.number) and hasattr(obj, "item"):
return obj.item()
if np.issubdtype(obj.dtype, np.bool_):
return bool(obj)
if np.issubdtype(obj.dtype, np.complexfloating):
return complex(cast(complex, obj))
if np.issubdtype(obj.dtype, np.str_):
return str(obj)
if np.issubdtype(obj.dtype, np.bytes_) and hasattr(obj, "tobytes"):
return obj.tobytes().decode("utf-8", errors="ignore")
if np.issubdtype(obj.dtype, np.object_) and hasattr(obj, "item"):
return serialize(obj.item())
return UNSERIALIZABLE_SENTINEL
if isinstance(obj, str):
return _serialize_str(obj, max_length, max_items)
if isinstance(obj, bytes):
return _serialize_bytes(obj, max_length, max_items)
if isinstance(obj, datetime):
return _serialize_datetime(obj, max_length, max_items)
if isinstance(obj, Decimal):
return _serialize_decimal(obj, max_length, max_items)
if isinstance(obj, UUID):
return _serialize_uuid(obj, max_length, max_items)
if isinstance(obj, Document):
return _serialize_document(obj, max_length, max_items)
if isinstance(obj, (AsyncIterator, Generator, Iterator)):

Check failure on line 169 in src/backend/base/langflow/serialization/serialization.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (UP038)

src/backend/base/langflow/serialization/serialization.py:169:8: UP038 Use `X | Y` in `isinstance` call instead of `(X, Y)`
return _serialize_iterator(obj, max_length, max_items)
if isinstance(obj, BaseModel):
return _serialize_pydantic(obj, max_length, max_items)
if isinstance(obj, BaseModelV1):
return _serialize_pydantic_v1(obj, max_length, max_items)
if isinstance(obj, dict):
return _serialize_dict(obj, max_length, max_items)
if isinstance(obj, pd.DataFrame):
return _serialize_dataframe(obj, max_length, max_items)
if isinstance(obj, pd.Series):
return _serialize_series(obj, max_length, max_items)
if isinstance(obj, (list, tuple)):

Check failure on line 181 in src/backend/base/langflow/serialization/serialization.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (UP038)

src/backend/base/langflow/serialization/serialization.py:181:8: UP038 Use `X | Y` in `isinstance` call instead of `(X, Y)`
return _serialize_list_tuple(obj, max_length, max_items)
if _is_numpy_type(obj):
return _serialize_numpy_type(obj, max_length, max_items)
if not isinstance(obj, type): # Match any instance that's not a class
return _serialize_instance(obj, max_length, max_items)
if hasattr(obj, "_name_"): # Enum case
return f"{obj.__class__.__name__}.{obj._name_}"
if hasattr(obj, "__name__") and hasattr(obj, "__bound__"): # TypeVar case
return repr(obj)
if hasattr(obj, "__origin__") or hasattr(obj, "__parameters__"): # Type alias/generic case
return repr(obj)
return UNSERIALIZABLE_SENTINEL


def serialize(
Expand All @@ -240,25 +214,19 @@
if obj is None:
return None
try:
# First try type-specific serialization
result = _serialize_dispatcher(obj, max_length, max_items)
if result is not UNSERIALIZABLE_SENTINEL: # Special check for None since it's a valid result
if result is not UNSERIALIZABLE_SENTINEL:
return result

# Handle class-based Pydantic types and other types
if isinstance(obj, type):
if issubclass(obj, BaseModel | BaseModelV1):
if issubclass(obj, (BaseModel, BaseModelV1)):

Check failure on line 222 in src/backend/base/langflow/serialization/serialization.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.12)

Ruff (UP038)

src/backend/base/langflow/serialization/serialization.py:222:16: UP038 Use `X | Y` in `issubclass` call instead of `(X, Y)`
return repr(obj)
return str(obj) # Handle other class types
return str(obj)

# Handle type aliases and generic types
if hasattr(obj, "__origin__") or hasattr(obj, "__parameters__"): # Type alias or generic type check
try:
return repr(obj)
except Exception as e: # noqa: BLE001
logger.debug(f"Cannot serialize object {obj}: {e!s}")
if hasattr(obj, "__origin__") or hasattr(obj, "__parameters__"):
return repr(obj)

# Fallback to common serialization patterns
if hasattr(obj, "model_dump"):
return serialize(obj.model_dump(), max_length, max_items)
if hasattr(obj, "dict") and not isinstance(obj, type):
Expand Down
Loading