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,470 +0,0 @@
1
- from decimal import Decimal
2
-
3
- import pytest
4
- from pydantic import BaseModel
5
-
6
- from planar.app import PlanarApp
7
- from planar.config import sqlite_config
8
- from planar.rules import rule
9
- from planar.testing.planar_test_client import PlanarTestClient
10
-
11
-
12
- class ExpenseRuleInput(BaseModel):
13
- title: str
14
- amount: float
15
- description: str
16
- status: str
17
- category: str
18
-
19
-
20
- class RuleOutput(BaseModel):
21
- reason: str
22
- approved: bool
23
-
24
-
25
- class TransactionVolumeRow(BaseModel):
26
- period: str
27
- country: str
28
- currency: str
29
- completed_count: int
30
- rejected_count: int
31
-
32
-
33
- class TransactionVolume(BaseModel):
34
- rows: list[TransactionVolumeRow]
35
- total_completed_count: int
36
- total_rejected_count: int
37
-
38
-
39
- class PricingInput(BaseModel):
40
- rows: list[TransactionVolumeRow]
41
-
42
-
43
- class TransactionPricingLine(TransactionVolumeRow):
44
- completed_price_per_transaction_usd: Decimal
45
- rejected_price_per_transaction_usd: Decimal
46
-
47
-
48
- class PricingRuleOutput(BaseModel):
49
- line_items: list[TransactionPricingLine]
50
-
51
-
52
- class PricingRuleOutputWrongType(BaseModel):
53
- line_items: list[TransactionPricingLine]
54
- some_other_field: str
55
-
56
-
57
- @rule(description="Complex business rule")
58
- def complex_business_rule(input: ExpenseRuleInput) -> RuleOutput:
59
- """
60
- A complex business rule that determines if the expense should be approved
61
- """
62
- # input and output must be json serializable objects for the zen / gorules lib to work
63
- return RuleOutput(reason="The widgets look fantastic", approved=True)
64
-
65
-
66
- @rule(description="Calculates fees based on tiered, total transaction volume.")
67
- def pricing_rule(
68
- input: PricingInput,
69
- ) -> PricingRuleOutput:
70
- """
71
- Calculates fees based on country, currency, and tiered volume.
72
- """
73
- return PricingRuleOutput(line_items=[])
74
-
75
-
76
- @rule(
77
- description="Calculates fees based on tiered, total transaction volume with wrong type"
78
- )
79
- def pricing_rule_with_wrong_type(
80
- input: PricingInput,
81
- ) -> PricingRuleOutputWrongType:
82
- return PricingRuleOutputWrongType(line_items=[], some_other_field="test")
83
-
84
-
85
- @pytest.fixture(name="app")
86
- def app_fixture(tmp_db_path: str):
87
- app = PlanarApp(
88
- config=sqlite_config(tmp_db_path),
89
- title="Test app for agent router",
90
- description="Testing agent endpoints",
91
- )
92
-
93
- app.register_rule(complex_business_rule)
94
- app.register_rule(pricing_rule_with_wrong_type)
95
- app.register_rule(pricing_rule)
96
- return app
97
-
98
-
99
- EXPENSE_RULE_JDM = {
100
- "nodes": [
101
- {
102
- "id": "7e51efb8-7463-4775-ad69-180442a34444",
103
- "type": "inputNode",
104
- "name": "Input",
105
- "content": {
106
- "schema": '{"properties": {"title": {"title": "Title", "type": "string"}, "amount": {"title": "Amount", "type": "number"}, "description": {"title": "Description", "type": "string"}, "status": {"title": "Status", "type": "string"}, "category": {"title": "Category", "type": "string"}}, "required": ["title", "amount", "description", "status", "category"], "title": "ExpenseRuleInput", "type": "object"}'
107
- },
108
- "position": {"x": 100, "y": 100},
109
- },
110
- {
111
- "id": "abf8c265-da42-4b81-b7bf-349d3e248294",
112
- "type": "decisionTableNode",
113
- "name": "decisionTable1",
114
- "content": {
115
- "hitPolicy": "first",
116
- "rules": [
117
- {
118
- "_id": "9fc59e78-58be-412b-9d2b-79c22bcfefe4",
119
- "2a5ac809-24db-431b-a228-7bc318cd0a3f": "",
120
- "25f2e0b6-c02b-43a2-a9cd-d40e5c1ca709": '"default value"',
121
- "4b09e532-bb42-453b-9c00-26af89f70a03": "true",
122
- }
123
- ],
124
- "inputs": [
125
- {
126
- "id": "2a5ac809-24db-431b-a228-7bc318cd0a3f",
127
- "name": "Input",
128
- "field": "",
129
- }
130
- ],
131
- "outputs": [
132
- {
133
- "id": "25f2e0b6-c02b-43a2-a9cd-d40e5c1ca709",
134
- "field": "reason",
135
- "name": "reason",
136
- },
137
- {
138
- "id": "4b09e532-bb42-453b-9c00-26af89f70a03",
139
- "field": "approved",
140
- "name": "approved",
141
- },
142
- ],
143
- "passThrough": True,
144
- "passThorough": False,
145
- "inputField": None,
146
- "outputPath": None,
147
- "executionMode": "single",
148
- },
149
- "position": {"x": 405, "y": 120},
150
- },
151
- {
152
- "id": "40abc689-6e0e-40ee-bc76-df51065e6ff5",
153
- "type": "outputNode",
154
- "name": "Output",
155
- "content": {
156
- "schema": '{"properties": {"reason": {"title": "Reason", "type": "string"}, "approved": {"title": "Approved", "type": "boolean"}}, "required": ["reason", "approved"], "title": "RuleOutput", "type": "object"}'
157
- },
158
- "position": {"x": 885, "y": 130},
159
- },
160
- {
161
- "id": "a5385f35-5ba7-4cbf-a5b8-f87bca6fd95c",
162
- "type": "expressionNode",
163
- "name": "expression1",
164
- "content": {
165
- "expressions": [],
166
- "passThrough": True,
167
- "inputField": None,
168
- "outputPath": None,
169
- "executionMode": "single",
170
- },
171
- "position": {"x": 590, "y": 350},
172
- },
173
- {
174
- "id": "0689381e-0650-4ba5-b4ba-1a1800f035ca",
175
- "type": "decisionTableNode",
176
- "name": "decisionTable2",
177
- "content": {
178
- "hitPolicy": "first",
179
- "rules": [],
180
- "inputs": [
181
- {
182
- "id": "4caf4578-a643-4a3c-bc6e-2bdf8559e601",
183
- "name": "Input",
184
- "field": "",
185
- }
186
- ],
187
- "outputs": [
188
- {
189
- "id": "4ec07a83-70ca-46ec-aee7-331c44a8da76",
190
- "field": "output",
191
- "name": "Output",
192
- }
193
- ],
194
- "passThrough": True,
195
- "passThorough": None,
196
- "inputField": None,
197
- "outputPath": None,
198
- "executionMode": "single",
199
- },
200
- "position": {"x": 885, "y": 500},
201
- },
202
- ],
203
- "edges": [
204
- {
205
- "id": "cd19ba68-3f39-4b50-8014-85f01258fbe3",
206
- "type": "edge",
207
- "sourceId": "7e51efb8-7463-4775-ad69-180442a34444",
208
- "targetId": "abf8c265-da42-4b81-b7bf-349d3e248294",
209
- },
210
- {
211
- "id": "7c26024c-0f02-4393-8cf0-0f5097cd21d0",
212
- "type": "edge",
213
- "sourceId": "abf8c265-da42-4b81-b7bf-349d3e248294",
214
- "targetId": "40abc689-6e0e-40ee-bc76-df51065e6ff5",
215
- },
216
- {
217
- "id": "66d1a27e-2cf2-4b2e-862d-b65dc554c320",
218
- "type": "edge",
219
- "sourceId": "abf8c265-da42-4b81-b7bf-349d3e248294",
220
- "targetId": "a5385f35-5ba7-4cbf-a5b8-f87bca6fd95c",
221
- },
222
- {
223
- "id": "abf1329e-c27d-4655-b1a7-d410ea03b998",
224
- "type": "edge",
225
- "sourceId": "a5385f35-5ba7-4cbf-a5b8-f87bca6fd95c",
226
- "targetId": "40abc689-6e0e-40ee-bc76-df51065e6ff5",
227
- },
228
- {
229
- "id": "d56e19e6-2303-4272-b7df-49e3df75c62f",
230
- "type": "edge",
231
- "sourceId": "a5385f35-5ba7-4cbf-a5b8-f87bca6fd95c",
232
- "targetId": "0689381e-0650-4ba5-b4ba-1a1800f035ca",
233
- },
234
- ],
235
- }
236
-
237
-
238
- async def test_save_rule_endpoints(client: PlanarTestClient, app: PlanarApp):
239
- response = await client.get("/planar/v1/rules/complex_business_rule")
240
-
241
- assert response.status_code == 200
242
-
243
- data = response.json()
244
-
245
- assert len(data["configs"]) == 1
246
-
247
- # save the rule
248
- response = await client.post(
249
- "/planar/v1/rules/complex_business_rule", json=EXPENSE_RULE_JDM
250
- )
251
-
252
- assert response.status_code == 200, response.text
253
-
254
- data = response.json()
255
-
256
- assert len(data["configs"]) == 2
257
-
258
-
259
- PRICING_RULE_JDM = {
260
- "nodes": [
261
- {
262
- "id": "6cc036d3-3350-449e-9b2c-1569b8f86ffc",
263
- "type": "inputNode",
264
- "name": "Input",
265
- "content": {
266
- "schema": '{"$defs": {"TransactionVolumeRow": {"properties": {"period": {"title": "Period", "type": "string"}, "country": {"title": "Country", "type": "string"}, "currency": {"title": "Currency", "type": "string"}, "completed_count": {"title": "Completed Count", "type": "integer"}, "rejected_count": {"title": "Rejected Count", "type": "integer"}}, "required": ["period", "country", "currency", "completed_count", "rejected_count"], "title": "TransactionVolumeRow", "type": "object"}}, "properties": {"rows": {"items": {"$ref": "#/$defs/TransactionVolumeRow"}, "title": "Rows", "type": "array"}}, "required": ["rows"], "title": "PricingInput", "type": "object"}'
267
- },
268
- "position": {"x": 100, "y": 100},
269
- },
270
- {
271
- "id": "3921e9d3-02e3-4a72-b74d-037c80f97eaa",
272
- "type": "decisionTableNode",
273
- "name": "decisionTable1",
274
- "content": {
275
- "hitPolicy": "first",
276
- "rules": [
277
- {
278
- "_id": "15d4429c-39bc-448d-a7f4-187eaea4493a",
279
- "e5688083-30b9-449e-adaf-bf8ff69eb2ac": '"ARS"',
280
- "42c29309-9aa4-4441-bd1a-b1b57d1b628e": "<= 6000",
281
- "71b9d121-5b37-4b0b-b4c2-d29a868fed35": '"Argentina"',
282
- "662e29e1-d0b8-4cf4-a443-3e92f2157054": "100",
283
- "_description": "",
284
- },
285
- {
286
- "_id": "9ef47884-004d-4314-a61c-38778ed7b7d7",
287
- "e5688083-30b9-449e-adaf-bf8ff69eb2ac": "",
288
- "42c29309-9aa4-4441-bd1a-b1b57d1b628e": "",
289
- "71b9d121-5b37-4b0b-b4c2-d29a868fed35": "",
290
- "662e29e1-d0b8-4cf4-a443-3e92f2157054": "1.00",
291
- },
292
- ],
293
- "inputs": [
294
- {
295
- "id": "e5688083-30b9-449e-adaf-bf8ff69eb2ac",
296
- "name": "Currency",
297
- "field": "currency",
298
- },
299
- {
300
- "id": "42c29309-9aa4-4441-bd1a-b1b57d1b628e",
301
- "name": "Completed Count",
302
- "field": "completed_count",
303
- },
304
- {
305
- "id": "71b9d121-5b37-4b0b-b4c2-d29a868fed35",
306
- "name": "Country",
307
- "field": "country",
308
- },
309
- ],
310
- "outputs": [
311
- {
312
- "id": "662e29e1-d0b8-4cf4-a443-3e92f2157054",
313
- "field": "completed_price_per_transaction_usd",
314
- "name": "Completed Price Per Transaction (USD)",
315
- }
316
- ],
317
- "passThrough": True,
318
- "passThorough": None,
319
- "inputField": "rows",
320
- "outputPath": "line_items",
321
- "executionMode": "loop",
322
- },
323
- "position": {"x": 350, "y": 95},
324
- },
325
- {
326
- "id": "a9a82683-5dbb-4eed-8326-83dea36c1d53",
327
- "type": "outputNode",
328
- "name": "Output",
329
- "content": {
330
- "schema": '{"$defs": {"TransactionPricingLine": {"properties": {"period": {"title": "Period", "type": "string"}, "country": {"title": "Country", "type": "string"}, "currency": {"title": "Currency", "type": "string"}, "completed_count": {"title": "Completed Count", "type": "integer"}, "rejected_count": {"title": "Rejected Count", "type": "integer"}, "completed_price_per_transaction_usd": {"title": "Completed Price Per Transaction Usd", "type": "number"}, "rejected_price_per_transaction_usd": {"title": "Rejected Price Per Transaction Usd", "type": "number"}}, "required": ["period", "country", "currency", "completed_count", "rejected_count", "completed_price_per_transaction_usd", "rejected_price_per_transaction_usd"], "title": "TransactionPricingLine", "type": "object"}}, "properties": {"line_items": {"items": {"$ref": "#/$defs/TransactionPricingLine"}, "title": "Line Items", "type": "array"}}, "required": ["line_items"], "title": "PricingRuleOutput", "type": "object"}'
331
- },
332
- "position": {"x": 1195, "y": 60},
333
- },
334
- {
335
- "id": "b384c91d-dbc3-4043-a0f0-a3adef9ac340",
336
- "type": "expressionNode",
337
- "name": "expression1",
338
- "content": {
339
- "expressions": [
340
- {
341
- "id": "b0e3f514-c109-43e4-91ad-3007110d0a35",
342
- "key": "rejected_price_per_transaction_usd",
343
- "value": "200",
344
- }
345
- ],
346
- "passThrough": True,
347
- "inputField": "line_items",
348
- "outputPath": "line_items",
349
- "executionMode": "loop",
350
- },
351
- "position": {"x": 670, "y": 100},
352
- },
353
- {
354
- "id": "b89b13b8-526f-4db2-a524-faeeca0e78d7",
355
- "type": "expressionNode",
356
- "name": "expression2",
357
- "content": {
358
- "expressions": [
359
- {
360
- "id": "b6a9570b-7ea1-4ebb-b5eb-b693fe14ca47",
361
- "key": "line_items",
362
- "value": "line_items",
363
- }
364
- ],
365
- "passThrough": False,
366
- "inputField": None,
367
- "outputPath": None,
368
- "executionMode": "single",
369
- },
370
- "position": {"x": 950, "y": 100},
371
- },
372
- ],
373
- "edges": [
374
- {
375
- "id": "a9c02fbb-3ad6-4f65-a718-16c0e02d7551",
376
- "type": "edge",
377
- "sourceId": "6cc036d3-3350-449e-9b2c-1569b8f86ffc",
378
- "targetId": "3921e9d3-02e3-4a72-b74d-037c80f97eaa",
379
- },
380
- {
381
- "id": "c2959035-5f0d-4317-8ccf-2f885450b669",
382
- "type": "edge",
383
- "sourceId": "3921e9d3-02e3-4a72-b74d-037c80f97eaa",
384
- "targetId": "b384c91d-dbc3-4043-a0f0-a3adef9ac340",
385
- },
386
- {
387
- "id": "6513b9bf-7016-4776-bf84-81418caf7b74",
388
- "type": "edge",
389
- "sourceId": "b384c91d-dbc3-4043-a0f0-a3adef9ac340",
390
- "targetId": "b89b13b8-526f-4db2-a524-faeeca0e78d7",
391
- },
392
- {
393
- "id": "b75e295e-8fb4-45b6-9040-06fdd8d6723e",
394
- "type": "edge",
395
- "sourceId": "b89b13b8-526f-4db2-a524-faeeca0e78d7",
396
- "targetId": "a9a82683-5dbb-4eed-8326-83dea36c1d53",
397
- },
398
- ],
399
- }
400
-
401
-
402
- async def test_save_rule_endpoints_with_jdm(client: PlanarTestClient, app: PlanarApp):
403
- response = await client.get("/planar/v1/rules/pricing_rule")
404
-
405
- assert response.status_code == 200
406
-
407
- data = response.json()
408
-
409
- assert len(data["configs"]) == 1
410
-
411
- # save the rule
412
- response = await client.post("/planar/v1/rules/pricing_rule", json=PRICING_RULE_JDM)
413
-
414
- assert response.status_code == 200
415
-
416
- data = response.json()
417
-
418
- assert len(data["configs"]) == 2
419
-
420
-
421
- async def test_save_rule_endpoints_with_jdm_wrong_type(
422
- client: PlanarTestClient, app: PlanarApp
423
- ):
424
- response = await client.get("/planar/v1/rules/pricing_rule_with_wrong_type")
425
-
426
- assert response.status_code == 200
427
-
428
- data = response.json()
429
-
430
- assert len(data["configs"]) == 1
431
-
432
- # save the rule
433
- response = await client.post(
434
- "/planar/v1/rules/pricing_rule_with_wrong_type",
435
- json=PRICING_RULE_JDM,
436
- )
437
-
438
- assert response.status_code == 400
439
- response_json = response.json()
440
- assert response_json["detail"]["error"] == "ValidationError"
441
- assert response_json["detail"]["object_name"] == "pricing_rule_with_wrong_type"
442
- assert response_json["detail"]["object_type"] == "rule"
443
- assert response_json["detail"]["diagnostics"]["is_valid"] is False
444
- assert (
445
- response_json["detail"]["diagnostics"]["suggested_fix"]["jdm"]["nodes"][0][
446
- "content"
447
- ]["schema"]
448
- == PRICING_RULE_JDM["nodes"][0]["content"]["schema"]
449
- )
450
- # check contains some_other_field
451
- assert (
452
- "some_other_field"
453
- in response_json["detail"]["diagnostics"]["suggested_fix"]["jdm"]["nodes"][2][
454
- "content"
455
- ]["schema"]
456
- )
457
-
458
- assert response_json["detail"]["diagnostics"]["issues"] == [
459
- {
460
- "error_code": "MISSING_FIELD",
461
- "field_path": "some_other_field",
462
- "message": "Field 'some_other_field' is missing in current node",
463
- "reference_value": {
464
- "title": "Some Other Field",
465
- "type": "string",
466
- },
467
- "current_value": None,
468
- "for_object": "outputNode",
469
- }
470
- ]