planar 0.10.0__py3-none-any.whl → 0.12.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 (73) hide show
  1. planar/app.py +26 -6
  2. planar/cli.py +26 -0
  3. planar/data/__init__.py +1 -0
  4. planar/data/config.py +12 -1
  5. planar/data/connection.py +89 -4
  6. planar/data/dataset.py +13 -7
  7. planar/data/utils.py +145 -25
  8. planar/db/alembic/env.py +68 -57
  9. planar/db/alembic.ini +1 -1
  10. planar/files/storage/config.py +7 -1
  11. planar/routers/dataset_router.py +5 -1
  12. planar/routers/info.py +79 -36
  13. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  14. planar/testing/fixtures.py +7 -4
  15. planar/testing/planar_test_client.py +8 -0
  16. planar/version.py +27 -0
  17. planar-0.12.0.dist-info/METADATA +202 -0
  18. {planar-0.10.0.dist-info → planar-0.12.0.dist-info}/RECORD +20 -71
  19. planar/ai/test_agent_serialization.py +0 -229
  20. planar/ai/test_agent_tool_step_display.py +0 -78
  21. planar/data/test_dataset.py +0 -358
  22. planar/files/storage/test_azure_blob.py +0 -435
  23. planar/files/storage/test_local_directory.py +0 -162
  24. planar/files/storage/test_s3.py +0 -299
  25. planar/files/test_files.py +0 -282
  26. planar/human/test_human.py +0 -385
  27. planar/logging/test_formatter.py +0 -327
  28. planar/modeling/mixins/test_auditable.py +0 -97
  29. planar/modeling/mixins/test_timestamp.py +0 -134
  30. planar/modeling/mixins/test_uuid_primary_key.py +0 -52
  31. planar/routers/test_agents_router.py +0 -174
  32. planar/routers/test_dataset_router.py +0 -429
  33. planar/routers/test_files_router.py +0 -49
  34. planar/routers/test_object_config_router.py +0 -367
  35. planar/routers/test_routes_security.py +0 -168
  36. planar/routers/test_rule_router.py +0 -470
  37. planar/routers/test_workflow_router.py +0 -564
  38. planar/rules/test_data/account_dormancy_management.json +0 -223
  39. planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
  40. planar/rules/test_data/applicant_risk_assessment.json +0 -435
  41. planar/rules/test_data/booking_fraud_detection.json +0 -407
  42. planar/rules/test_data/cellular_data_rollover_system.json +0 -258
  43. planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
  44. planar/rules/test_data/customer_lifetime_value.json +0 -143
  45. planar/rules/test_data/import_duties_calculator.json +0 -289
  46. planar/rules/test_data/insurance_prior_authorization.json +0 -443
  47. planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
  48. planar/rules/test_data/order_consolidation_system.json +0 -375
  49. planar/rules/test_data/portfolio_risk_monitor.json +0 -471
  50. planar/rules/test_data/supply_chain_risk.json +0 -253
  51. planar/rules/test_data/warehouse_cross_docking.json +0 -237
  52. planar/rules/test_rules.py +0 -1494
  53. planar/security/tests/test_auth_middleware.py +0 -162
  54. planar/security/tests/test_authorization_context.py +0 -78
  55. planar/security/tests/test_cedar_basics.py +0 -41
  56. planar/security/tests/test_cedar_policies.py +0 -158
  57. planar/security/tests/test_jwt_principal_context.py +0 -179
  58. planar/test_app.py +0 -142
  59. planar/test_cli.py +0 -394
  60. planar/test_config.py +0 -515
  61. planar/test_object_config.py +0 -527
  62. planar/test_object_registry.py +0 -14
  63. planar/test_sqlalchemy.py +0 -193
  64. planar/test_utils.py +0 -105
  65. planar/testing/test_memory_storage.py +0 -143
  66. planar/workflows/test_concurrency_detection.py +0 -120
  67. planar/workflows/test_lock_timeout.py +0 -140
  68. planar/workflows/test_serialization.py +0 -1203
  69. planar/workflows/test_suspend_deserialization.py +0 -231
  70. planar/workflows/test_workflow.py +0 -2005
  71. planar-0.10.0.dist-info/METADATA +0 -323
  72. {planar-0.10.0.dist-info → planar-0.12.0.dist-info}/WHEEL +0 -0
  73. {planar-0.10.0.dist-info → planar-0.12.0.dist-info}/entry_points.txt +0 -0
@@ -1,327 +0,0 @@
1
- import json
2
- import logging
3
- import re
4
- from datetime import datetime
5
- from decimal import Decimal
6
- from uuid import UUID, uuid4
7
-
8
- from pydantic import BaseModel
9
-
10
- from planar.logging.formatter import StructuredFormatter, dictionary_print, json_print
11
-
12
-
13
- class SampleModel(BaseModel):
14
- name: str
15
- value: int
16
-
17
-
18
- class TestJsonPrint:
19
- def test_json_print_simple_values(self):
20
- """Test json_print with simple values"""
21
- assert json_print("test") == '"test"'
22
- assert json_print(42) == "42"
23
- assert json_print(True) == "true"
24
- assert json_print(None) == "null"
25
-
26
- def test_json_print_dict(self):
27
- """Test json_print with dictionary"""
28
- data = {"key": "value", "number": 42}
29
- result = json_print(data)
30
- assert json.loads(result) == data
31
-
32
- def test_json_print_list_without_colors(self):
33
- """Test json_print with list without colors - should produce valid JSON"""
34
- data = ["item1", "item2", {"nested": "value"}]
35
- result = json_print(data, use_colors=False)
36
- # Should be valid JSON
37
- parsed = json.loads(result)
38
- assert parsed == data
39
-
40
- def test_json_print_list_with_colors(self):
41
- """Test json_print with list with colors - should produce valid JSON with ANSI codes"""
42
- data = ["item1", "item2", {"nested": "value"}]
43
- result = json_print(data, use_colors=True)
44
- # Should contain ANSI escape codes but when stripped should be valid JSON
45
- # Remove ANSI codes to check JSON validity
46
- import re
47
-
48
- ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
49
- clean_result = ansi_escape.sub("", result)
50
- parsed = json.loads(clean_result)
51
- assert parsed == data
52
- # Should contain color codes
53
- assert "\x1b[" in result
54
-
55
- def test_json_print_nested_structures(self):
56
- """Test json_print with deeply nested structures"""
57
- data = {
58
- "messages": [
59
- {"content": "hello", "role": "user"},
60
- {"content": "world", "role": "assistant"},
61
- ],
62
- "tools": [
63
- {"name": "tool1", "params": {"key": "value"}},
64
- {"name": "tool2", "params": {"num": 42}},
65
- ],
66
- }
67
- result = json_print(data, use_colors=False)
68
- parsed = json.loads(result)
69
- assert parsed == data
70
-
71
- def test_json_print_pydantic_model(self):
72
- """Test json_print with Pydantic models"""
73
- model = SampleModel(name="test", value=42)
74
- result = json_print(model, use_colors=False)
75
- parsed = json.loads(result)
76
- assert parsed == {"name": "test", "value": 42}
77
-
78
- def test_json_print_custom_objects(self):
79
- """Test json_print with custom objects that need string conversion"""
80
-
81
- class CustomObject:
82
- def __str__(self):
83
- return "custom_object"
84
-
85
- data = {"obj": CustomObject()}
86
- result = json_print(data, use_colors=False)
87
- parsed = json.loads(result)
88
- assert parsed == {"obj": "custom_object"}
89
-
90
- def test_json_print_no_ansi_in_escaped_strings(self):
91
- """Test that ANSI codes don't get escaped in JSON strings"""
92
- data = ["message1", "message2", {"key": "value"}]
93
- result = json_print(data, use_colors=False)
94
- # Should not contain escaped ANSI codes like \u001b
95
- assert "\\u001b" not in result
96
- # Should be valid JSON
97
- parsed = json.loads(result)
98
- assert parsed == data
99
-
100
- def test_json_print_complex_types(self):
101
- """Test json_print with datetime, uuid, and decimal types"""
102
- test_datetime = datetime(2023, 12, 25, 10, 30, 45)
103
- test_uuid = uuid4()
104
- test_decimal = Decimal("123.45")
105
-
106
- # Test complex data structure with these types
107
- data = {
108
- "timestamp": test_datetime,
109
- "id": test_uuid,
110
- "amount": test_decimal,
111
- "nested": {
112
- "dates": [test_datetime, datetime(2024, 1, 1)],
113
- "ids": [test_uuid, uuid4()],
114
- "values": [test_decimal, Decimal("67.89")],
115
- },
116
- }
117
-
118
- # Test without colors
119
- result = json_print(data, use_colors=False)
120
-
121
- # Should be valid JSON
122
- parsed = json.loads(result)
123
-
124
- # All complex types should be converted to strings
125
- assert isinstance(parsed["timestamp"], str)
126
- assert isinstance(parsed["id"], str)
127
- assert isinstance(parsed["amount"], str)
128
- assert isinstance(parsed["nested"]["dates"][0], str)
129
- assert isinstance(parsed["nested"]["ids"][0], str)
130
- assert isinstance(parsed["nested"]["values"][0], str)
131
-
132
- # Verify string representations contain expected content
133
- assert "2023-12-25" in parsed["timestamp"]
134
- assert str(test_uuid) == parsed["id"]
135
- assert "123.45" in parsed["amount"]
136
-
137
- # Test with colors - should also work and produce valid JSON when stripped
138
- result_colored = json_print(data, use_colors=True)
139
- assert "\x1b[" in result_colored # Should contain ANSI codes
140
-
141
- ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
142
- clean_result = ansi_escape.sub("", result_colored)
143
- parsed_colored = json.loads(clean_result)
144
- assert parsed_colored == parsed # Should be same as non-colored version
145
-
146
- def test_json_print_with_base_model(self):
147
- """Test json_print with BaseModel using complex data"""
148
-
149
- class TestModel(BaseModel):
150
- name: str
151
- date: datetime
152
- uuid_val: UUID
153
- decimal_val: Decimal
154
-
155
- model = TestModel(
156
- name="test",
157
- date=datetime(2023, 12, 25, 10, 30, 45),
158
- uuid_val=uuid4(),
159
- decimal_val=Decimal("123.45"),
160
- )
161
- result = json_print(model, use_colors=False)
162
- parsed = json.loads(result)
163
- assert parsed == {
164
- "name": "test",
165
- "date": "2023-12-25T10:30:45",
166
- "uuid_val": str(model.uuid_val),
167
- "decimal_val": "123.45",
168
- }
169
-
170
-
171
- class TestDictionaryPrint:
172
- def test_dictionary_print_simple(self):
173
- """Test dictionary_print with simple values"""
174
- data = {"key": "value", "number": 42}
175
- result = dictionary_print(data, use_colors=False)
176
- assert 'key="value"' in result
177
- assert "number=42" in result
178
-
179
- def test_dictionary_print_with_lists(self):
180
- """Test dictionary_print with lists"""
181
- data = {"items": ["a", "b", "c"], "count": 3}
182
- result = dictionary_print(data, use_colors=False)
183
- assert 'items=["a","b","c"]' in result or 'items=["a", "b", "c"]' in result
184
- assert "count=3" in result
185
-
186
- def test_dictionary_print_with_colors(self):
187
- """Test dictionary_print with colors enabled"""
188
- data = {"key": "value"}
189
- result = dictionary_print(data, use_colors=True)
190
- # Should contain ANSI codes
191
- assert "\x1b[" in result
192
- # Should still contain the key-value pair
193
- assert "key=" in result
194
-
195
-
196
- class TestStructuredFormatter:
197
- def test_structured_formatter_with_extra_attrs(self):
198
- """Test StructuredFormatter with extra attributes"""
199
- formatter = StructuredFormatter(use_colors=False)
200
-
201
- record = logging.LogRecord(
202
- name="test.logger",
203
- level=logging.INFO,
204
- pathname="test.py",
205
- lineno=1,
206
- msg="test message",
207
- args=(),
208
- exc_info=None,
209
- )
210
-
211
- # Add extra attributes (simulating what PlanarLogger does)
212
- record.__dict__.update(
213
- {
214
- "$workflow_id": "test-workflow",
215
- "$step_id": 42,
216
- "$messages": ["msg1", "msg2"],
217
- }
218
- )
219
-
220
- result = formatter.format(record)
221
- assert "workflow_id=" in result
222
- assert "step_id=42" in result
223
- assert "messages=" in result
224
- # Should not contain escaped ANSI codes
225
- assert "\\u001b" not in result
226
-
227
- def test_structured_formatter_with_colors(self):
228
- """Test StructuredFormatter with colors enabled"""
229
- formatter = StructuredFormatter(use_colors=True)
230
-
231
- record = logging.LogRecord(
232
- name="test.logger",
233
- level=logging.INFO,
234
- pathname="test.py",
235
- lineno=1,
236
- msg="test message",
237
- args=(),
238
- exc_info=None,
239
- )
240
-
241
- result = formatter.format(record)
242
- # Should contain ANSI color codes
243
- assert "\x1b[" in result
244
-
245
- def test_structured_formatter_complex_data(self):
246
- """Test StructuredFormatter with complex nested data like the original issue"""
247
- formatter = StructuredFormatter(use_colors=True)
248
-
249
- record = logging.LogRecord(
250
- name="planar.ai.test_agent",
251
- level=logging.INFO,
252
- pathname="test_agent.py",
253
- lineno=188,
254
- msg="patched_complete",
255
- args=(),
256
- exc_info=None,
257
- )
258
-
259
- # Simulate the complex data from the original issue
260
- record.__dict__.update(
261
- {
262
- "$messages": [
263
- {"content": "Use tools to solve the problem"},
264
- {"content": "Problem: complex problem", "files": []},
265
- {
266
- "content": None,
267
- "tool_calls": [
268
- {
269
- "id": "call_1",
270
- "name": "tool1",
271
- "arguments": {"param": "test_param"},
272
- }
273
- ],
274
- },
275
- {"content": "Tool 1 result: test_param", "tool_call_id": "call_1"},
276
- ],
277
- "$tools": [
278
- {
279
- "name": "tool1",
280
- "description": "Test tool 1",
281
- "parameters": {
282
- "type": "object",
283
- "properties": {"param": {"type": "string"}},
284
- },
285
- },
286
- {
287
- "name": "tool2",
288
- "description": "Test tool 2",
289
- "parameters": {
290
- "type": "object",
291
- "properties": {"num": {"type": "integer"}},
292
- },
293
- },
294
- ],
295
- "$workflow_id": "test-workflow-id",
296
- "$step_id": 4,
297
- }
298
- )
299
-
300
- result = formatter.format(record)
301
-
302
- # Should contain the message
303
- assert "patched_complete" in result
304
- assert "planar.ai.test_agent" in result
305
-
306
- # Should contain the extra attributes
307
- assert "messages=" in result
308
- assert "tools=" in result
309
- assert "workflow_id=" in result
310
- assert "step_id=" in result
311
-
312
- # Most importantly: should NOT contain escaped ANSI codes
313
- assert "\\u001b" not in result
314
-
315
- # Should contain actual ANSI codes (for colors)
316
- assert "\x1b[" in result
317
-
318
- # Verify the fix - the data should be properly formatted JSON with colors
319
- # Extract the JSON parts and verify they're valid when ANSI codes are stripped
320
- import re
321
-
322
- ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
323
- clean_result = ansi_escape.sub("", result)
324
-
325
- # The messages should be valid JSON when extracted
326
- assert '"content": "Use tools to solve the problem"' in clean_result
327
- assert '"content": "Problem: complex problem"' in clean_result
@@ -1,97 +0,0 @@
1
- import pytest
2
- from sqlmodel import Field, SQLModel
3
-
4
- from planar.db import PlanarSession, new_session
5
- from planar.modeling.mixins.auditable import AuditableMixin
6
- from planar.security.auth_context import (
7
- Principal,
8
- as_principal,
9
- get_current_principal,
10
- )
11
-
12
- TEST_PRINCIPAL = Principal(
13
- sub="test_user",
14
- iss="test",
15
- exp=1000,
16
- iat=1000,
17
- sid="test",
18
- jti="test",
19
- org_id="test",
20
- org_name="test",
21
- user_first_name="test",
22
- user_last_name="test",
23
- user_email="test@test.com",
24
- role="test",
25
- permissions=["test"],
26
- extra_claims={"test": "test"},
27
- )
28
-
29
-
30
- class TestAuditableModel(AuditableMixin, SQLModel, table=True):
31
- """Test model using AuditableMixin."""
32
-
33
- __test__ = False
34
-
35
- id: int | None = Field(default=None, primary_key=True)
36
- name: str = Field()
37
-
38
-
39
- @pytest.fixture
40
- async def session(tmp_db_engine):
41
- """Create a database session."""
42
-
43
- async with new_session(tmp_db_engine) as session:
44
- await (await session.connection()).run_sync(SQLModel.metadata.create_all)
45
- yield session
46
-
47
-
48
- def test_auditable_mixin_has_audit_fields():
49
- """Test that AuditableMixin provides default audit fields."""
50
- model = TestAuditableModel(name="test")
51
-
52
- assert hasattr(model, "created_by")
53
- assert hasattr(model, "updated_by")
54
- assert model.created_by == "system"
55
- assert model.updated_by == "system"
56
-
57
-
58
- async def test_auditable_mixin_sets_values_on_insert(session: PlanarSession):
59
- """Test that audit fields are set from SecurityContext on insert."""
60
- with as_principal(TEST_PRINCIPAL):
61
- model = TestAuditableModel(name="test_insert")
62
- session.add(model)
63
- await session.commit()
64
-
65
- # Refresh to get the updated values
66
- await session.refresh(model)
67
-
68
- assert model.created_by == "test@test.com"
69
- assert model.updated_by == "test@test.com"
70
-
71
-
72
- async def test_auditable_mixin_sets_updated_by_on_update(session: PlanarSession):
73
- """Test that updated_by is set from SecurityContext on update."""
74
- # First insert with initial user
75
- with as_principal(TEST_PRINCIPAL):
76
- model = TestAuditableModel(name="test_update")
77
- session.add(model)
78
- await session.commit()
79
- await session.refresh(model)
80
-
81
- assert model.created_by == "test@test.com"
82
- assert model.updated_by == "test@test.com"
83
-
84
- # Now update with different user
85
- updating_principal = TEST_PRINCIPAL.model_copy(
86
- update={"user_email": "updating@test.com"}
87
- )
88
- with as_principal(updating_principal):
89
- assert get_current_principal() == updating_principal
90
- model.name = "updated_name"
91
- session.add(model)
92
- await session.commit()
93
- await session.refresh(model)
94
-
95
- # created_by should remain the same, updated_by should change
96
- assert model.created_by == "test@test.com"
97
- assert model.updated_by == "updating@test.com"
@@ -1,134 +0,0 @@
1
- import asyncio
2
- from datetime import timedelta
3
-
4
- from sqlalchemy.ext.asyncio import AsyncEngine
5
- from sqlmodel import select
6
-
7
- from planar.db import new_session
8
- from planar.modeling.mixins import TimestampMixin
9
- from planar.modeling.orm.planar_base_entity import PlanarBaseEntity
10
- from planar.utils import utc_now
11
-
12
-
13
- class TimestampTestModel(TimestampMixin, PlanarBaseEntity, table=True):
14
- """Test model that uses the TimestampMixin."""
15
-
16
- name: str
17
- value: int = 0
18
-
19
-
20
- async def test_timestamp_fields_set_on_creation(tmp_db_engine: AsyncEngine):
21
- """Test that created_at and updated_at are set when a model is created."""
22
- # Record time before the operation
23
- before_creation = utc_now()
24
-
25
- # Create and insert model
26
- model_id = None
27
- async with new_session(tmp_db_engine) as session:
28
- model = TimestampTestModel(name="test_item", value=42)
29
- session.add(model)
30
- await session.commit()
31
- model_id = model.id
32
-
33
- # Record time after the operation
34
- after_creation = utc_now()
35
-
36
- # Fetch model to verify timestamps
37
- async with new_session(tmp_db_engine) as session:
38
- created_model = (
39
- await session.exec(
40
- select(TimestampTestModel).where(TimestampTestModel.id == model_id)
41
- )
42
- ).one()
43
-
44
- # Verify created_at is set and within the expected time range
45
- # and that it equals updated_at
46
- assert created_model.created_at is not None
47
- assert before_creation <= created_model.created_at <= after_creation
48
- assert created_model.created_at == created_model.updated_at
49
-
50
-
51
- async def test_updated_at_reflects_changes(tmp_db_engine: AsyncEngine):
52
- """Test that updated_at is updated when a model is modified."""
53
- # Create and insert model
54
- model_id = None
55
- async with new_session(tmp_db_engine) as session:
56
- model = TimestampTestModel(name="test_item", value=42)
57
- session.add(model)
58
- await session.commit()
59
- model_id = model.id
60
-
61
- # Get initial created_at and updated_at values
62
- initial_model = (
63
- await session.exec(
64
- select(TimestampTestModel).where(TimestampTestModel.id == model_id)
65
- )
66
- ).one()
67
- await session.commit()
68
- initial_created_at = initial_model.created_at
69
- initial_updated_at = initial_model.updated_at
70
-
71
- # Wait a moment to ensure timestamp will be different
72
- await asyncio.sleep(0.01)
73
-
74
- # Record time before update
75
- before_update = utc_now()
76
-
77
- # Update the model
78
- async with new_session(tmp_db_engine) as session:
79
- model_to_update = (
80
- await session.exec(
81
- select(TimestampTestModel).where(TimestampTestModel.id == model_id)
82
- )
83
- ).one()
84
- model_to_update.value = 99
85
- await session.commit()
86
-
87
- # Record time after update
88
- after_update = utc_now()
89
-
90
- # Verify the timestamps
91
- async with new_session(tmp_db_engine) as session:
92
- updated_model = (
93
- await session.exec(
94
- select(TimestampTestModel).where(TimestampTestModel.id == model_id)
95
- )
96
- ).one()
97
- await session.commit()
98
-
99
- # created_at should not change
100
- assert updated_model.created_at == initial_created_at
101
-
102
- # updated_at should be newer than before
103
- assert updated_model.updated_at is not None
104
- assert initial_updated_at is not None
105
- assert updated_model.updated_at > initial_updated_at
106
- assert before_update <= updated_model.updated_at <= after_update
107
-
108
-
109
- async def test_timestamp_init_with_explicit_values():
110
- """Test initializing a model with explicit timestamp values."""
111
- # Create a specific timestamp
112
- now = utc_now()
113
- past = now - timedelta(days=1)
114
-
115
- # Initialize model with explicit timestamps
116
- model = TimestampTestModel(
117
- name="test_explicit_timestamps",
118
- value=200,
119
- created_at=past,
120
- updated_at=now,
121
- )
122
-
123
- # Verify timestamps match what we provided
124
- assert model.created_at == past
125
- assert model.updated_at == now
126
-
127
- # Initialize model with only created_at
128
- model2 = TimestampTestModel(
129
- name="test_partial_timestamps", value=300, created_at=past
130
- )
131
-
132
- # Verify updated_at equals created_at when only created_at is provided
133
- assert model2.created_at == past
134
- assert model2.updated_at == past
@@ -1,52 +0,0 @@
1
- from uuid import UUID
2
-
3
- from sqlmodel import Field
4
-
5
- from planar.db import PlanarSession
6
- from planar.modeling.mixins.uuid_primary_key import UUIDPrimaryKeyMixin
7
- from planar.modeling.orm.planar_base_entity import PlanarBaseEntity
8
-
9
-
10
- class UUIDModelTest(PlanarBaseEntity, UUIDPrimaryKeyMixin, table=True):
11
- """Test model using UUIDPrimaryKeyMixin."""
12
-
13
- name: str = Field()
14
-
15
-
16
- def test_uuid_primary_key_mixin_creates_uuid_id():
17
- """Test that UUIDPrimaryKeyMixin provides a UUID id field."""
18
- model = UUIDModelTest(name="test")
19
-
20
- assert hasattr(model, "id")
21
- assert isinstance(model.id, UUID)
22
- assert model.id is not None
23
-
24
-
25
- def test_uuid_primary_key_mixin_allows_custom_id():
26
- """Test that a custom UUID can be provided."""
27
- custom_uuid = UUID("12345678-1234-5678-1234-123456789abc")
28
- model = UUIDModelTest(id=custom_uuid, name="test")
29
-
30
- assert model.id == custom_uuid
31
-
32
-
33
- async def test_uuid_primary_key_mixin_is_primary_key(session: PlanarSession):
34
- """Test that the id field works as a primary key."""
35
- model1 = UUIDModelTest(name="test1")
36
- model2 = UUIDModelTest(name="test2")
37
-
38
- session.add(model1)
39
- session.add(model2)
40
- await session.commit()
41
-
42
- # Both should have different IDs
43
- assert model1.id != model2.id
44
-
45
- # Both should be retrievable by their IDs
46
- retrieved1 = await session.get(UUIDModelTest, model1.id)
47
- retrieved2 = await session.get(UUIDModelTest, model2.id)
48
-
49
- assert retrieved1 is not None
50
- assert retrieved1.name == "test1"
51
- assert retrieved2 is not None
52
- assert retrieved2.name == "test2"