uipath-core 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {uipath_core-0.2.1 → uipath_core-0.2.2}/PKG-INFO +1 -1
- {uipath_core-0.2.1 → uipath_core-0.2.2}/pyproject.toml +1 -1
- uipath_core-0.2.2/src/uipath/core/serialization/__init__.py +5 -0
- uipath_core-0.2.2/src/uipath/core/serialization/json.py +150 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/_utils.py +5 -53
- uipath_core-0.2.2/tests/serialization/test_json.py +614 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/uv.lock +1 -1
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.cursorrules +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.editorconfig +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.gitattributes +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/cd.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/ci.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/commitlint.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/lint.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/publish-dev.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.github/workflows/test.yml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.gitignore +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.pre-commit-config.yaml +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.python-version +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.vscode/extensions.json +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.vscode/launch.json +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/.vscode/settings.json +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/CONTRIBUTING.md +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/LICENSE +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/README.md +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/justfile +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/async_stream.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/citation.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/content.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/conversation.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/error.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/event.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/exchange.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/interrupt.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/message.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/meta.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/chat/tool.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/errors/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/errors/errors.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/guardrails/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/guardrails/_deterministic_guardrails_service.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/guardrails/_evaluators.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/guardrails/guardrails.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/py.typed +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/decorators.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/exporters.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/processors.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/span_utils.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/trace_manager.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/src/uipath/core/tracing/types.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/__init__.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/conftest.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/guardrails/test_deterministic_guardrails_service.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_external_integration.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_serialization.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_span_filtering.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_span_nesting.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_span_registry.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_trace_manager.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_traced.py +0 -0
- {uipath_core-0.2.1 → uipath_core-0.2.2}/tests/tracing/test_tracing_utils.py +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""JSON serialization utilities for converting Python objects to JSON formats."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import asdict, is_dataclass
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, cast
|
|
8
|
+
from zoneinfo import ZoneInfo
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def serialize_defaults(
|
|
14
|
+
obj: Any,
|
|
15
|
+
) -> dict[str, Any] | list[Any] | str | int | float | bool | None:
|
|
16
|
+
"""Convert Python objects to JSON-serializable formats.
|
|
17
|
+
|
|
18
|
+
Handles common Python types that are not natively JSON-serializable:
|
|
19
|
+
- Pydantic models (v1 and v2)
|
|
20
|
+
- Dataclasses
|
|
21
|
+
- Enums
|
|
22
|
+
- Datetime objects
|
|
23
|
+
- Timezone objects
|
|
24
|
+
- Named tuples
|
|
25
|
+
- Sets and tuples
|
|
26
|
+
|
|
27
|
+
This function is designed to be used as the `default` parameter in json.dumps():
|
|
28
|
+
```python
|
|
29
|
+
import json
|
|
30
|
+
result = json.dumps(obj, default=serialize_defaults)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or use the convenience function `serialize_json()` which wraps this:
|
|
34
|
+
```python
|
|
35
|
+
result = serialize_json(obj)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
obj: The object to serialize
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A JSON-serializable representation of the object:
|
|
43
|
+
- Pydantic models: dict from model_dump()
|
|
44
|
+
- Dataclasses: dict from asdict()
|
|
45
|
+
- Enums: the enum value (recursively serialized)
|
|
46
|
+
- datetime: ISO format string
|
|
47
|
+
- timezone/ZoneInfo: timezone name
|
|
48
|
+
- sets/tuples: converted to lists
|
|
49
|
+
- named tuples: converted to dict
|
|
50
|
+
- Primitives (None, bool, int, float, str, list, dict): returned unchanged
|
|
51
|
+
- Other types: converted to string with str()
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
>>> from datetime import datetime
|
|
55
|
+
>>> from pydantic import BaseModel
|
|
56
|
+
>>>
|
|
57
|
+
>>> class User(BaseModel):
|
|
58
|
+
... name: str
|
|
59
|
+
... created_at: datetime
|
|
60
|
+
>>>
|
|
61
|
+
>>> user = User(name="Alice", created_at=datetime.now())
|
|
62
|
+
>>> import json
|
|
63
|
+
>>> json.dumps(user, default=serialize_defaults)
|
|
64
|
+
'{"name": "Alice", "created_at": "2024-01-01T12:00:00"}'
|
|
65
|
+
>>> # Or use the convenience function
|
|
66
|
+
>>> serialize_json(user)
|
|
67
|
+
'{"name": "Alice", "created_at": "2024-01-01T12:00:00"}'
|
|
68
|
+
"""
|
|
69
|
+
# Handle Pydantic BaseModel instances
|
|
70
|
+
if hasattr(obj, "model_dump") and not isinstance(obj, type):
|
|
71
|
+
return obj.model_dump(exclude_none=True, mode="json")
|
|
72
|
+
|
|
73
|
+
# Handle Pydantic model classes - convert to schema representation
|
|
74
|
+
if isinstance(obj, type) and issubclass(obj, BaseModel):
|
|
75
|
+
return {
|
|
76
|
+
"__class__": obj.__name__,
|
|
77
|
+
"__module__": obj.__module__,
|
|
78
|
+
"schema": obj.model_json_schema(),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Handle Pydantic v1 models
|
|
82
|
+
if hasattr(obj, "dict") and not isinstance(obj, type):
|
|
83
|
+
return obj.dict()
|
|
84
|
+
|
|
85
|
+
# Handle objects with to_dict method
|
|
86
|
+
if hasattr(obj, "to_dict") and not isinstance(obj, type):
|
|
87
|
+
return obj.to_dict()
|
|
88
|
+
|
|
89
|
+
# Handle dataclasses
|
|
90
|
+
if is_dataclass(obj) and not isinstance(obj, type):
|
|
91
|
+
return asdict(obj)
|
|
92
|
+
|
|
93
|
+
# Handle enums - recursively serialize the value
|
|
94
|
+
if isinstance(obj, Enum):
|
|
95
|
+
return serialize_defaults(obj.value)
|
|
96
|
+
|
|
97
|
+
# Handle sets and tuples
|
|
98
|
+
if isinstance(obj, (set, tuple)):
|
|
99
|
+
# Check if it's a named tuple (has _asdict method)
|
|
100
|
+
if hasattr(obj, "_asdict") and callable(
|
|
101
|
+
obj._asdict # pyright: ignore[reportAttributeAccessIssue]
|
|
102
|
+
):
|
|
103
|
+
return cast(
|
|
104
|
+
dict[str, Any],
|
|
105
|
+
obj._asdict(), # pyright: ignore[reportAttributeAccessIssue]
|
|
106
|
+
)
|
|
107
|
+
# Convert to list
|
|
108
|
+
return list(obj)
|
|
109
|
+
|
|
110
|
+
# Handle datetime objects
|
|
111
|
+
if isinstance(obj, datetime):
|
|
112
|
+
return obj.isoformat()
|
|
113
|
+
|
|
114
|
+
# Handle timezone objects
|
|
115
|
+
if isinstance(obj, (timezone, ZoneInfo)):
|
|
116
|
+
return obj.tzname(None)
|
|
117
|
+
|
|
118
|
+
# Allow JSON-serializable primitives to pass through unchanged
|
|
119
|
+
if obj is None or isinstance(obj, (bool, int, float, str, list, dict)):
|
|
120
|
+
return obj
|
|
121
|
+
|
|
122
|
+
# Fallback: convert to string
|
|
123
|
+
return str(obj)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def serialize_json(obj: Any) -> str:
|
|
127
|
+
"""Serialize Python object to JSON string.
|
|
128
|
+
|
|
129
|
+
This is a convenience function that wraps json.dumps() with serialize_defaults()
|
|
130
|
+
as the default handler for non-JSON-serializable types.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
obj: The object to serialize to JSON
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
JSON string representation of the object
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
>>> from datetime import datetime
|
|
140
|
+
>>> from pydantic import BaseModel
|
|
141
|
+
>>>
|
|
142
|
+
>>> class Task(BaseModel):
|
|
143
|
+
... name: str
|
|
144
|
+
... created: datetime
|
|
145
|
+
>>>
|
|
146
|
+
>>> task = Task(name="Review PR", created=datetime(2024, 1, 15, 10, 30))
|
|
147
|
+
>>> serialize_json(task)
|
|
148
|
+
'{"name": "Review PR", "created": "2024-01-15T10:30:00"}'
|
|
149
|
+
"""
|
|
150
|
+
return json.dumps(obj, default=serialize_defaults)
|
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
-
from
|
|
7
|
-
from datetime import datetime, timezone
|
|
8
|
-
from enum import Enum
|
|
9
|
-
from typing import Any, Mapping, Optional, cast
|
|
10
|
-
from zoneinfo import ZoneInfo
|
|
6
|
+
from typing import Any, Mapping, Optional
|
|
11
7
|
|
|
12
8
|
from opentelemetry.trace import Span
|
|
13
|
-
|
|
9
|
+
|
|
10
|
+
from uipath.core.serialization import serialize_json
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
def get_supported_params(
|
|
@@ -31,64 +28,19 @@ def get_supported_params(
|
|
|
31
28
|
return supported
|
|
32
29
|
|
|
33
30
|
|
|
34
|
-
def _simple_serialize_defaults(
|
|
35
|
-
obj: Any,
|
|
36
|
-
) -> dict[str, Any] | list[Any] | str | int | float | bool | None:
|
|
37
|
-
# Handle Pydantic BaseModel instances
|
|
38
|
-
if hasattr(obj, "model_dump") and not isinstance(obj, type):
|
|
39
|
-
return obj.model_dump(exclude_none=True, mode="json")
|
|
40
|
-
|
|
41
|
-
# Handle classes - convert to schema representation
|
|
42
|
-
if isinstance(obj, type) and issubclass(obj, BaseModel):
|
|
43
|
-
return {
|
|
44
|
-
"__class__": obj.__name__,
|
|
45
|
-
"__module__": obj.__module__,
|
|
46
|
-
"schema": obj.model_json_schema(),
|
|
47
|
-
}
|
|
48
|
-
if hasattr(obj, "dict") and not isinstance(obj, type):
|
|
49
|
-
return obj.dict()
|
|
50
|
-
if hasattr(obj, "to_dict") and not isinstance(obj, type):
|
|
51
|
-
return obj.to_dict()
|
|
52
|
-
|
|
53
|
-
# Handle dataclasses
|
|
54
|
-
if is_dataclass(obj) and not isinstance(obj, type):
|
|
55
|
-
return asdict(obj)
|
|
56
|
-
|
|
57
|
-
# Handle enums
|
|
58
|
-
if isinstance(obj, Enum):
|
|
59
|
-
return _simple_serialize_defaults(obj.value)
|
|
60
|
-
|
|
61
|
-
if isinstance(obj, (set, tuple)):
|
|
62
|
-
if hasattr(obj, "_asdict") and callable(obj._asdict): # pyright: ignore[reportAttributeAccessIssue]
|
|
63
|
-
return cast(dict[str, Any], obj._asdict()) # pyright: ignore[reportAttributeAccessIssue]
|
|
64
|
-
return list(obj)
|
|
65
|
-
|
|
66
|
-
if isinstance(obj, datetime):
|
|
67
|
-
return obj.isoformat()
|
|
68
|
-
|
|
69
|
-
if isinstance(obj, (timezone, ZoneInfo)):
|
|
70
|
-
return obj.tzname(None)
|
|
71
|
-
|
|
72
|
-
# Allow JSON-serializable primitives to pass through unchanged
|
|
73
|
-
if obj is None or isinstance(obj, (bool, int, float, str)):
|
|
74
|
-
return obj
|
|
75
|
-
|
|
76
|
-
return str(obj)
|
|
77
|
-
|
|
78
|
-
|
|
79
31
|
def format_args_for_trace_json(
|
|
80
32
|
signature: inspect.Signature, *args: Any, **kwargs: Any
|
|
81
33
|
) -> str:
|
|
82
34
|
"""Return a JSON string of inputs from the function signature."""
|
|
83
35
|
result = format_args_for_trace(signature, *args, **kwargs)
|
|
84
|
-
return
|
|
36
|
+
return serialize_json(result)
|
|
85
37
|
|
|
86
38
|
|
|
87
39
|
def format_object_for_trace_json(
|
|
88
40
|
input_object: Any,
|
|
89
41
|
) -> str:
|
|
90
42
|
"""Return a JSON string of inputs from the function signature."""
|
|
91
|
-
return
|
|
43
|
+
return serialize_json(input_object)
|
|
92
44
|
|
|
93
45
|
|
|
94
46
|
def format_args_for_trace(
|
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"""Tests for serialization utilities."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any
|
|
9
|
+
from zoneinfo import ZoneInfo
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from uipath.core.serialization import serialize_json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _has_tzdata() -> bool:
|
|
18
|
+
"""Check if timezone data is available."""
|
|
19
|
+
try:
|
|
20
|
+
ZoneInfo("America/New_York")
|
|
21
|
+
return True
|
|
22
|
+
except Exception:
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Color(Enum):
|
|
27
|
+
"""Test enum."""
|
|
28
|
+
|
|
29
|
+
RED = "red"
|
|
30
|
+
GREEN = "green"
|
|
31
|
+
BLUE = 3
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Priority(Enum):
|
|
35
|
+
"""Test enum with int values."""
|
|
36
|
+
|
|
37
|
+
LOW = 1
|
|
38
|
+
MEDIUM = 2
|
|
39
|
+
HIGH = 3
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SimpleModel(BaseModel):
|
|
43
|
+
"""Simple Pydantic v2 model."""
|
|
44
|
+
|
|
45
|
+
name: str
|
|
46
|
+
value: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NestedModel(BaseModel):
|
|
50
|
+
"""Pydantic model with nested model."""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
inner: SimpleModel
|
|
54
|
+
items: list[SimpleModel]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class SimpleDataclass:
|
|
59
|
+
"""Simple dataclass for testing."""
|
|
60
|
+
|
|
61
|
+
name: str
|
|
62
|
+
count: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class NestedDataclass:
|
|
67
|
+
"""Dataclass with nested dataclass."""
|
|
68
|
+
|
|
69
|
+
id: str
|
|
70
|
+
inner: SimpleDataclass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Point = namedtuple("Point", ["x", "y"])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestSimpleSerializeDefaults:
|
|
77
|
+
"""Tests for serialize_defaults and serialize_json functions."""
|
|
78
|
+
|
|
79
|
+
def test_serializes_none(self) -> None:
|
|
80
|
+
"""Test None serialization via json.dumps."""
|
|
81
|
+
data = {"value": None}
|
|
82
|
+
result = serialize_json(data)
|
|
83
|
+
parsed = json.loads(result)
|
|
84
|
+
assert parsed["value"] is None
|
|
85
|
+
|
|
86
|
+
def test_serializes_primitives(self) -> None:
|
|
87
|
+
"""Test primitive types pass through json.dumps unchanged."""
|
|
88
|
+
data = {
|
|
89
|
+
"bool_true": True,
|
|
90
|
+
"bool_false": False,
|
|
91
|
+
"integer": 42,
|
|
92
|
+
"float": 3.14,
|
|
93
|
+
"string": "hello",
|
|
94
|
+
}
|
|
95
|
+
result = serialize_json(data)
|
|
96
|
+
parsed = json.loads(result)
|
|
97
|
+
assert parsed["bool_true"] is True
|
|
98
|
+
assert parsed["bool_false"] is False
|
|
99
|
+
assert parsed["integer"] == 42
|
|
100
|
+
assert parsed["float"] == 3.14
|
|
101
|
+
assert parsed["string"] == "hello"
|
|
102
|
+
|
|
103
|
+
def test_serializes_pydantic_model(self) -> None:
|
|
104
|
+
"""Test Pydantic BaseModel serialization via json.dumps."""
|
|
105
|
+
model = SimpleModel(name="test", value=42)
|
|
106
|
+
result = serialize_json(model)
|
|
107
|
+
parsed = json.loads(result)
|
|
108
|
+
assert isinstance(parsed, dict)
|
|
109
|
+
assert parsed["name"] == "test"
|
|
110
|
+
assert parsed["value"] == 42
|
|
111
|
+
|
|
112
|
+
def test_serializes_nested_pydantic_model(self) -> None:
|
|
113
|
+
"""Test nested Pydantic models via json.dumps."""
|
|
114
|
+
inner = SimpleModel(name="inner", value=10)
|
|
115
|
+
model = NestedModel(
|
|
116
|
+
id="123",
|
|
117
|
+
inner=inner,
|
|
118
|
+
items=[
|
|
119
|
+
SimpleModel(name="item1", value=1),
|
|
120
|
+
SimpleModel(name="item2", value=2),
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
result = serialize_json(model)
|
|
124
|
+
parsed = json.loads(result)
|
|
125
|
+
assert isinstance(parsed, dict)
|
|
126
|
+
assert parsed["id"] == "123"
|
|
127
|
+
assert parsed["inner"]["name"] == "inner"
|
|
128
|
+
assert len(parsed["items"]) == 2
|
|
129
|
+
assert parsed["items"][0]["name"] == "item1"
|
|
130
|
+
assert parsed["items"][1]["value"] == 2
|
|
131
|
+
|
|
132
|
+
def test_serializes_pydantic_model_excludes_none(self) -> None:
|
|
133
|
+
"""Test Pydantic model with None values excluded via json.dumps."""
|
|
134
|
+
|
|
135
|
+
class OptionalModel(BaseModel):
|
|
136
|
+
required: str
|
|
137
|
+
optional: str | None = None
|
|
138
|
+
|
|
139
|
+
model = OptionalModel(required="value")
|
|
140
|
+
result = serialize_json(model)
|
|
141
|
+
parsed = json.loads(result)
|
|
142
|
+
assert isinstance(parsed, dict)
|
|
143
|
+
assert parsed["required"] == "value"
|
|
144
|
+
# exclude_none=True should exclude the None field
|
|
145
|
+
assert "optional" not in parsed
|
|
146
|
+
|
|
147
|
+
def test_serializes_pydantic_model_class(self) -> None:
|
|
148
|
+
"""Test Pydantic model class (not instance) serialization via json.dumps."""
|
|
149
|
+
data = {"model_class": SimpleModel}
|
|
150
|
+
result = serialize_json(data)
|
|
151
|
+
parsed = json.loads(result)
|
|
152
|
+
assert isinstance(parsed["model_class"], dict)
|
|
153
|
+
assert parsed["model_class"]["__class__"] == "SimpleModel"
|
|
154
|
+
assert parsed["model_class"]["__module__"] == "test_json"
|
|
155
|
+
assert "schema" in parsed["model_class"]
|
|
156
|
+
assert isinstance(parsed["model_class"]["schema"], dict)
|
|
157
|
+
|
|
158
|
+
def test_serializes_dataclass(self) -> None:
|
|
159
|
+
"""Test dataclass serialization via json.dumps."""
|
|
160
|
+
obj = SimpleDataclass(name="test", count=5)
|
|
161
|
+
result = serialize_json(obj)
|
|
162
|
+
parsed = json.loads(result)
|
|
163
|
+
assert isinstance(parsed, dict)
|
|
164
|
+
assert parsed["name"] == "test"
|
|
165
|
+
assert parsed["count"] == 5
|
|
166
|
+
|
|
167
|
+
def test_serializes_nested_dataclass(self) -> None:
|
|
168
|
+
"""Test nested dataclass serialization via json.dumps."""
|
|
169
|
+
inner = SimpleDataclass(name="inner", count=10)
|
|
170
|
+
obj = NestedDataclass(id="123", inner=inner)
|
|
171
|
+
result = serialize_json(obj)
|
|
172
|
+
parsed = json.loads(result)
|
|
173
|
+
assert isinstance(parsed, dict)
|
|
174
|
+
assert parsed["id"] == "123"
|
|
175
|
+
assert parsed["inner"]["name"] == "inner"
|
|
176
|
+
assert parsed["inner"]["count"] == 10
|
|
177
|
+
|
|
178
|
+
def test_serializes_enum_string_value(self) -> None:
|
|
179
|
+
"""Test enum with string value via json.dumps."""
|
|
180
|
+
data = {"color": Color.RED}
|
|
181
|
+
result = serialize_json(data)
|
|
182
|
+
parsed = json.loads(result)
|
|
183
|
+
assert parsed["color"] == "red"
|
|
184
|
+
|
|
185
|
+
def test_serializes_enum_int_value(self) -> None:
|
|
186
|
+
"""Test enum with int value via json.dumps."""
|
|
187
|
+
data = {"priority": Priority.HIGH}
|
|
188
|
+
result = serialize_json(data)
|
|
189
|
+
parsed = json.loads(result)
|
|
190
|
+
assert parsed["priority"] == 3
|
|
191
|
+
|
|
192
|
+
def test_serializes_enum_mixed_value(self) -> None:
|
|
193
|
+
"""Test enum with mixed types via json.dumps."""
|
|
194
|
+
data = {"color1": Color.GREEN, "color2": Color.BLUE}
|
|
195
|
+
result = serialize_json(data)
|
|
196
|
+
parsed = json.loads(result)
|
|
197
|
+
assert parsed["color1"] == "green"
|
|
198
|
+
assert parsed["color2"] == 3
|
|
199
|
+
|
|
200
|
+
def test_serializes_datetime(self) -> None:
|
|
201
|
+
"""Test datetime serialization via json.dumps."""
|
|
202
|
+
dt = datetime(2024, 1, 15, 10, 30, 45)
|
|
203
|
+
data = {"timestamp": dt}
|
|
204
|
+
result = serialize_json(data)
|
|
205
|
+
parsed = json.loads(result)
|
|
206
|
+
assert isinstance(parsed["timestamp"], str)
|
|
207
|
+
assert "2024-01-15" in parsed["timestamp"]
|
|
208
|
+
assert "10:30:45" in parsed["timestamp"]
|
|
209
|
+
|
|
210
|
+
def test_serializes_datetime_with_timezone(self) -> None:
|
|
211
|
+
"""Test datetime with timezone via json.dumps."""
|
|
212
|
+
dt = datetime(2024, 1, 15, 10, 30, 45, tzinfo=timezone.utc)
|
|
213
|
+
data = {"timestamp": dt}
|
|
214
|
+
result = serialize_json(data)
|
|
215
|
+
parsed = json.loads(result)
|
|
216
|
+
assert isinstance(parsed["timestamp"], str)
|
|
217
|
+
assert parsed["timestamp"] == "2024-01-15T10:30:45+00:00"
|
|
218
|
+
|
|
219
|
+
def test_serializes_timezone(self) -> None:
|
|
220
|
+
"""Test timezone object serialization via json.dumps."""
|
|
221
|
+
tz = timezone.utc
|
|
222
|
+
data = {"timezone": tz}
|
|
223
|
+
result = serialize_json(data)
|
|
224
|
+
parsed = json.loads(result)
|
|
225
|
+
assert parsed["timezone"] == "UTC"
|
|
226
|
+
|
|
227
|
+
@pytest.mark.skipif(not _has_tzdata(), reason="Timezone data not available")
|
|
228
|
+
def test_serializes_zoneinfo(self) -> None:
|
|
229
|
+
"""Test ZoneInfo serialization via json.dumps."""
|
|
230
|
+
tz = ZoneInfo("America/New_York")
|
|
231
|
+
data = {"timezone": tz}
|
|
232
|
+
result = serialize_json(data)
|
|
233
|
+
parsed = json.loads(result)
|
|
234
|
+
# ZoneInfo returns timezone name or None
|
|
235
|
+
assert isinstance(parsed["timezone"], (str, type(None)))
|
|
236
|
+
|
|
237
|
+
def test_serializes_set(self) -> None:
|
|
238
|
+
"""Test set serialization via json.dumps."""
|
|
239
|
+
obj = {1, 2, 3}
|
|
240
|
+
data = {"numbers": obj}
|
|
241
|
+
result = serialize_json(data)
|
|
242
|
+
parsed = json.loads(result)
|
|
243
|
+
assert isinstance(parsed["numbers"], list)
|
|
244
|
+
assert set(parsed["numbers"]) == {1, 2, 3}
|
|
245
|
+
|
|
246
|
+
def test_serializes_tuple(self) -> None:
|
|
247
|
+
"""Test tuple serialization via json.dumps."""
|
|
248
|
+
obj = (1, 2, 3)
|
|
249
|
+
data = {"numbers": obj}
|
|
250
|
+
result = serialize_json(data)
|
|
251
|
+
parsed = json.loads(result)
|
|
252
|
+
assert isinstance(parsed["numbers"], list)
|
|
253
|
+
assert parsed["numbers"] == [1, 2, 3]
|
|
254
|
+
|
|
255
|
+
def test_serializes_named_tuple(self) -> None:
|
|
256
|
+
"""Test named tuple serialization via json.dumps.
|
|
257
|
+
|
|
258
|
+
Note: Python's json encoder treats namedtuples as regular tuples,
|
|
259
|
+
so they serialize as lists [x, y] rather than dicts {"x": x, "y": y}.
|
|
260
|
+
The serialize_defaults function is not called for namedtuples
|
|
261
|
+
because they're natively JSON-serializable as tuples.
|
|
262
|
+
"""
|
|
263
|
+
point = Point(x=10, y=20)
|
|
264
|
+
data = {"point": point}
|
|
265
|
+
result = serialize_json(data)
|
|
266
|
+
parsed = json.loads(result)
|
|
267
|
+
# Namedtuples serialize as lists through json.dumps
|
|
268
|
+
assert isinstance(parsed["point"], list)
|
|
269
|
+
assert parsed["point"] == [10, 20]
|
|
270
|
+
|
|
271
|
+
def test_serializes_object_with_to_dict(self) -> None:
|
|
272
|
+
"""Test object with to_dict method via json.dumps."""
|
|
273
|
+
|
|
274
|
+
class CustomObject:
|
|
275
|
+
def to_dict(self) -> dict[str, Any]:
|
|
276
|
+
return {"custom": "value"}
|
|
277
|
+
|
|
278
|
+
obj = CustomObject()
|
|
279
|
+
data = {"obj": obj}
|
|
280
|
+
result = serialize_json(data)
|
|
281
|
+
parsed = json.loads(result)
|
|
282
|
+
assert parsed["obj"] == {"custom": "value"}
|
|
283
|
+
|
|
284
|
+
def test_serializes_unknown_object_to_str(self) -> None:
|
|
285
|
+
"""Test unknown object falls back to str() via json.dumps."""
|
|
286
|
+
|
|
287
|
+
class CustomClass:
|
|
288
|
+
def __str__(self) -> str:
|
|
289
|
+
return "custom_string"
|
|
290
|
+
|
|
291
|
+
obj = CustomClass()
|
|
292
|
+
data = {"obj": obj}
|
|
293
|
+
result = serialize_json(data)
|
|
294
|
+
parsed = json.loads(result)
|
|
295
|
+
assert parsed["obj"] == "custom_string"
|
|
296
|
+
|
|
297
|
+
def test_with_json_dumps(self) -> None:
|
|
298
|
+
"""Test integration with json.dumps()."""
|
|
299
|
+
|
|
300
|
+
class ComplexObject(BaseModel):
|
|
301
|
+
name: str
|
|
302
|
+
created_at: datetime
|
|
303
|
+
priority: Priority
|
|
304
|
+
|
|
305
|
+
obj = ComplexObject(
|
|
306
|
+
name="task",
|
|
307
|
+
created_at=datetime(2024, 1, 15, 10, 30),
|
|
308
|
+
priority=Priority.HIGH,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Should not raise TypeError
|
|
312
|
+
result = serialize_json(obj)
|
|
313
|
+
parsed = json.loads(result)
|
|
314
|
+
|
|
315
|
+
assert parsed["name"] == "task"
|
|
316
|
+
assert "2024-01-15" in parsed["created_at"]
|
|
317
|
+
assert parsed["priority"] == 3
|
|
318
|
+
|
|
319
|
+
def test_with_json_dumps_complex_nested(self) -> None:
|
|
320
|
+
"""Test with complex nested structure."""
|
|
321
|
+
data = {
|
|
322
|
+
"model": SimpleModel(name="test", value=42),
|
|
323
|
+
"dataclass": SimpleDataclass(name="dc", count=5),
|
|
324
|
+
"enum": Color.RED,
|
|
325
|
+
"datetime": datetime(2024, 1, 1),
|
|
326
|
+
"set": {1, 2, 3},
|
|
327
|
+
"tuple": (4, 5, 6),
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
result = serialize_json(data)
|
|
331
|
+
parsed = json.loads(result)
|
|
332
|
+
|
|
333
|
+
assert parsed["model"]["name"] == "test"
|
|
334
|
+
assert parsed["dataclass"]["name"] == "dc"
|
|
335
|
+
assert parsed["enum"] == "red"
|
|
336
|
+
assert "2024-01-01" in parsed["datetime"]
|
|
337
|
+
assert set(parsed["set"]) == {1, 2, 3}
|
|
338
|
+
assert parsed["tuple"] == [4, 5, 6]
|
|
339
|
+
|
|
340
|
+
def test_with_list_of_pydantic_models(self) -> None:
|
|
341
|
+
"""Test with list of Pydantic models (common MCP scenario)."""
|
|
342
|
+
models = [
|
|
343
|
+
SimpleModel(name="first", value=1),
|
|
344
|
+
SimpleModel(name="second", value=2),
|
|
345
|
+
SimpleModel(name="third", value=3),
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
# This should not raise TypeError
|
|
349
|
+
result = serialize_json(models)
|
|
350
|
+
parsed = json.loads(result)
|
|
351
|
+
|
|
352
|
+
assert len(parsed) == 3
|
|
353
|
+
assert parsed[0]["name"] == "first"
|
|
354
|
+
assert parsed[1]["value"] == 2
|
|
355
|
+
|
|
356
|
+
def test_recursive_enum_serialization(self) -> None:
|
|
357
|
+
"""Test that enum values are recursively serialized via json.dumps."""
|
|
358
|
+
|
|
359
|
+
class NestedEnum(Enum):
|
|
360
|
+
"""Enum with complex value."""
|
|
361
|
+
|
|
362
|
+
COMPLEX = {"key": "value"}
|
|
363
|
+
|
|
364
|
+
data = {"enum": NestedEnum.COMPLEX}
|
|
365
|
+
result = serialize_json(data)
|
|
366
|
+
parsed = json.loads(result)
|
|
367
|
+
# The enum value itself (a dict) should be returned as-is
|
|
368
|
+
assert parsed["enum"] == {"key": "value"}
|
|
369
|
+
assert isinstance(parsed["enum"], dict)
|
|
370
|
+
|
|
371
|
+
def test_dataclass_class_returns_string(self) -> None:
|
|
372
|
+
"""Test that dataclass class (not instance) falls back to str via json.dumps."""
|
|
373
|
+
data = {"dataclass_class": SimpleDataclass}
|
|
374
|
+
result = serialize_json(data)
|
|
375
|
+
parsed = json.loads(result)
|
|
376
|
+
assert isinstance(parsed["dataclass_class"], str)
|
|
377
|
+
assert "SimpleDataclass" in parsed["dataclass_class"]
|
|
378
|
+
|
|
379
|
+
def test_empty_collections(self) -> None:
|
|
380
|
+
"""Test empty collections via json.dumps."""
|
|
381
|
+
data: dict[str, Any] = {"empty_set": set(), "empty_tuple": (), "empty_list": []}
|
|
382
|
+
result = serialize_json(data)
|
|
383
|
+
parsed = json.loads(result)
|
|
384
|
+
assert parsed["empty_set"] == []
|
|
385
|
+
assert parsed["empty_tuple"] == []
|
|
386
|
+
assert parsed["empty_list"] == []
|
|
387
|
+
|
|
388
|
+
def test_with_dict_method(self) -> None:
|
|
389
|
+
"""Test object with dict() method (Pydantic v1 compatibility) via json.dumps."""
|
|
390
|
+
|
|
391
|
+
class OldStyleModel:
|
|
392
|
+
def dict(self) -> dict[str, Any]:
|
|
393
|
+
return {"old": "style"}
|
|
394
|
+
|
|
395
|
+
obj = OldStyleModel()
|
|
396
|
+
data = {"obj": obj}
|
|
397
|
+
result = serialize_json(data)
|
|
398
|
+
parsed = json.loads(result)
|
|
399
|
+
assert parsed["obj"] == {"old": "style"}
|
|
400
|
+
|
|
401
|
+
def test_dict_of_pydantic_models(self) -> None:
|
|
402
|
+
"""Test dictionary containing Pydantic models as values."""
|
|
403
|
+
data = {
|
|
404
|
+
"user1": SimpleModel(name="Alice", value=100),
|
|
405
|
+
"user2": SimpleModel(name="Bob", value=200),
|
|
406
|
+
"user3": SimpleModel(name="Charlie", value=300),
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
result = serialize_json(data)
|
|
410
|
+
parsed = json.loads(result)
|
|
411
|
+
|
|
412
|
+
assert isinstance(parsed, dict)
|
|
413
|
+
assert parsed["user1"]["name"] == "Alice"
|
|
414
|
+
assert parsed["user1"]["value"] == 100
|
|
415
|
+
assert parsed["user2"]["name"] == "Bob"
|
|
416
|
+
assert parsed["user2"]["value"] == 200
|
|
417
|
+
assert parsed["user3"]["name"] == "Charlie"
|
|
418
|
+
assert parsed["user3"]["value"] == 300
|
|
419
|
+
|
|
420
|
+
def test_dict_of_dataclass_models(self) -> None:
|
|
421
|
+
"""Test dictionary containing dataclass instances as values."""
|
|
422
|
+
data = {
|
|
423
|
+
"item1": SimpleDataclass(name="First", count=1),
|
|
424
|
+
"item2": SimpleDataclass(name="Second", count=2),
|
|
425
|
+
"item3": SimpleDataclass(name="Third", count=3),
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
result = serialize_json(data)
|
|
429
|
+
parsed = json.loads(result)
|
|
430
|
+
|
|
431
|
+
assert isinstance(parsed, dict)
|
|
432
|
+
assert parsed["item1"]["name"] == "First"
|
|
433
|
+
assert parsed["item1"]["count"] == 1
|
|
434
|
+
assert parsed["item2"]["name"] == "Second"
|
|
435
|
+
assert parsed["item2"]["count"] == 2
|
|
436
|
+
assert parsed["item3"]["name"] == "Third"
|
|
437
|
+
assert parsed["item3"]["count"] == 3
|
|
438
|
+
|
|
439
|
+
def test_normal_class_fallback_to_str(self) -> None:
|
|
440
|
+
"""Test normal class (not Pydantic, dataclass, or enum) falls back to str()."""
|
|
441
|
+
|
|
442
|
+
class RegularClass:
|
|
443
|
+
def __init__(self, value: str) -> None:
|
|
444
|
+
self.value = value
|
|
445
|
+
|
|
446
|
+
def __str__(self) -> str:
|
|
447
|
+
return f"RegularClass({self.value})"
|
|
448
|
+
|
|
449
|
+
obj = RegularClass("test_value")
|
|
450
|
+
data = {"object": obj, "name": "test"}
|
|
451
|
+
json_result = serialize_json(data)
|
|
452
|
+
parsed = json.loads(json_result)
|
|
453
|
+
assert parsed["object"] == "RegularClass(test_value)"
|
|
454
|
+
assert parsed["name"] == "test"
|
|
455
|
+
|
|
456
|
+
def test_list_of_dataclass(self) -> None:
|
|
457
|
+
"""Test list containing dataclass instances."""
|
|
458
|
+
data = [
|
|
459
|
+
SimpleDataclass(name="First", count=1),
|
|
460
|
+
SimpleDataclass(name="Second", count=2),
|
|
461
|
+
SimpleDataclass(name="Third", count=3),
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
result = serialize_json(data)
|
|
465
|
+
parsed = json.loads(result)
|
|
466
|
+
|
|
467
|
+
assert isinstance(parsed, list)
|
|
468
|
+
assert len(parsed) == 3
|
|
469
|
+
assert parsed[0]["name"] == "First"
|
|
470
|
+
assert parsed[0]["count"] == 1
|
|
471
|
+
assert parsed[1]["name"] == "Second"
|
|
472
|
+
assert parsed[2]["count"] == 3
|
|
473
|
+
|
|
474
|
+
def test_list_of_pydantic_models(self) -> None:
|
|
475
|
+
"""Test list containing Pydantic model instances."""
|
|
476
|
+
data = [
|
|
477
|
+
SimpleModel(name="Alice", value=100),
|
|
478
|
+
SimpleModel(name="Bob", value=200),
|
|
479
|
+
SimpleModel(name="Charlie", value=300),
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
result = serialize_json(data)
|
|
483
|
+
parsed = json.loads(result)
|
|
484
|
+
|
|
485
|
+
assert isinstance(parsed, list)
|
|
486
|
+
assert len(parsed) == 3
|
|
487
|
+
assert parsed[0]["name"] == "Alice"
|
|
488
|
+
assert parsed[0]["value"] == 100
|
|
489
|
+
assert parsed[1]["name"] == "Bob"
|
|
490
|
+
assert parsed[2]["value"] == 300
|
|
491
|
+
|
|
492
|
+
def test_list_of_normal_classes(self) -> None:
|
|
493
|
+
"""Test list containing normal class instances (fallback to str)."""
|
|
494
|
+
|
|
495
|
+
class Item:
|
|
496
|
+
def __init__(self, id: int, label: str) -> None:
|
|
497
|
+
self.id = id
|
|
498
|
+
self.label = label
|
|
499
|
+
|
|
500
|
+
def __str__(self) -> str:
|
|
501
|
+
return f"Item(id={self.id}, label={self.label})"
|
|
502
|
+
|
|
503
|
+
data = [
|
|
504
|
+
Item(1, "First"),
|
|
505
|
+
Item(2, "Second"),
|
|
506
|
+
Item(3, "Third"),
|
|
507
|
+
]
|
|
508
|
+
|
|
509
|
+
result = serialize_json(data)
|
|
510
|
+
parsed = json.loads(result)
|
|
511
|
+
|
|
512
|
+
assert isinstance(parsed, list)
|
|
513
|
+
assert len(parsed) == 3
|
|
514
|
+
assert parsed[0] == "Item(id=1, label=First)"
|
|
515
|
+
assert parsed[1] == "Item(id=2, label=Second)"
|
|
516
|
+
assert parsed[2] == "Item(id=3, label=Third)"
|
|
517
|
+
|
|
518
|
+
def test_list_of_mixed_types(self) -> None:
|
|
519
|
+
"""Test list containing mixed types: Pydantic, dataclass, normal class, primitives."""
|
|
520
|
+
|
|
521
|
+
class CustomItem:
|
|
522
|
+
def __init__(self, name: str) -> None:
|
|
523
|
+
self.name = name
|
|
524
|
+
|
|
525
|
+
def __str__(self) -> str:
|
|
526
|
+
return f"CustomItem({self.name})"
|
|
527
|
+
|
|
528
|
+
data = [
|
|
529
|
+
SimpleModel(name="pydantic", value=1),
|
|
530
|
+
SimpleDataclass(name="dataclass", count=2),
|
|
531
|
+
CustomItem("custom"),
|
|
532
|
+
"plain_string",
|
|
533
|
+
42,
|
|
534
|
+
True,
|
|
535
|
+
None,
|
|
536
|
+
Color.RED,
|
|
537
|
+
]
|
|
538
|
+
|
|
539
|
+
result = serialize_json(data)
|
|
540
|
+
parsed = json.loads(result)
|
|
541
|
+
|
|
542
|
+
assert isinstance(parsed, list)
|
|
543
|
+
assert len(parsed) == 8
|
|
544
|
+
# Pydantic model
|
|
545
|
+
assert parsed[0]["name"] == "pydantic"
|
|
546
|
+
assert parsed[0]["value"] == 1
|
|
547
|
+
# Dataclass
|
|
548
|
+
assert parsed[1]["name"] == "dataclass"
|
|
549
|
+
assert parsed[1]["count"] == 2
|
|
550
|
+
# Normal class (str fallback)
|
|
551
|
+
assert parsed[2] == "CustomItem(custom)"
|
|
552
|
+
# Primitives
|
|
553
|
+
assert parsed[3] == "plain_string"
|
|
554
|
+
assert parsed[4] == 42
|
|
555
|
+
assert parsed[5] is True
|
|
556
|
+
assert parsed[6] is None
|
|
557
|
+
# Enum
|
|
558
|
+
assert parsed[7] == "red"
|
|
559
|
+
|
|
560
|
+
def test_list_of_lists_mixed(self) -> None:
|
|
561
|
+
"""Test nested lists containing mixed types."""
|
|
562
|
+
|
|
563
|
+
class Widget:
|
|
564
|
+
def __init__(self, id: int) -> None:
|
|
565
|
+
self.id = id
|
|
566
|
+
|
|
567
|
+
def __str__(self) -> str:
|
|
568
|
+
return f"Widget({self.id})"
|
|
569
|
+
|
|
570
|
+
data = [
|
|
571
|
+
[SimpleModel(name="model1", value=1), SimpleModel(name="model2", value=2)],
|
|
572
|
+
[
|
|
573
|
+
SimpleDataclass(name="dc1", count=10),
|
|
574
|
+
SimpleDataclass(name="dc2", count=20),
|
|
575
|
+
],
|
|
576
|
+
[Widget(1), Widget(2), Widget(3)],
|
|
577
|
+
["string1", "string2"],
|
|
578
|
+
[1, 2, 3, 4],
|
|
579
|
+
[Color.RED, Color.GREEN, Priority.HIGH],
|
|
580
|
+
[True, False, None],
|
|
581
|
+
]
|
|
582
|
+
|
|
583
|
+
result = serialize_json(data)
|
|
584
|
+
parsed = json.loads(result)
|
|
585
|
+
|
|
586
|
+
assert isinstance(parsed, list)
|
|
587
|
+
assert len(parsed) == 7
|
|
588
|
+
|
|
589
|
+
# First sublist: Pydantic models
|
|
590
|
+
assert len(parsed[0]) == 2
|
|
591
|
+
assert parsed[0][0]["name"] == "model1"
|
|
592
|
+
assert parsed[0][1]["value"] == 2
|
|
593
|
+
|
|
594
|
+
# Second sublist: Dataclasses
|
|
595
|
+
assert len(parsed[1]) == 2
|
|
596
|
+
assert parsed[1][0]["name"] == "dc1"
|
|
597
|
+
assert parsed[1][1]["count"] == 20
|
|
598
|
+
|
|
599
|
+
# Third sublist: Normal classes
|
|
600
|
+
assert len(parsed[2]) == 3
|
|
601
|
+
assert parsed[2][0] == "Widget(1)"
|
|
602
|
+
assert parsed[2][2] == "Widget(3)"
|
|
603
|
+
|
|
604
|
+
# Fourth sublist: Strings
|
|
605
|
+
assert parsed[3] == ["string1", "string2"]
|
|
606
|
+
|
|
607
|
+
# Fifth sublist: Integers
|
|
608
|
+
assert parsed[4] == [1, 2, 3, 4]
|
|
609
|
+
|
|
610
|
+
# Sixth sublist: Enums
|
|
611
|
+
assert parsed[5] == ["red", "green", 3]
|
|
612
|
+
|
|
613
|
+
# Seventh sublist: Booleans and None
|
|
614
|
+
assert parsed[6] == [True, False, None]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{uipath_core-0.2.1 → uipath_core-0.2.2}/tests/guardrails/test_deterministic_guardrails_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|