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.
Files changed (76) hide show
  1. planar/ai/agent.py +2 -1
  2. planar/ai/agent_base.py +24 -5
  3. planar/ai/state.py +17 -0
  4. planar/app.py +18 -1
  5. planar/data/connection.py +108 -0
  6. planar/data/dataset.py +11 -104
  7. planar/data/utils.py +89 -0
  8. planar/db/alembic/env.py +25 -1
  9. planar/files/storage/azure_blob.py +1 -1
  10. planar/registry_items.py +2 -0
  11. planar/routers/dataset_router.py +213 -0
  12. planar/routers/info.py +79 -36
  13. planar/routers/models.py +1 -0
  14. planar/routers/workflow.py +2 -0
  15. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  16. planar/security/authorization.py +31 -3
  17. planar/security/default_policies.cedar +25 -0
  18. planar/testing/fixtures.py +34 -1
  19. planar/testing/planar_test_client.py +1 -1
  20. planar/workflows/decorators.py +2 -1
  21. planar/workflows/wrappers.py +1 -0
  22. {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
  23. {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/RECORD +25 -72
  24. {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/WHEEL +1 -1
  25. planar/ai/test_agent_serialization.py +0 -229
  26. planar/ai/test_agent_tool_step_display.py +0 -78
  27. planar/data/test_dataset.py +0 -354
  28. planar/files/storage/test_azure_blob.py +0 -435
  29. planar/files/storage/test_local_directory.py +0 -162
  30. planar/files/storage/test_s3.py +0 -299
  31. planar/files/test_files.py +0 -282
  32. planar/human/test_human.py +0 -385
  33. planar/logging/test_formatter.py +0 -327
  34. planar/modeling/mixins/test_auditable.py +0 -97
  35. planar/modeling/mixins/test_timestamp.py +0 -134
  36. planar/modeling/mixins/test_uuid_primary_key.py +0 -52
  37. planar/routers/test_agents_router.py +0 -174
  38. planar/routers/test_files_router.py +0 -49
  39. planar/routers/test_object_config_router.py +0 -367
  40. planar/routers/test_routes_security.py +0 -168
  41. planar/routers/test_rule_router.py +0 -470
  42. planar/routers/test_workflow_router.py +0 -539
  43. planar/rules/test_data/account_dormancy_management.json +0 -223
  44. planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
  45. planar/rules/test_data/applicant_risk_assessment.json +0 -435
  46. planar/rules/test_data/booking_fraud_detection.json +0 -407
  47. planar/rules/test_data/cellular_data_rollover_system.json +0 -258
  48. planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
  49. planar/rules/test_data/customer_lifetime_value.json +0 -143
  50. planar/rules/test_data/import_duties_calculator.json +0 -289
  51. planar/rules/test_data/insurance_prior_authorization.json +0 -443
  52. planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
  53. planar/rules/test_data/order_consolidation_system.json +0 -375
  54. planar/rules/test_data/portfolio_risk_monitor.json +0 -471
  55. planar/rules/test_data/supply_chain_risk.json +0 -253
  56. planar/rules/test_data/warehouse_cross_docking.json +0 -237
  57. planar/rules/test_rules.py +0 -1494
  58. planar/security/tests/test_auth_middleware.py +0 -162
  59. planar/security/tests/test_authorization_context.py +0 -78
  60. planar/security/tests/test_cedar_basics.py +0 -41
  61. planar/security/tests/test_cedar_policies.py +0 -158
  62. planar/security/tests/test_jwt_principal_context.py +0 -179
  63. planar/test_app.py +0 -142
  64. planar/test_cli.py +0 -394
  65. planar/test_config.py +0 -515
  66. planar/test_object_config.py +0 -527
  67. planar/test_object_registry.py +0 -14
  68. planar/test_sqlalchemy.py +0 -193
  69. planar/test_utils.py +0 -105
  70. planar/testing/test_memory_storage.py +0 -143
  71. planar/workflows/test_concurrency_detection.py +0 -120
  72. planar/workflows/test_lock_timeout.py +0 -140
  73. planar/workflows/test_serialization.py +0 -1203
  74. planar/workflows/test_suspend_deserialization.py +0 -231
  75. planar/workflows/test_workflow.py +0 -2005
  76. {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