planar 0.9.3__py3-none-any.whl → 0.11.0__py3-none-any.whl
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.
- planar/ai/agent.py +2 -1
- planar/ai/agent_base.py +24 -5
- planar/ai/state.py +17 -0
- planar/app.py +18 -1
- planar/data/connection.py +108 -0
- planar/data/dataset.py +11 -104
- planar/data/utils.py +89 -0
- planar/db/alembic/env.py +25 -1
- planar/files/storage/azure_blob.py +1 -1
- planar/registry_items.py +2 -0
- planar/routers/dataset_router.py +213 -0
- planar/routers/info.py +79 -36
- planar/routers/models.py +1 -0
- planar/routers/workflow.py +2 -0
- planar/scaffold_templates/pyproject.toml.j2 +1 -1
- planar/security/authorization.py +31 -3
- planar/security/default_policies.cedar +25 -0
- planar/testing/fixtures.py +34 -1
- planar/testing/planar_test_client.py +1 -1
- planar/workflows/decorators.py +2 -1
- planar/workflows/wrappers.py +1 -0
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/RECORD +25 -72
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/WHEEL +1 -1
- planar/ai/test_agent_serialization.py +0 -229
- planar/ai/test_agent_tool_step_display.py +0 -78
- planar/data/test_dataset.py +0 -354
- planar/files/storage/test_azure_blob.py +0 -435
- planar/files/storage/test_local_directory.py +0 -162
- planar/files/storage/test_s3.py +0 -299
- planar/files/test_files.py +0 -282
- planar/human/test_human.py +0 -385
- planar/logging/test_formatter.py +0 -327
- planar/modeling/mixins/test_auditable.py +0 -97
- planar/modeling/mixins/test_timestamp.py +0 -134
- planar/modeling/mixins/test_uuid_primary_key.py +0 -52
- planar/routers/test_agents_router.py +0 -174
- planar/routers/test_files_router.py +0 -49
- planar/routers/test_object_config_router.py +0 -367
- planar/routers/test_routes_security.py +0 -168
- planar/routers/test_rule_router.py +0 -470
- planar/routers/test_workflow_router.py +0 -539
- planar/rules/test_data/account_dormancy_management.json +0 -223
- planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
- planar/rules/test_data/applicant_risk_assessment.json +0 -435
- planar/rules/test_data/booking_fraud_detection.json +0 -407
- planar/rules/test_data/cellular_data_rollover_system.json +0 -258
- planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
- planar/rules/test_data/customer_lifetime_value.json +0 -143
- planar/rules/test_data/import_duties_calculator.json +0 -289
- planar/rules/test_data/insurance_prior_authorization.json +0 -443
- planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
- planar/rules/test_data/order_consolidation_system.json +0 -375
- planar/rules/test_data/portfolio_risk_monitor.json +0 -471
- planar/rules/test_data/supply_chain_risk.json +0 -253
- planar/rules/test_data/warehouse_cross_docking.json +0 -237
- planar/rules/test_rules.py +0 -1494
- planar/security/tests/test_auth_middleware.py +0 -162
- planar/security/tests/test_authorization_context.py +0 -78
- planar/security/tests/test_cedar_basics.py +0 -41
- planar/security/tests/test_cedar_policies.py +0 -158
- planar/security/tests/test_jwt_principal_context.py +0 -179
- planar/test_app.py +0 -142
- planar/test_cli.py +0 -394
- planar/test_config.py +0 -515
- planar/test_object_config.py +0 -527
- planar/test_object_registry.py +0 -14
- planar/test_sqlalchemy.py +0 -193
- planar/test_utils.py +0 -105
- planar/testing/test_memory_storage.py +0 -143
- planar/workflows/test_concurrency_detection.py +0 -120
- planar/workflows/test_lock_timeout.py +0 -140
- planar/workflows/test_serialization.py +0 -1203
- planar/workflows/test_suspend_deserialization.py +0 -231
- planar/workflows/test_workflow.py +0 -2005
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -1,1203 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
from dataclasses import dataclass
|
3
|
-
from datetime import date, datetime, timedelta
|
4
|
-
from decimal import Decimal
|
5
|
-
from typing import Any, List, Optional, Union
|
6
|
-
|
7
|
-
import pytest
|
8
|
-
from pydantic import BaseModel, Field
|
9
|
-
|
10
|
-
from planar.workflows.serialization import (
|
11
|
-
deserialize_args,
|
12
|
-
deserialize_primitive,
|
13
|
-
deserialize_result,
|
14
|
-
deserialize_value,
|
15
|
-
is_pydantic_model,
|
16
|
-
serialize_args,
|
17
|
-
serialize_primitive,
|
18
|
-
serialize_result,
|
19
|
-
serialize_value,
|
20
|
-
)
|
21
|
-
|
22
|
-
|
23
|
-
class SamplePerson(BaseModel):
|
24
|
-
name: str
|
25
|
-
age: int
|
26
|
-
birth_date: datetime
|
27
|
-
balance: Decimal
|
28
|
-
id: uuid.UUID
|
29
|
-
active: bool
|
30
|
-
tags: List[str]
|
31
|
-
|
32
|
-
|
33
|
-
class SampleNestedModel(BaseModel):
|
34
|
-
person: SamplePerson
|
35
|
-
metadata: Optional[dict] = None
|
36
|
-
|
37
|
-
|
38
|
-
@dataclass
|
39
|
-
class SampleDataClass:
|
40
|
-
name: str
|
41
|
-
value: int
|
42
|
-
|
43
|
-
|
44
|
-
def test_is_pydantic_model():
|
45
|
-
assert is_pydantic_model(SamplePerson)
|
46
|
-
assert is_pydantic_model(SampleNestedModel)
|
47
|
-
assert not is_pydantic_model(dict)
|
48
|
-
assert not is_pydantic_model(str)
|
49
|
-
assert not is_pydantic_model(5)
|
50
|
-
|
51
|
-
|
52
|
-
def test_serialize_primitive():
|
53
|
-
# Test basic primitives
|
54
|
-
assert serialize_primitive(True) is True
|
55
|
-
assert serialize_primitive(42) == 42
|
56
|
-
assert serialize_primitive(3.14) == 3.14
|
57
|
-
|
58
|
-
# Test Decimal
|
59
|
-
decimal_value = Decimal("123.456")
|
60
|
-
assert serialize_primitive(decimal_value) == "123.456"
|
61
|
-
|
62
|
-
# Test UUID
|
63
|
-
test_uuid = uuid.uuid4()
|
64
|
-
assert serialize_primitive(test_uuid) == str(test_uuid)
|
65
|
-
|
66
|
-
# Test datetime
|
67
|
-
test_dt = datetime(2023, 5, 15, 12, 30, 45)
|
68
|
-
assert serialize_primitive(test_dt) == test_dt.isoformat()
|
69
|
-
|
70
|
-
# Test date
|
71
|
-
test_date = date(2023, 5, 15)
|
72
|
-
assert serialize_primitive(test_date) == test_date.isoformat()
|
73
|
-
|
74
|
-
# Test timedelta
|
75
|
-
test_td = timedelta(days=1, hours=2, minutes=30)
|
76
|
-
serialized_td = serialize_primitive(test_td)
|
77
|
-
assert isinstance(serialized_td, dict)
|
78
|
-
assert serialized_td["days"] == 1
|
79
|
-
assert serialized_td["seconds"] == (2 * 3600) + (30 * 60)
|
80
|
-
assert serialized_td["microseconds"] == 0
|
81
|
-
|
82
|
-
|
83
|
-
def test_deserialize_primitive():
|
84
|
-
# Test basic primitives
|
85
|
-
assert deserialize_primitive(True, bool) is True
|
86
|
-
assert deserialize_primitive(42, int) == 42
|
87
|
-
assert deserialize_primitive(3.14, float) == 3.14
|
88
|
-
|
89
|
-
# Test Decimal
|
90
|
-
assert deserialize_primitive("123.456", Decimal) == Decimal("123.456")
|
91
|
-
|
92
|
-
# Test UUID
|
93
|
-
test_uuid = uuid.uuid4()
|
94
|
-
assert deserialize_primitive(str(test_uuid), uuid.UUID) == test_uuid
|
95
|
-
|
96
|
-
# Test datetime
|
97
|
-
test_dt = datetime(2023, 5, 15, 12, 30, 45)
|
98
|
-
assert deserialize_primitive(test_dt.isoformat(), datetime) == test_dt
|
99
|
-
|
100
|
-
# Test date
|
101
|
-
test_date = date(2023, 5, 15)
|
102
|
-
assert deserialize_primitive(test_date.isoformat(), date) == test_date
|
103
|
-
|
104
|
-
# Test timedelta as dict
|
105
|
-
test_td = timedelta(days=1, hours=2, minutes=30)
|
106
|
-
serialized_td = {"days": 1, "seconds": (2 * 3600) + (30 * 60), "microseconds": 0}
|
107
|
-
assert deserialize_primitive(serialized_td, timedelta) == test_td
|
108
|
-
|
109
|
-
# Test timedelta as seconds
|
110
|
-
assert deserialize_primitive(3600, timedelta) == timedelta(hours=1)
|
111
|
-
|
112
|
-
# Test invalid timedelta
|
113
|
-
with pytest.raises(ValueError):
|
114
|
-
deserialize_primitive("invalid", timedelta)
|
115
|
-
|
116
|
-
|
117
|
-
def test_serialize_value():
|
118
|
-
# Test None
|
119
|
-
assert serialize_value(None) is None
|
120
|
-
|
121
|
-
# Test Pydantic model
|
122
|
-
person = SamplePerson(
|
123
|
-
name="John",
|
124
|
-
age=30,
|
125
|
-
birth_date=datetime(1990, 1, 1),
|
126
|
-
balance=Decimal("1000.50"),
|
127
|
-
id=uuid.uuid4(),
|
128
|
-
active=True,
|
129
|
-
tags=["developer", "python"],
|
130
|
-
)
|
131
|
-
serialized_person = serialize_value(person)
|
132
|
-
assert isinstance(serialized_person, dict)
|
133
|
-
assert serialized_person["name"] == "John"
|
134
|
-
assert serialized_person["age"] == 30
|
135
|
-
assert "birth_date" in serialized_person
|
136
|
-
# Handle both string representation or Decimal object (implementation might vary)
|
137
|
-
assert str(serialized_person["balance"]) == "1000.50"
|
138
|
-
assert "id" in serialized_person
|
139
|
-
assert serialized_person["active"] is True
|
140
|
-
assert serialized_person["tags"] == ["developer", "python"]
|
141
|
-
|
142
|
-
# Test Pydantic model type
|
143
|
-
serialized_model_definition = serialize_value(SamplePerson)
|
144
|
-
expected_definition = {
|
145
|
-
"title": "SamplePerson",
|
146
|
-
"type": "object",
|
147
|
-
"properties": {
|
148
|
-
"name": {"title": "Name", "type": "string"},
|
149
|
-
"age": {"title": "Age", "type": "integer"},
|
150
|
-
"birth_date": {
|
151
|
-
"title": "Birth Date",
|
152
|
-
"type": "string",
|
153
|
-
"format": "date-time",
|
154
|
-
},
|
155
|
-
"balance": {
|
156
|
-
"title": "Balance",
|
157
|
-
"anyOf": [{"type": "number"}, {"type": "string"}],
|
158
|
-
},
|
159
|
-
"id": {"format": "uuid", "title": "Id", "type": "string"},
|
160
|
-
"active": {"title": "Active", "type": "boolean"},
|
161
|
-
"tags": {"title": "Tags", "type": "array", "items": {"type": "string"}},
|
162
|
-
},
|
163
|
-
"required": ["name", "age", "birth_date", "balance", "id", "active", "tags"],
|
164
|
-
}
|
165
|
-
|
166
|
-
assert serialized_model_definition == expected_definition
|
167
|
-
|
168
|
-
# Test primitives
|
169
|
-
assert serialize_value(42) == 42
|
170
|
-
assert serialize_value(Decimal("123.45")) == "123.45"
|
171
|
-
|
172
|
-
# Test nested model
|
173
|
-
nested = SampleNestedModel(person=person, metadata={"source": "test"})
|
174
|
-
serialized_nested = serialize_value(nested)
|
175
|
-
assert isinstance(serialized_nested, dict)
|
176
|
-
assert "person" in serialized_nested
|
177
|
-
assert serialized_nested["metadata"] == {"source": "test"}
|
178
|
-
|
179
|
-
|
180
|
-
def test_deserialize_value():
|
181
|
-
# Test None
|
182
|
-
assert deserialize_value(None) is None
|
183
|
-
assert deserialize_value(None, str) is None
|
184
|
-
|
185
|
-
# Test with no type hint
|
186
|
-
assert deserialize_value(42, None) == 42
|
187
|
-
|
188
|
-
# Test Pydantic model
|
189
|
-
test_uuid = uuid.uuid4()
|
190
|
-
person_data = {
|
191
|
-
"name": "John",
|
192
|
-
"age": 30,
|
193
|
-
"birth_date": "1990-01-01T00:00:00",
|
194
|
-
"balance": "1000.50",
|
195
|
-
"id": str(test_uuid),
|
196
|
-
"active": True,
|
197
|
-
"tags": ["developer", "python"],
|
198
|
-
}
|
199
|
-
deserialized_person = deserialize_value(person_data, SamplePerson)
|
200
|
-
assert isinstance(deserialized_person, SamplePerson)
|
201
|
-
assert deserialized_person.name == "John"
|
202
|
-
assert deserialized_person.age == 30
|
203
|
-
assert deserialized_person.birth_date == datetime(1990, 1, 1)
|
204
|
-
assert deserialized_person.balance == Decimal("1000.50")
|
205
|
-
assert deserialized_person.id == test_uuid
|
206
|
-
assert deserialized_person.active is True
|
207
|
-
assert deserialized_person.tags == ["developer", "python"]
|
208
|
-
|
209
|
-
# Test Pydantic model type
|
210
|
-
serialized_model_definition = {
|
211
|
-
"title": "SamplePerson",
|
212
|
-
"type": "object",
|
213
|
-
"properties": {
|
214
|
-
"name": {"title": "Name", "type": "string"},
|
215
|
-
"age": {"title": "Age", "type": "integer"},
|
216
|
-
"birth_date": {
|
217
|
-
"title": "Birth Date",
|
218
|
-
"type": "string",
|
219
|
-
"format": "date-time",
|
220
|
-
},
|
221
|
-
"balance": {
|
222
|
-
"title": "Balance",
|
223
|
-
"anyOf": [{"type": "number"}, {"type": "string"}],
|
224
|
-
},
|
225
|
-
"id": {"format": "uuid", "title": "Id", "type": "string"},
|
226
|
-
"active": {"title": "Active", "type": "boolean"},
|
227
|
-
"tags": {"title": "Tags", "type": "array", "items": {"type": "string"}},
|
228
|
-
},
|
229
|
-
"required": ["name", "age", "birth_date", "balance", "id", "active", "tags"],
|
230
|
-
}
|
231
|
-
deserialized_model_definition = deserialize_value(
|
232
|
-
serialized_model_definition, type_hint=type[SamplePerson]
|
233
|
-
)
|
234
|
-
assert deserialized_model_definition == SamplePerson
|
235
|
-
|
236
|
-
# Test Union type
|
237
|
-
union_type = Union[int, str]
|
238
|
-
assert deserialize_value(42, union_type) == 42
|
239
|
-
assert deserialize_value("hello", union_type) == "hello"
|
240
|
-
union_type_pipe = int | str
|
241
|
-
assert deserialize_value(42, union_type_pipe) == 42
|
242
|
-
assert deserialize_value("hello", union_type_pipe) == "hello"
|
243
|
-
|
244
|
-
# Test primitive types
|
245
|
-
assert deserialize_value("123.45", Decimal) == Decimal("123.45")
|
246
|
-
assert deserialize_value(3600, timedelta) == timedelta(hours=1)
|
247
|
-
|
248
|
-
# Test an invalid Union type
|
249
|
-
with pytest.raises(ValueError):
|
250
|
-
deserialize_value("not_a_number", Union[int, float])
|
251
|
-
|
252
|
-
|
253
|
-
def test_deserialize_value_uniontype():
|
254
|
-
class FooModel(BaseModel):
|
255
|
-
value: int
|
256
|
-
|
257
|
-
data = {"value": 5}
|
258
|
-
deserialized = deserialize_value(data, FooModel | None)
|
259
|
-
assert isinstance(deserialized, FooModel)
|
260
|
-
assert deserialized.value == 5
|
261
|
-
|
262
|
-
deserialized = deserialize_value(data, Optional[FooModel])
|
263
|
-
assert isinstance(deserialized, FooModel)
|
264
|
-
assert deserialized.value == 5
|
265
|
-
|
266
|
-
deserialized = deserialize_value(data, Union[FooModel, None])
|
267
|
-
assert isinstance(deserialized, FooModel)
|
268
|
-
assert deserialized.value == 5
|
269
|
-
|
270
|
-
|
271
|
-
def example_function(
|
272
|
-
person: SamplePerson, count: int, tags: List[str] = []
|
273
|
-
) -> SampleNestedModel:
|
274
|
-
return SampleNestedModel(
|
275
|
-
person=person, metadata={"count": count, "tags": tags or []}
|
276
|
-
)
|
277
|
-
|
278
|
-
|
279
|
-
def test_serialize_args():
|
280
|
-
person = SamplePerson(
|
281
|
-
name="Alice",
|
282
|
-
age=25,
|
283
|
-
birth_date=datetime(1998, 5, 10),
|
284
|
-
balance=Decimal("500.25"),
|
285
|
-
id=uuid.uuid4(),
|
286
|
-
active=True,
|
287
|
-
tags=["tester"],
|
288
|
-
)
|
289
|
-
|
290
|
-
args = [person]
|
291
|
-
kwargs = {"count": 42, "tags": ["important", "test"]}
|
292
|
-
|
293
|
-
serialized_args, serialized_kwargs = serialize_args(example_function, args, kwargs)
|
294
|
-
|
295
|
-
# Check args
|
296
|
-
assert isinstance(serialized_args[0], dict)
|
297
|
-
assert serialized_args[0]["name"] == "Alice"
|
298
|
-
|
299
|
-
# Check kwargs
|
300
|
-
assert serialized_kwargs["count"] == 42
|
301
|
-
assert serialized_kwargs["tags"] == ["important", "test"]
|
302
|
-
|
303
|
-
|
304
|
-
def test_deserialize_args():
|
305
|
-
test_uuid = uuid.uuid4()
|
306
|
-
person_data = {
|
307
|
-
"name": "Alice",
|
308
|
-
"age": 25,
|
309
|
-
"birth_date": "1998-05-10T00:00:00",
|
310
|
-
"balance": "500.25",
|
311
|
-
"id": str(test_uuid),
|
312
|
-
"active": True,
|
313
|
-
"tags": ["tester"],
|
314
|
-
}
|
315
|
-
|
316
|
-
args = [person_data]
|
317
|
-
kwargs = {"count": 42, "tags": ["important", "test"]}
|
318
|
-
|
319
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
320
|
-
example_function, args, kwargs
|
321
|
-
)
|
322
|
-
|
323
|
-
# Check args
|
324
|
-
assert isinstance(deserialized_args[0], SamplePerson)
|
325
|
-
assert deserialized_args[0].name == "Alice"
|
326
|
-
assert deserialized_args[0].id == test_uuid
|
327
|
-
|
328
|
-
# Check kwargs
|
329
|
-
assert deserialized_kwargs["count"] == 42
|
330
|
-
assert deserialized_kwargs["tags"] == ["important", "test"]
|
331
|
-
|
332
|
-
|
333
|
-
def test_serialize_result():
|
334
|
-
person = SamplePerson(
|
335
|
-
name="Bob",
|
336
|
-
age=35,
|
337
|
-
birth_date=datetime(1988, 3, 15),
|
338
|
-
balance=Decimal("1200.75"),
|
339
|
-
id=uuid.uuid4(),
|
340
|
-
active=True,
|
341
|
-
tags=["developer"],
|
342
|
-
)
|
343
|
-
|
344
|
-
result = SampleNestedModel(person=person, metadata={"source": "result_test"})
|
345
|
-
|
346
|
-
serialized_result = serialize_result(example_function, result)
|
347
|
-
|
348
|
-
assert isinstance(serialized_result, dict)
|
349
|
-
assert "person" in serialized_result
|
350
|
-
assert serialized_result["person"]["name"] == "Bob"
|
351
|
-
assert serialized_result["metadata"] == {"source": "result_test"}
|
352
|
-
|
353
|
-
|
354
|
-
def test_deserialize_result():
|
355
|
-
test_uuid = uuid.uuid4()
|
356
|
-
result_data = {
|
357
|
-
"person": {
|
358
|
-
"name": "Bob",
|
359
|
-
"age": 35,
|
360
|
-
"birth_date": "1988-03-15T00:00:00",
|
361
|
-
"balance": "1200.75",
|
362
|
-
"id": str(test_uuid),
|
363
|
-
"active": True,
|
364
|
-
"tags": ["developer"],
|
365
|
-
},
|
366
|
-
"metadata": {"source": "result_test"},
|
367
|
-
}
|
368
|
-
|
369
|
-
deserialized_result = deserialize_result(example_function, result_data)
|
370
|
-
|
371
|
-
assert isinstance(deserialized_result, SampleNestedModel)
|
372
|
-
assert isinstance(deserialized_result.person, SamplePerson)
|
373
|
-
assert deserialized_result.person.name == "Bob"
|
374
|
-
assert deserialized_result.person.id == test_uuid
|
375
|
-
assert deserialized_result.metadata == {"source": "result_test"}
|
376
|
-
|
377
|
-
|
378
|
-
def test_serialize_deserialize_roundtrip():
|
379
|
-
# Create test data
|
380
|
-
test_uuid = uuid.uuid4()
|
381
|
-
person = SamplePerson(
|
382
|
-
name="Charlie",
|
383
|
-
age=40,
|
384
|
-
birth_date=datetime(1983, 7, 22),
|
385
|
-
balance=Decimal("2500.10"),
|
386
|
-
id=test_uuid,
|
387
|
-
active=True,
|
388
|
-
tags=["manager", "python"],
|
389
|
-
)
|
390
|
-
|
391
|
-
# Original function call
|
392
|
-
args = [person]
|
393
|
-
kwargs = {"count": 100, "tags": ["high-priority"]}
|
394
|
-
|
395
|
-
# Serialize arguments
|
396
|
-
serialized_args, serialized_kwargs = serialize_args(example_function, args, kwargs)
|
397
|
-
|
398
|
-
# Deserialize arguments
|
399
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
400
|
-
example_function, serialized_args, serialized_kwargs
|
401
|
-
)
|
402
|
-
|
403
|
-
# Call function with deserialized arguments
|
404
|
-
result = example_function(*deserialized_args, **deserialized_kwargs)
|
405
|
-
|
406
|
-
# Serialize result
|
407
|
-
serialized_result = serialize_result(example_function, result)
|
408
|
-
|
409
|
-
# Deserialize result
|
410
|
-
deserialized_result = deserialize_result(example_function, serialized_result)
|
411
|
-
|
412
|
-
# Verify everything is intact
|
413
|
-
assert isinstance(deserialized_result, SampleNestedModel)
|
414
|
-
assert deserialized_result.person.name == "Charlie"
|
415
|
-
assert deserialized_result.person.age == 40
|
416
|
-
assert deserialized_result.person.tags == ["manager", "python"]
|
417
|
-
assert deserialized_result.person.birth_date == datetime(1983, 7, 22)
|
418
|
-
assert deserialized_result.person.balance == Decimal("2500.10")
|
419
|
-
assert deserialized_result.person.id == test_uuid
|
420
|
-
assert deserialized_result.person.active is True
|
421
|
-
assert deserialized_result.metadata
|
422
|
-
assert deserialized_result.metadata["count"] == 100
|
423
|
-
assert deserialized_result.metadata["tags"] == ["high-priority"]
|
424
|
-
|
425
|
-
|
426
|
-
# Test function with Union types for args and kwargs
|
427
|
-
class SampleEventModel(BaseModel):
|
428
|
-
event_id: uuid.UUID
|
429
|
-
timestamp: datetime
|
430
|
-
description: str
|
431
|
-
|
432
|
-
|
433
|
-
class ModelWithDatetimeField(BaseModel):
|
434
|
-
"""Model to test datetime serialization"""
|
435
|
-
|
436
|
-
timestamp: datetime = Field(...)
|
437
|
-
name: str
|
438
|
-
|
439
|
-
|
440
|
-
class ModelWithUUID(BaseModel):
|
441
|
-
"""Model to test UUID serialization"""
|
442
|
-
|
443
|
-
id: uuid.UUID = Field(...)
|
444
|
-
name: str
|
445
|
-
|
446
|
-
|
447
|
-
def function_with_dicts(data: dict, count: int, metadata: dict[str, Any]) -> dict:
|
448
|
-
return {"count": count, "metadata": metadata}
|
449
|
-
|
450
|
-
|
451
|
-
def test_serialize_args_with_dict():
|
452
|
-
# Test with SamplePerson
|
453
|
-
person = {"name": "David", "age": 28, "id": "1235abc"}
|
454
|
-
|
455
|
-
args = [person]
|
456
|
-
kwargs = {"another_dict_key": {"count": 50, "metadata": {"source": "test"}}}
|
457
|
-
|
458
|
-
serialized_args, serialized_kwargs = serialize_args(
|
459
|
-
function_with_dicts, args, kwargs
|
460
|
-
)
|
461
|
-
|
462
|
-
# Check args - should be serialized to dict
|
463
|
-
assert isinstance(serialized_args[0], dict)
|
464
|
-
assert serialized_args[0]["name"] == "David"
|
465
|
-
assert serialized_args[0]["age"] == 28
|
466
|
-
assert serialized_args[0]["id"] == "1235abc"
|
467
|
-
|
468
|
-
# Check kwargs
|
469
|
-
assert serialized_kwargs["another_dict_key"]["count"] == 50
|
470
|
-
assert serialized_kwargs["another_dict_key"]["metadata"] == {"source": "test"}
|
471
|
-
|
472
|
-
|
473
|
-
def test_deserialize_args_with_dict():
|
474
|
-
# Test with SamplePerson
|
475
|
-
person = {"name": "David", "age": 28, "id": "1235abc"}
|
476
|
-
|
477
|
-
args = [person]
|
478
|
-
kwargs = {"another_dict_key": {"count": 50, "metadata": {"source": "test"}}}
|
479
|
-
|
480
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
481
|
-
function_with_dicts, args, kwargs
|
482
|
-
)
|
483
|
-
|
484
|
-
assert deserialized_args[0] == person
|
485
|
-
assert deserialized_kwargs["another_dict_key"] == {
|
486
|
-
"count": 50,
|
487
|
-
"metadata": {"source": "test"},
|
488
|
-
}
|
489
|
-
|
490
|
-
|
491
|
-
# A function with Union type parameters for testing
|
492
|
-
def function_with_unions(
|
493
|
-
data: Union[SamplePerson, SampleEventModel],
|
494
|
-
count: int,
|
495
|
-
metadata: Union[dict, str, None] = None,
|
496
|
-
) -> Union[SampleNestedModel, dict]:
|
497
|
-
if isinstance(data, SamplePerson):
|
498
|
-
return SampleNestedModel(
|
499
|
-
person=data, metadata={"count": count, "extra": metadata}
|
500
|
-
)
|
501
|
-
else: # SampleEventModel
|
502
|
-
return {"event": data.model_dump(), "count": count, "metadata": metadata}
|
503
|
-
|
504
|
-
|
505
|
-
def test_serialize_args_with_unions():
|
506
|
-
# Test with SamplePerson
|
507
|
-
person = SamplePerson(
|
508
|
-
name="David",
|
509
|
-
age=28,
|
510
|
-
birth_date=datetime(1995, 8, 12),
|
511
|
-
balance=Decimal("750.25"),
|
512
|
-
id=uuid.uuid4(),
|
513
|
-
active=True,
|
514
|
-
tags=["engineer"],
|
515
|
-
)
|
516
|
-
|
517
|
-
args = [person]
|
518
|
-
kwargs = {"count": 50, "metadata": {"source": "test"}}
|
519
|
-
|
520
|
-
serialized_args, serialized_kwargs = serialize_args(
|
521
|
-
function_with_unions, args, kwargs
|
522
|
-
)
|
523
|
-
|
524
|
-
# Check args - should be serialized to dict
|
525
|
-
assert isinstance(serialized_args[0], dict)
|
526
|
-
assert serialized_args[0]["name"] == "David"
|
527
|
-
assert serialized_args[0]["age"] == 28
|
528
|
-
assert serialized_args[0]["id"] == str(person.id)
|
529
|
-
|
530
|
-
# Check kwargs
|
531
|
-
assert serialized_kwargs["count"] == 50
|
532
|
-
assert serialized_kwargs["metadata"] == {"source": "test"}
|
533
|
-
|
534
|
-
# Test with SampleEventModel
|
535
|
-
event = SampleEventModel(
|
536
|
-
event_id=uuid.uuid4(), timestamp=datetime.now(), description="Test event"
|
537
|
-
)
|
538
|
-
|
539
|
-
args = [event]
|
540
|
-
kwargs = {"count": 10, "metadata": "event_metadata"}
|
541
|
-
|
542
|
-
serialized_args, serialized_kwargs = serialize_args(
|
543
|
-
function_with_unions, args, kwargs
|
544
|
-
)
|
545
|
-
|
546
|
-
# Check args
|
547
|
-
assert isinstance(serialized_args[0], dict)
|
548
|
-
assert "event_id" in serialized_args[0]
|
549
|
-
assert "timestamp" in serialized_args[0]
|
550
|
-
assert serialized_args[0]["description"] == "Test event"
|
551
|
-
|
552
|
-
# Check kwargs
|
553
|
-
assert serialized_kwargs["count"] == 10
|
554
|
-
assert serialized_kwargs["metadata"] == "event_metadata"
|
555
|
-
|
556
|
-
|
557
|
-
def test_deserialize_args_with_unions():
|
558
|
-
# Test with SamplePerson data
|
559
|
-
test_uuid = uuid.uuid4()
|
560
|
-
person_data = {
|
561
|
-
"name": "David",
|
562
|
-
"age": 28,
|
563
|
-
"birth_date": "1995-08-12T00:00:00",
|
564
|
-
"balance": "750.25",
|
565
|
-
"id": str(test_uuid),
|
566
|
-
"active": True,
|
567
|
-
"tags": ["engineer"],
|
568
|
-
}
|
569
|
-
|
570
|
-
args = [person_data]
|
571
|
-
kwargs = {"count": 50, "metadata": {"source": "test"}}
|
572
|
-
|
573
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
574
|
-
function_with_unions, args, kwargs
|
575
|
-
)
|
576
|
-
|
577
|
-
# For Union types, it will try each type and use the first one that works
|
578
|
-
# SamplePerson should be successfully deserialized
|
579
|
-
assert isinstance(deserialized_args[0], SamplePerson)
|
580
|
-
assert deserialized_args[0].name == "David"
|
581
|
-
assert deserialized_args[0].age == 28
|
582
|
-
assert deserialized_args[0].id == test_uuid
|
583
|
-
|
584
|
-
# Check kwargs
|
585
|
-
assert deserialized_kwargs["count"] == 50
|
586
|
-
assert deserialized_kwargs["metadata"] == {"source": "test"}
|
587
|
-
|
588
|
-
# Test with SampleEventModel data
|
589
|
-
event_uuid = uuid.uuid4()
|
590
|
-
event_time = datetime.now()
|
591
|
-
event_data = {
|
592
|
-
"event_id": str(event_uuid),
|
593
|
-
"timestamp": event_time.isoformat(),
|
594
|
-
"description": "Test event",
|
595
|
-
}
|
596
|
-
|
597
|
-
args = [event_data]
|
598
|
-
kwargs = {"count": 10, "metadata": "event_metadata"}
|
599
|
-
|
600
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
601
|
-
function_with_unions, args, kwargs
|
602
|
-
)
|
603
|
-
|
604
|
-
# It should deserialize to SampleEventModel (the first compatible type)
|
605
|
-
assert isinstance(deserialized_args[0], SampleEventModel)
|
606
|
-
assert deserialized_args[0].event_id == event_uuid
|
607
|
-
assert deserialized_args[0].description == "Test event"
|
608
|
-
|
609
|
-
# Check kwargs
|
610
|
-
assert deserialized_kwargs["count"] == 10
|
611
|
-
assert deserialized_kwargs["metadata"] == "event_metadata"
|
612
|
-
|
613
|
-
|
614
|
-
def test_serialize_result_with_unions():
|
615
|
-
# Test with SampleNestedModel result
|
616
|
-
person = SamplePerson(
|
617
|
-
name="Eve",
|
618
|
-
age=32,
|
619
|
-
birth_date=datetime(1991, 3, 15),
|
620
|
-
balance=Decimal("1500.75"),
|
621
|
-
id=uuid.uuid4(),
|
622
|
-
active=True,
|
623
|
-
tags=["manager"],
|
624
|
-
)
|
625
|
-
|
626
|
-
result = SampleNestedModel(person=person, metadata={"source": "union_test"})
|
627
|
-
|
628
|
-
serialized_result = serialize_result(function_with_unions, result)
|
629
|
-
|
630
|
-
assert isinstance(serialized_result, dict)
|
631
|
-
assert "person" in serialized_result
|
632
|
-
assert serialized_result["person"]["name"] == "Eve"
|
633
|
-
assert serialized_result["metadata"] == {"source": "union_test"}
|
634
|
-
|
635
|
-
# Test with dict result
|
636
|
-
event = SampleEventModel(
|
637
|
-
event_id=uuid.uuid4(), timestamp=datetime.now(), description="Important event"
|
638
|
-
)
|
639
|
-
|
640
|
-
result = {"event": event.model_dump(), "count": 25, "metadata": "event data"}
|
641
|
-
|
642
|
-
serialized_result = serialize_result(function_with_unions, result)
|
643
|
-
|
644
|
-
assert isinstance(serialized_result, dict)
|
645
|
-
assert "event" in serialized_result
|
646
|
-
assert serialized_result["count"] == 25
|
647
|
-
assert serialized_result["metadata"] == "event data"
|
648
|
-
|
649
|
-
|
650
|
-
def test_deserialize_result_with_unions():
|
651
|
-
# Test with SampleNestedModel data
|
652
|
-
test_uuid = uuid.uuid4()
|
653
|
-
result_data = {
|
654
|
-
"person": {
|
655
|
-
"name": "Eve",
|
656
|
-
"age": 32,
|
657
|
-
"birth_date": "1991-03-15T00:00:00",
|
658
|
-
"balance": "1500.75",
|
659
|
-
"id": str(test_uuid),
|
660
|
-
"active": True,
|
661
|
-
"tags": ["manager"],
|
662
|
-
},
|
663
|
-
"metadata": {"source": "union_test"},
|
664
|
-
}
|
665
|
-
|
666
|
-
deserialized_result = deserialize_result(function_with_unions, result_data)
|
667
|
-
|
668
|
-
# For Union types in return type, it tries each type and uses the first one that works
|
669
|
-
assert isinstance(deserialized_result, SampleNestedModel)
|
670
|
-
assert isinstance(deserialized_result.person, SamplePerson)
|
671
|
-
assert deserialized_result.person.name == "Eve"
|
672
|
-
assert deserialized_result.person.id == test_uuid
|
673
|
-
assert deserialized_result.metadata == {"source": "union_test"}
|
674
|
-
|
675
|
-
# Test with dict result
|
676
|
-
event_uuid = uuid.uuid4()
|
677
|
-
event_time = datetime.now().isoformat()
|
678
|
-
result_data = {
|
679
|
-
"event": {
|
680
|
-
"event_id": str(event_uuid),
|
681
|
-
"timestamp": event_time,
|
682
|
-
"description": "Important event",
|
683
|
-
},
|
684
|
-
"count": 25,
|
685
|
-
"metadata": "event data",
|
686
|
-
}
|
687
|
-
|
688
|
-
deserialized_result = deserialize_result(function_with_unions, result_data)
|
689
|
-
|
690
|
-
# Since a dict can be parsed as-is without conversion, it should remain a dict
|
691
|
-
assert isinstance(deserialized_result, dict)
|
692
|
-
assert "event" in deserialized_result
|
693
|
-
assert deserialized_result["count"] == 25
|
694
|
-
assert deserialized_result["metadata"] == "event data"
|
695
|
-
|
696
|
-
|
697
|
-
def test_serialize_deserialize_roundtrip_with_unions():
|
698
|
-
# Create test data - SamplePerson
|
699
|
-
person = SamplePerson(
|
700
|
-
name="Frank",
|
701
|
-
age=45,
|
702
|
-
birth_date=datetime(1978, 11, 5),
|
703
|
-
balance=Decimal("3200.80"),
|
704
|
-
id=uuid.uuid4(),
|
705
|
-
active=True,
|
706
|
-
tags=["director", "finance"],
|
707
|
-
)
|
708
|
-
|
709
|
-
# Original function call
|
710
|
-
args = [person]
|
711
|
-
kwargs = {"count": 75, "metadata": {"department": "finance"}}
|
712
|
-
|
713
|
-
# Serialize arguments
|
714
|
-
serialized_args, serialized_kwargs = serialize_args(
|
715
|
-
function_with_unions, args, kwargs
|
716
|
-
)
|
717
|
-
|
718
|
-
# Deserialize arguments
|
719
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
720
|
-
function_with_unions, serialized_args, serialized_kwargs
|
721
|
-
)
|
722
|
-
|
723
|
-
# Call function with deserialized arguments
|
724
|
-
result = function_with_unions(*deserialized_args, **deserialized_kwargs)
|
725
|
-
|
726
|
-
# Serialize result
|
727
|
-
serialized_result = serialize_result(function_with_unions, result)
|
728
|
-
|
729
|
-
# Deserialize result
|
730
|
-
deserialized_result = deserialize_result(function_with_unions, serialized_result)
|
731
|
-
|
732
|
-
# Verify everything is intact
|
733
|
-
assert isinstance(deserialized_result, SampleNestedModel)
|
734
|
-
assert deserialized_result.person.name == "Frank"
|
735
|
-
assert deserialized_result.person.age == 45
|
736
|
-
assert deserialized_result.person.tags == ["director", "finance"]
|
737
|
-
assert deserialized_result.metadata
|
738
|
-
assert deserialized_result.metadata["count"] == 75
|
739
|
-
assert deserialized_result.metadata["extra"] == {"department": "finance"}
|
740
|
-
|
741
|
-
# Create test data - SampleEventModel
|
742
|
-
event = SampleEventModel(
|
743
|
-
event_id=uuid.uuid4(),
|
744
|
-
timestamp=datetime(2023, 1, 15, 14, 30),
|
745
|
-
description="Quarterly review",
|
746
|
-
)
|
747
|
-
|
748
|
-
# Original function call
|
749
|
-
args = [event]
|
750
|
-
kwargs = {"count": 120, "metadata": "quarterly"}
|
751
|
-
|
752
|
-
# Serialize arguments
|
753
|
-
serialized_args, serialized_kwargs = serialize_args(
|
754
|
-
function_with_unions, args, kwargs
|
755
|
-
)
|
756
|
-
|
757
|
-
# Deserialize arguments
|
758
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
759
|
-
function_with_unions, serialized_args, serialized_kwargs
|
760
|
-
)
|
761
|
-
|
762
|
-
# Call function with deserialized arguments
|
763
|
-
result = function_with_unions(*deserialized_args, **deserialized_kwargs)
|
764
|
-
|
765
|
-
# Serialize result
|
766
|
-
serialized_result = serialize_result(function_with_unions, result)
|
767
|
-
|
768
|
-
# Deserialize result
|
769
|
-
deserialized_result = deserialize_result(function_with_unions, serialized_result)
|
770
|
-
|
771
|
-
# Verify everything is intact
|
772
|
-
assert isinstance(deserialized_result, dict)
|
773
|
-
assert "event" in deserialized_result
|
774
|
-
assert deserialized_result["event"]["description"] == "Quarterly review"
|
775
|
-
assert deserialized_result["count"] == 120
|
776
|
-
assert deserialized_result["metadata"] == "quarterly"
|
777
|
-
|
778
|
-
|
779
|
-
# This class is used for testing list serialization
|
780
|
-
class ListContainer(BaseModel):
|
781
|
-
items: List[SamplePerson]
|
782
|
-
labels: List[str]
|
783
|
-
|
784
|
-
|
785
|
-
def test_serialize_list_of_primitives():
|
786
|
-
# Test serializing lists of primitives
|
787
|
-
int_list = [1, 2, 3, 4, 5]
|
788
|
-
assert serialize_value(int_list) == int_list
|
789
|
-
|
790
|
-
# Test with mixed primitive types
|
791
|
-
mixed_list = [1, "string", 3.14, True]
|
792
|
-
assert serialize_value(mixed_list) == mixed_list
|
793
|
-
|
794
|
-
# Test with complex primitive types
|
795
|
-
now = datetime.now()
|
796
|
-
uuid_val = uuid.uuid4()
|
797
|
-
decimal_val = Decimal("123.456")
|
798
|
-
complex_list = [now, uuid_val, decimal_val]
|
799
|
-
serialized = serialize_value(complex_list)
|
800
|
-
assert serialized[0] == now.isoformat()
|
801
|
-
assert serialized[1] == str(uuid_val)
|
802
|
-
assert serialized[2] == str(decimal_val)
|
803
|
-
|
804
|
-
|
805
|
-
def test_deserialize_list_of_primitives():
|
806
|
-
# Test deserializing lists of primitives
|
807
|
-
serialized_ints = [1, 2, 3, 4, 5]
|
808
|
-
assert deserialize_value(serialized_ints, List[int]) == serialized_ints
|
809
|
-
|
810
|
-
# Test with mixed types that should be cast to a specific type
|
811
|
-
serialized_strings = ["1", "2", "3"]
|
812
|
-
assert deserialize_value(serialized_strings, List[int]) == [1, 2, 3]
|
813
|
-
|
814
|
-
# Test with complex types
|
815
|
-
uuid_val = uuid.uuid4()
|
816
|
-
now = datetime.now()
|
817
|
-
serialized_complex = [str(uuid_val), now.isoformat(), "123.456"]
|
818
|
-
deserialized = deserialize_value(
|
819
|
-
serialized_complex, List[Union[uuid.UUID, datetime, Decimal]]
|
820
|
-
)
|
821
|
-
|
822
|
-
# For Union types, it will use the first type that works
|
823
|
-
# In this case, UUID should be the first successful type for str(uuid_val)
|
824
|
-
assert deserialized[0] == uuid_val
|
825
|
-
assert isinstance(deserialized[1], datetime)
|
826
|
-
# The string "123.456" would be deserialized as a UUID which would fail,
|
827
|
-
# then datetime which would fail, and finally Decimal which should succeed
|
828
|
-
assert deserialized[2] == Decimal("123.456")
|
829
|
-
|
830
|
-
|
831
|
-
def test_serialize_list_of_models():
|
832
|
-
# Create sample data
|
833
|
-
persons = [
|
834
|
-
SamplePerson(
|
835
|
-
name="Person 1",
|
836
|
-
age=30,
|
837
|
-
birth_date=datetime(1993, 5, 10),
|
838
|
-
balance=Decimal("500.00"),
|
839
|
-
id=uuid.uuid4(),
|
840
|
-
active=True,
|
841
|
-
tags=["developer"],
|
842
|
-
),
|
843
|
-
SamplePerson(
|
844
|
-
name="Person 2",
|
845
|
-
age=25,
|
846
|
-
birth_date=datetime(1998, 8, 15),
|
847
|
-
balance=Decimal("750.50"),
|
848
|
-
id=uuid.uuid4(),
|
849
|
-
active=False,
|
850
|
-
tags=["designer", "ui"],
|
851
|
-
),
|
852
|
-
]
|
853
|
-
|
854
|
-
# Test serializing a list of models
|
855
|
-
serialized_persons = serialize_value(persons)
|
856
|
-
assert isinstance(serialized_persons, list)
|
857
|
-
assert len(serialized_persons) == 2
|
858
|
-
assert isinstance(serialized_persons[0], dict)
|
859
|
-
assert serialized_persons[0]["name"] == "Person 1"
|
860
|
-
assert serialized_persons[1]["name"] == "Person 2"
|
861
|
-
|
862
|
-
# Check that datetime fields are properly serialized
|
863
|
-
assert isinstance(serialized_persons[0]["birth_date"], str)
|
864
|
-
assert isinstance(serialized_persons[1]["birth_date"], str)
|
865
|
-
# Verify they can be parsed as ISO format
|
866
|
-
datetime.fromisoformat(serialized_persons[0]["birth_date"])
|
867
|
-
datetime.fromisoformat(serialized_persons[1]["birth_date"])
|
868
|
-
|
869
|
-
# Test serializing a model with a list field
|
870
|
-
container = ListContainer(
|
871
|
-
items=persons,
|
872
|
-
labels=["employee", "contractor"],
|
873
|
-
)
|
874
|
-
|
875
|
-
serialized_container = serialize_value(container)
|
876
|
-
assert isinstance(serialized_container, dict)
|
877
|
-
assert isinstance(serialized_container["items"], list)
|
878
|
-
assert len(serialized_container["items"]) == 2
|
879
|
-
assert serialized_container["items"][0]["name"] == "Person 1"
|
880
|
-
assert serialized_container["items"][1]["name"] == "Person 2"
|
881
|
-
assert serialized_container["labels"] == ["employee", "contractor"]
|
882
|
-
|
883
|
-
|
884
|
-
def test_deserialize_list_of_models():
|
885
|
-
# Create serialized data
|
886
|
-
person1_id = uuid.uuid4()
|
887
|
-
person2_id = uuid.uuid4()
|
888
|
-
|
889
|
-
serialized_persons = [
|
890
|
-
{
|
891
|
-
"name": "Person 1",
|
892
|
-
"age": 30,
|
893
|
-
"birth_date": "1993-05-10T00:00:00",
|
894
|
-
"balance": "500.00",
|
895
|
-
"id": str(person1_id),
|
896
|
-
"active": True,
|
897
|
-
"tags": ["developer"],
|
898
|
-
},
|
899
|
-
{
|
900
|
-
"name": "Person 2",
|
901
|
-
"age": 25,
|
902
|
-
"birth_date": "1998-08-15T00:00:00",
|
903
|
-
"balance": "750.50",
|
904
|
-
"id": str(person2_id),
|
905
|
-
"active": False,
|
906
|
-
"tags": ["designer", "ui"],
|
907
|
-
},
|
908
|
-
]
|
909
|
-
|
910
|
-
# Test deserializing a list of models
|
911
|
-
deserialized_persons = deserialize_value(serialized_persons, List[SamplePerson])
|
912
|
-
assert isinstance(deserialized_persons, list)
|
913
|
-
assert len(deserialized_persons) == 2
|
914
|
-
assert isinstance(deserialized_persons[0], SamplePerson)
|
915
|
-
assert deserialized_persons[0].id == person1_id
|
916
|
-
assert deserialized_persons[0].name == "Person 1"
|
917
|
-
assert deserialized_persons[1].id == person2_id
|
918
|
-
assert deserialized_persons[1].name == "Person 2"
|
919
|
-
|
920
|
-
# Test deserializing a model with a list field
|
921
|
-
serialized_container = {
|
922
|
-
"items": serialized_persons,
|
923
|
-
"labels": ["employee", "contractor"],
|
924
|
-
}
|
925
|
-
|
926
|
-
deserialized_container = deserialize_value(serialized_container, ListContainer)
|
927
|
-
assert isinstance(deserialized_container, ListContainer)
|
928
|
-
assert isinstance(deserialized_container.items, list)
|
929
|
-
assert len(deserialized_container.items) == 2
|
930
|
-
assert isinstance(deserialized_container.items[0], SamplePerson)
|
931
|
-
assert deserialized_container.items[0].name == "Person 1"
|
932
|
-
assert deserialized_container.items[1].name == "Person 2"
|
933
|
-
assert deserialized_container.labels == ["employee", "contractor"]
|
934
|
-
|
935
|
-
|
936
|
-
# For testing list args and return values
|
937
|
-
def function_with_list_args_return(
|
938
|
-
persons: List[SamplePerson], tags: List[str]
|
939
|
-
) -> List[ListContainer]:
|
940
|
-
"""Function that takes and returns lists for testing serialization/deserialization."""
|
941
|
-
containers = []
|
942
|
-
for i, tag in enumerate(tags):
|
943
|
-
# Take a slice of persons for each container
|
944
|
-
container_persons = persons[: i + 1]
|
945
|
-
containers.append(
|
946
|
-
ListContainer(
|
947
|
-
items=container_persons,
|
948
|
-
labels=[tag] * len(container_persons),
|
949
|
-
)
|
950
|
-
)
|
951
|
-
return containers
|
952
|
-
|
953
|
-
|
954
|
-
def test_timestamp_serialization():
|
955
|
-
"""Test specifically for datetime serialization in Pydantic models"""
|
956
|
-
# Create a model with a timestamp
|
957
|
-
model = ModelWithDatetimeField(
|
958
|
-
timestamp=datetime(2023, 5, 15, 12, 30, 45), name="Test Model"
|
959
|
-
)
|
960
|
-
|
961
|
-
# Serialize the model
|
962
|
-
serialized = serialize_value(model)
|
963
|
-
|
964
|
-
# Verify the timestamp is properly serialized as a string
|
965
|
-
assert isinstance(serialized, dict)
|
966
|
-
assert "timestamp" in serialized
|
967
|
-
assert isinstance(serialized["timestamp"], str)
|
968
|
-
|
969
|
-
# Verify it can be parsed back to a datetime
|
970
|
-
dt = datetime.fromisoformat(serialized["timestamp"])
|
971
|
-
assert dt == model.timestamp
|
972
|
-
|
973
|
-
# Verify we can deserialize it back to the original model
|
974
|
-
deserialized = deserialize_value(serialized, ModelWithDatetimeField)
|
975
|
-
assert isinstance(deserialized, ModelWithDatetimeField)
|
976
|
-
assert deserialized.timestamp == model.timestamp
|
977
|
-
assert deserialized.name == model.name
|
978
|
-
|
979
|
-
|
980
|
-
def test_uuid_serialization():
|
981
|
-
"""Test specifically for UUID serialization in Pydantic models"""
|
982
|
-
# Create a model with a UUID
|
983
|
-
test_uuid = uuid.uuid4()
|
984
|
-
model = ModelWithUUID(id=test_uuid, name="Test Model")
|
985
|
-
|
986
|
-
# Serialize the model
|
987
|
-
serialized = serialize_value(model)
|
988
|
-
|
989
|
-
# Verify the UUID is properly serialized as a string
|
990
|
-
assert isinstance(serialized, dict)
|
991
|
-
assert "id" in serialized
|
992
|
-
assert isinstance(serialized["id"], str)
|
993
|
-
|
994
|
-
# Verify it can be parsed back to a UUID
|
995
|
-
parsed_uuid = uuid.UUID(serialized["id"])
|
996
|
-
assert parsed_uuid == test_uuid
|
997
|
-
|
998
|
-
# Verify we can deserialize it back to the original model
|
999
|
-
deserialized = deserialize_value(serialized, ModelWithUUID)
|
1000
|
-
assert isinstance(deserialized, ModelWithUUID)
|
1001
|
-
assert deserialized.id == test_uuid
|
1002
|
-
assert deserialized.name == model.name
|
1003
|
-
|
1004
|
-
|
1005
|
-
def test_serialize_deserialize_list_roundtrip():
|
1006
|
-
# Create test data
|
1007
|
-
persons = [
|
1008
|
-
SamplePerson(
|
1009
|
-
name=f"Person {i}",
|
1010
|
-
age=30 + i,
|
1011
|
-
birth_date=datetime(1990, i, 15),
|
1012
|
-
balance=Decimal(f"{i}00.50"),
|
1013
|
-
id=uuid.uuid4(),
|
1014
|
-
active=i % 2 == 0,
|
1015
|
-
tags=[f"tag{i}", f"group{i}"],
|
1016
|
-
)
|
1017
|
-
for i in range(1, 4)
|
1018
|
-
]
|
1019
|
-
tags = ["employee", "contractor"]
|
1020
|
-
|
1021
|
-
# Serialize arguments
|
1022
|
-
serialized_args, serialized_kwargs = serialize_args(
|
1023
|
-
function_with_list_args_return, [persons, tags], {}
|
1024
|
-
)
|
1025
|
-
|
1026
|
-
# Check serialized args
|
1027
|
-
assert isinstance(serialized_args[0], list)
|
1028
|
-
assert len(serialized_args[0]) == 3
|
1029
|
-
assert isinstance(serialized_args[0][0], dict)
|
1030
|
-
assert serialized_args[0][0]["name"] == "Person 1"
|
1031
|
-
assert serialized_args[1] == tags
|
1032
|
-
|
1033
|
-
# Deserialize arguments
|
1034
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
1035
|
-
function_with_list_args_return, serialized_args, serialized_kwargs
|
1036
|
-
)
|
1037
|
-
|
1038
|
-
# Check deserialized args
|
1039
|
-
assert isinstance(deserialized_args[0], list)
|
1040
|
-
assert isinstance(deserialized_args[0][0], SamplePerson)
|
1041
|
-
assert deserialized_args[0][0].name == "Person 1"
|
1042
|
-
assert deserialized_args[1] == tags
|
1043
|
-
|
1044
|
-
# Call function with deserialized arguments
|
1045
|
-
result = function_with_list_args_return(*deserialized_args, **deserialized_kwargs)
|
1046
|
-
|
1047
|
-
# Serialize result
|
1048
|
-
serialized_result = serialize_result(function_with_list_args_return, result)
|
1049
|
-
|
1050
|
-
# Check serialized result
|
1051
|
-
assert isinstance(serialized_result, list)
|
1052
|
-
assert len(serialized_result) == 2
|
1053
|
-
assert isinstance(serialized_result[0], dict)
|
1054
|
-
assert "items" in serialized_result[0]
|
1055
|
-
assert isinstance(serialized_result[0]["items"], list)
|
1056
|
-
|
1057
|
-
# Deserialize result
|
1058
|
-
deserialized_result = deserialize_result(
|
1059
|
-
function_with_list_args_return,
|
1060
|
-
serialized_result,
|
1061
|
-
None,
|
1062
|
-
deserialized_args,
|
1063
|
-
deserialized_kwargs,
|
1064
|
-
)
|
1065
|
-
|
1066
|
-
# Check deserialized result
|
1067
|
-
assert isinstance(deserialized_result, list)
|
1068
|
-
assert len(deserialized_result) == 2
|
1069
|
-
assert isinstance(deserialized_result[0], ListContainer)
|
1070
|
-
assert len(deserialized_result[0].items) == 1
|
1071
|
-
assert deserialized_result[0].items[0].name == "Person 1"
|
1072
|
-
assert deserialized_result[0].labels == ["employee"]
|
1073
|
-
assert len(deserialized_result[1].items) == 2
|
1074
|
-
assert deserialized_result[1].labels == ["contractor", "contractor"]
|
1075
|
-
|
1076
|
-
|
1077
|
-
class GenericResultModel[T](BaseModel):
|
1078
|
-
content: T
|
1079
|
-
metadata: str
|
1080
|
-
|
1081
|
-
|
1082
|
-
class SimpleData(BaseModel):
|
1083
|
-
value: int
|
1084
|
-
|
1085
|
-
|
1086
|
-
def function_with_generic_return[T](data: T) -> GenericResultModel[T]:
|
1087
|
-
return GenericResultModel[T](content=data, metadata="Generic metadata")
|
1088
|
-
|
1089
|
-
|
1090
|
-
def test_deserialize_generic_result():
|
1091
|
-
"""Test deserialization of a result with a simple generic type."""
|
1092
|
-
original_data = SimpleData(value=123)
|
1093
|
-
args = [original_data]
|
1094
|
-
kwargs = {}
|
1095
|
-
|
1096
|
-
# Simulate execution and serialization
|
1097
|
-
result = function_with_generic_return(*args, **kwargs)
|
1098
|
-
serialized_result = serialize_result(function_with_generic_return, result)
|
1099
|
-
|
1100
|
-
# Deserialize the result, inferring the type from args
|
1101
|
-
deserialized = deserialize_result(
|
1102
|
-
function_with_generic_return, serialized_result, None, args, kwargs
|
1103
|
-
)
|
1104
|
-
|
1105
|
-
assert isinstance(deserialized, GenericResultModel)
|
1106
|
-
assert isinstance(deserialized.content, SimpleData)
|
1107
|
-
assert deserialized.content.value == 123
|
1108
|
-
assert deserialized.metadata == "Generic metadata"
|
1109
|
-
|
1110
|
-
|
1111
|
-
def function_with_generic_list_return[T](items: list[T]) -> GenericResultModel[list[T]]:
|
1112
|
-
return GenericResultModel[list[T]](content=items, metadata="List metadata")
|
1113
|
-
|
1114
|
-
|
1115
|
-
def test_deserialize_generic_list_result():
|
1116
|
-
"""Test deserialization where the generic type parameter is a List."""
|
1117
|
-
original_data = [SimpleData(value=1), SimpleData(value=2)]
|
1118
|
-
args = [original_data]
|
1119
|
-
kwargs = {}
|
1120
|
-
|
1121
|
-
result = function_with_generic_list_return(*args, **kwargs)
|
1122
|
-
serialized_result = serialize_result(function_with_generic_list_return, result)
|
1123
|
-
|
1124
|
-
deserialized = deserialize_result(
|
1125
|
-
function_with_generic_list_return, serialized_result, None, args, kwargs
|
1126
|
-
)
|
1127
|
-
|
1128
|
-
assert isinstance(deserialized, GenericResultModel)
|
1129
|
-
assert isinstance(deserialized.content, list)
|
1130
|
-
assert len(deserialized.content) == 2
|
1131
|
-
assert isinstance(deserialized.content[0], SimpleData)
|
1132
|
-
assert deserialized.content[0].value == 1
|
1133
|
-
assert deserialized.content[1].value == 2
|
1134
|
-
assert deserialized.metadata == "List metadata"
|
1135
|
-
|
1136
|
-
|
1137
|
-
# Test case where Generic comes before BaseModel (should still work)
|
1138
|
-
class AltGenericResultModel[T](BaseModel):
|
1139
|
-
content: T
|
1140
|
-
metadata: str
|
1141
|
-
|
1142
|
-
|
1143
|
-
def function_with_alt_generic_return[T](data: T) -> AltGenericResultModel[T]:
|
1144
|
-
return AltGenericResultModel[T](content=data, metadata="Alt Generic metadata")
|
1145
|
-
|
1146
|
-
|
1147
|
-
def test_deserialize_alt_order_generic_result():
|
1148
|
-
"""Test deserialization when Generic is inherited before BaseModel."""
|
1149
|
-
original_data = SimpleData(value=456)
|
1150
|
-
args = [original_data]
|
1151
|
-
kwargs = {}
|
1152
|
-
|
1153
|
-
result = function_with_alt_generic_return(*args, **kwargs)
|
1154
|
-
serialized_result = serialize_result(function_with_alt_generic_return, result)
|
1155
|
-
|
1156
|
-
deserialized = deserialize_result(
|
1157
|
-
function_with_alt_generic_return, serialized_result, None, args, kwargs
|
1158
|
-
)
|
1159
|
-
|
1160
|
-
assert isinstance(deserialized, AltGenericResultModel)
|
1161
|
-
assert isinstance(deserialized.content, SimpleData)
|
1162
|
-
assert deserialized.content.value == 456
|
1163
|
-
assert deserialized.metadata == "Alt Generic metadata"
|
1164
|
-
|
1165
|
-
|
1166
|
-
def dataclass_function(data: SampleDataClass) -> SampleDataClass:
|
1167
|
-
return SampleDataClass(name=data.name.upper(), value=data.value + 1)
|
1168
|
-
|
1169
|
-
|
1170
|
-
def test_dataclass_serialization_roundtrip():
|
1171
|
-
item = SampleDataClass(name="foo", value=1)
|
1172
|
-
args = [item]
|
1173
|
-
kwargs = {}
|
1174
|
-
|
1175
|
-
serialized_args, serialized_kwargs = serialize_args(
|
1176
|
-
dataclass_function, args, kwargs
|
1177
|
-
)
|
1178
|
-
assert isinstance(serialized_args[0], dict)
|
1179
|
-
|
1180
|
-
deserialized_args, deserialized_kwargs = deserialize_args(
|
1181
|
-
dataclass_function, serialized_args, serialized_kwargs
|
1182
|
-
)
|
1183
|
-
assert isinstance(deserialized_args[0], SampleDataClass)
|
1184
|
-
|
1185
|
-
result = dataclass_function(*deserialized_args, **deserialized_kwargs)
|
1186
|
-
|
1187
|
-
serialized_result = serialize_result(dataclass_function, result)
|
1188
|
-
deserialized_result = deserialize_result(dataclass_function, serialized_result)
|
1189
|
-
|
1190
|
-
assert isinstance(deserialized_result, SampleDataClass)
|
1191
|
-
assert deserialized_result.name == "FOO"
|
1192
|
-
assert deserialized_result.value == 2
|
1193
|
-
|
1194
|
-
|
1195
|
-
def test_serialize_deserialize_dataclass_value():
|
1196
|
-
item = SampleDataClass(name="bar", value=5)
|
1197
|
-
serialized = serialize_value(item)
|
1198
|
-
assert serialized == {"name": "bar", "value": 5}
|
1199
|
-
|
1200
|
-
deserialized = deserialize_value(serialized, SampleDataClass)
|
1201
|
-
assert isinstance(deserialized, SampleDataClass)
|
1202
|
-
assert deserialized.name == "bar"
|
1203
|
-
assert deserialized.value == 5
|