planar 0.10.0__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 (60) hide show
  1. planar/app.py +18 -6
  2. planar/routers/info.py +79 -36
  3. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  4. planar/testing/fixtures.py +7 -4
  5. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
  6. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/RECORD +8 -60
  7. planar/ai/test_agent_serialization.py +0 -229
  8. planar/ai/test_agent_tool_step_display.py +0 -78
  9. planar/data/test_dataset.py +0 -358
  10. planar/files/storage/test_azure_blob.py +0 -435
  11. planar/files/storage/test_local_directory.py +0 -162
  12. planar/files/storage/test_s3.py +0 -299
  13. planar/files/test_files.py +0 -282
  14. planar/human/test_human.py +0 -385
  15. planar/logging/test_formatter.py +0 -327
  16. planar/modeling/mixins/test_auditable.py +0 -97
  17. planar/modeling/mixins/test_timestamp.py +0 -134
  18. planar/modeling/mixins/test_uuid_primary_key.py +0 -52
  19. planar/routers/test_agents_router.py +0 -174
  20. planar/routers/test_dataset_router.py +0 -429
  21. planar/routers/test_files_router.py +0 -49
  22. planar/routers/test_object_config_router.py +0 -367
  23. planar/routers/test_routes_security.py +0 -168
  24. planar/routers/test_rule_router.py +0 -470
  25. planar/routers/test_workflow_router.py +0 -564
  26. planar/rules/test_data/account_dormancy_management.json +0 -223
  27. planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
  28. planar/rules/test_data/applicant_risk_assessment.json +0 -435
  29. planar/rules/test_data/booking_fraud_detection.json +0 -407
  30. planar/rules/test_data/cellular_data_rollover_system.json +0 -258
  31. planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
  32. planar/rules/test_data/customer_lifetime_value.json +0 -143
  33. planar/rules/test_data/import_duties_calculator.json +0 -289
  34. planar/rules/test_data/insurance_prior_authorization.json +0 -443
  35. planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
  36. planar/rules/test_data/order_consolidation_system.json +0 -375
  37. planar/rules/test_data/portfolio_risk_monitor.json +0 -471
  38. planar/rules/test_data/supply_chain_risk.json +0 -253
  39. planar/rules/test_data/warehouse_cross_docking.json +0 -237
  40. planar/rules/test_rules.py +0 -1494
  41. planar/security/tests/test_auth_middleware.py +0 -162
  42. planar/security/tests/test_authorization_context.py +0 -78
  43. planar/security/tests/test_cedar_basics.py +0 -41
  44. planar/security/tests/test_cedar_policies.py +0 -158
  45. planar/security/tests/test_jwt_principal_context.py +0 -179
  46. planar/test_app.py +0 -142
  47. planar/test_cli.py +0 -394
  48. planar/test_config.py +0 -515
  49. planar/test_object_config.py +0 -527
  50. planar/test_object_registry.py +0 -14
  51. planar/test_sqlalchemy.py +0 -193
  52. planar/test_utils.py +0 -105
  53. planar/testing/test_memory_storage.py +0 -143
  54. planar/workflows/test_concurrency_detection.py +0 -120
  55. planar/workflows/test_lock_timeout.py +0 -140
  56. planar/workflows/test_serialization.py +0 -1203
  57. planar/workflows/test_suspend_deserialization.py +0 -231
  58. planar/workflows/test_workflow.py +0 -2005
  59. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/WHEEL +0 -0
  60. {planar-0.10.0.dist-info → planar-0.11.0.dist-info}/entry_points.txt +0 -0
planar/app.py CHANGED
@@ -28,7 +28,6 @@ from planar.routers import (
28
28
  create_workflow_router,
29
29
  )
30
30
  from planar.routers.agents_router import create_agent_router
31
- from planar.routers.dataset_router import create_dataset_router
32
31
  from planar.routers.entity_router import create_entities_router
33
32
  from planar.routers.object_config_router import create_object_config_router
34
33
  from planar.routers.rule import create_rule_router
@@ -130,14 +129,27 @@ class PlanarApp:
130
129
  create_human_task_routes(),
131
130
  prefix="/human-tasks",
132
131
  )
133
- self.router_v1.include_router(
134
- create_dataset_router(),
135
- prefix="/datasets",
136
- )
132
+
133
+ if self.config.data:
134
+ try:
135
+ from planar.routers.dataset_router import create_dataset_router
136
+
137
+ self.router_v1.include_router(
138
+ create_dataset_router(),
139
+ prefix="/datasets",
140
+ )
141
+ except ImportError:
142
+ logger.error(
143
+ "Data dependencies not installed. Ensure you install the `data` optional dependency in your project (planar[data])"
144
+ )
145
+ raise
137
146
 
138
147
  self.router_v1.include_router(
139
148
  create_info_router(
140
- title=title or "Planar API", description=description or "Planar API"
149
+ title=title or "Planar API",
150
+ description=description or "Planar API",
151
+ config=self.config,
152
+ registry=self._object_registry,
141
153
  ),
142
154
  prefix="",
143
155
  )
planar/routers/info.py CHANGED
@@ -1,16 +1,34 @@
1
+ import importlib.metadata
2
+ from typing import Literal, TypedDict
3
+
1
4
  from fastapi import APIRouter, Depends
2
5
  from pydantic import BaseModel
3
- from sqlalchemy.ext.asyncio import AsyncSession
4
- from sqlmodel import col, distinct, func, select
6
+ from sqlmodel import col, func, select
7
+ from sqlmodel.ext.asyncio.session import AsyncSession
5
8
 
9
+ from planar.config import PlanarConfig, get_environment
10
+ from planar.data.config import DataConfig
11
+ from planar.files.storage.config import StorageConfig
6
12
  from planar.human.models import HumanTask, HumanTaskStatus
7
13
  from planar.logging import get_logger
8
- from planar.object_config import ConfigurableObjectType, ObjectConfiguration
14
+ from planar.object_registry import ObjectRegistry
9
15
  from planar.session import get_session
10
16
  from planar.workflows.models import Workflow, WorkflowStatus
11
17
 
12
18
  logger = get_logger(__name__)
13
19
 
20
+ StorageInfo = Literal["s3", "localdir", "azure_blob"]
21
+
22
+
23
+ class DatasetsInfo(BaseModel):
24
+ catalog: Literal["duckdb", "postgres", "sqlite"]
25
+ storage: StorageInfo
26
+
27
+
28
+ class SystemFeatures(BaseModel):
29
+ storage: StorageInfo | None = None
30
+ datasets: DatasetsInfo | None = None
31
+
14
32
 
15
33
  class SystemInfo(BaseModel):
16
34
  """Combined application information and system statistics"""
@@ -19,6 +37,11 @@ class SystemInfo(BaseModel):
19
37
  title: str
20
38
  description: str
21
39
 
40
+ version: str
41
+ environment: str
42
+
43
+ features: SystemFeatures
44
+
22
45
  # System stats
23
46
  total_workflow_runs: int = 0
24
47
  completed_runs: int = 0
@@ -27,7 +50,18 @@ class SystemInfo(BaseModel):
27
50
  active_agents: int = 0
28
51
 
29
52
 
30
- async def get_system_stats(session: AsyncSession = Depends(get_session)) -> dict:
53
+ class SystemStats(TypedDict):
54
+ total_workflow_runs: int
55
+ completed_runs: int
56
+ in_progress_runs: int
57
+ pending_human_tasks: int
58
+ active_agents: int
59
+
60
+
61
+ async def get_system_stats(
62
+ registry: ObjectRegistry,
63
+ session: AsyncSession = Depends(get_session),
64
+ ) -> SystemStats:
31
65
  """
32
66
  Get system-wide statistics directly from the database.
33
67
 
@@ -35,8 +69,10 @@ async def get_system_stats(session: AsyncSession = Depends(get_session)) -> dict
35
69
  rather than fetching all records and calculating in the application.
36
70
  """
37
71
  try:
72
+ agent_count = len(registry.get_agents())
73
+
38
74
  # Get workflow run counts
39
- workflow_stats = await session.execute(
75
+ workflow_stats = await session.exec(
40
76
  select(
41
77
  func.count().label("total_runs"),
42
78
  func.count(col(Workflow.id))
@@ -47,42 +83,21 @@ async def get_system_stats(session: AsyncSession = Depends(get_session)) -> dict
47
83
  .label("in_progress_runs"),
48
84
  ).select_from(Workflow)
49
85
  )
50
- workflow_row = workflow_stats.one()
86
+ total_runs, completed_runs, in_progress_runs = workflow_stats.one()
51
87
 
52
88
  # Get pending human task count
53
- human_task_query = await session.execute(
89
+ human_task_query = await session.exec(
54
90
  select(func.count())
55
91
  .select_from(HumanTask)
56
92
  .where(HumanTask.status == HumanTaskStatus.PENDING)
57
93
  )
58
- pending_tasks = human_task_query.scalar() or 0
59
-
60
- # Get agent count from the registry or count distinct agent configs
61
- agent_count = 0
62
- try:
63
- # Count distinct agent names in the AgentConfig table
64
- agent_query = await session.execute(
65
- select(
66
- func.count(distinct(ObjectConfiguration.object_name))
67
- ).select_from(
68
- select(ObjectConfiguration)
69
- .where(
70
- ObjectConfiguration.object_type == ConfigurableObjectType.AGENT
71
- )
72
- .subquery()
73
- )
74
- )
75
- agent_count = agent_query.scalar() or 0
76
- except Exception:
77
- logger.exception("error counting agents")
78
- # Fallback to 0
79
- agent_count = 0
94
+ pending_tasks = human_task_query.one()
80
95
 
81
96
  # Return stats dict
82
97
  return {
83
- "total_workflow_runs": workflow_row.total_runs or 0,
84
- "completed_runs": workflow_row.completed_runs or 0,
85
- "in_progress_runs": workflow_row.in_progress_runs or 0,
98
+ "total_workflow_runs": total_runs,
99
+ "completed_runs": completed_runs,
100
+ "in_progress_runs": in_progress_runs,
86
101
  "pending_human_tasks": pending_tasks,
87
102
  "active_agents": agent_count,
88
103
  }
@@ -98,13 +113,31 @@ async def get_system_stats(session: AsyncSession = Depends(get_session)) -> dict
98
113
  }
99
114
 
100
115
 
101
- def create_info_router(title: str, description: str) -> APIRouter:
116
+ def get_app_version() -> str:
117
+ try:
118
+ return importlib.metadata.version("planar")
119
+ except importlib.metadata.PackageNotFoundError:
120
+ logger.warning("Planar package not found, returning development version")
121
+ return "development"
122
+
123
+
124
+ def get_storage_info(cfg: StorageConfig) -> StorageInfo:
125
+ return cfg.backend
126
+
127
+
128
+ def get_datasets_info(cfg: DataConfig) -> DatasetsInfo | None:
129
+ return DatasetsInfo(catalog=cfg.catalog.type, storage=get_storage_info(cfg.storage))
130
+
131
+
132
+ def create_info_router(
133
+ title: str, description: str, config: PlanarConfig, registry: ObjectRegistry
134
+ ) -> APIRouter:
102
135
  """
103
136
  Create a router for serving combined application information and system statistics.
104
137
 
105
138
  This router provides a single endpoint to retrieve the application's title,
106
139
  description, and system-wide statistics on workflow runs, human tasks,
107
- and registered agents.
140
+ and registered agents, as well as the application's features and configuration.
108
141
 
109
142
  Args:
110
143
  title: The application title
@@ -125,7 +158,17 @@ def create_info_router(title: str, description: str) -> APIRouter:
125
158
  Returns:
126
159
  SystemInfo object containing app details and system stats
127
160
  """
128
- stats = await get_system_stats(session)
129
- return SystemInfo(title=title, description=description, **stats)
161
+ stats = await get_system_stats(registry, session)
162
+ return SystemInfo(
163
+ title=title,
164
+ description=description,
165
+ version=get_app_version(),
166
+ environment=get_environment(),
167
+ features=SystemFeatures(
168
+ storage=get_storage_info(config.storage) if config.storage else None,
169
+ datasets=get_datasets_info(config.data) if config.data else None,
170
+ ),
171
+ **stats,
172
+ )
130
173
 
131
174
  return router
@@ -3,7 +3,7 @@ name = "{{ name }}"
3
3
  version = "0.1.0"
4
4
  requires-python = ">=3.12"
5
5
  dependencies = [
6
- "planar>=0.9.0",
6
+ "planar[data]>=0.10.0",
7
7
  ]
8
8
 
9
9
  [[tool.uv.index]]
@@ -36,7 +36,7 @@ from pathlib import Path
36
36
  import pytest
37
37
 
38
38
  from planar.app import PlanarApp
39
- from planar.config import load_config
39
+ from planar.config import load_config, load_environment_aware_config
40
40
  from planar.data.config import DataConfig, SQLiteCatalogConfig
41
41
  from planar.db import DatabaseManager, new_session
42
42
  from planar.files.storage.config import LocalDirectoryConfig
@@ -138,9 +138,12 @@ def data_config(tmp_path):
138
138
  @pytest.fixture(name="app_with_data")
139
139
  def app_with_data_fixture(data_config):
140
140
  """Create a PlanarApp with data configuration."""
141
- app = PlanarApp()
142
- # Add data config to the app's config
143
- app.config.data = data_config
141
+ config = load_environment_aware_config()
142
+
143
+ config.data = data_config
144
+
145
+ app = PlanarApp(config=config)
146
+
144
147
  return app
145
148
 
146
149
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: planar
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Summary: Add your description here
5
5
  License-Expression: LicenseRef-Proprietary
6
6
  Requires-Dist: aiofiles>=24.1.0
@@ -296,6 +296,14 @@ To disable SQLite testing:
296
296
  PLANAR_TEST_SQLITE=0 uv run pytest
297
297
  ```
298
298
 
299
+ ### Test coverage
300
+
301
+ We use [pytest-cov](https://pypi.org/project/pytest-cov/) to measure test coverage. To generate a simple coverage report use the following command:
302
+
303
+ ```bash
304
+ uv run pytest --cov=planar
305
+ ```
306
+
299
307
  ### Pre-commit hooks
300
308
 
301
309
  We use [pre-commit](https://pre-commit.com/) to manage pre-commit hooks. To install the pre-commit hooks, run the following command:
@@ -6,10 +6,8 @@ planar/ai/agent_utils.py,sha256=MYNerdAm2TPVbDSKAmBCUlGmR56NAc8seZmDAFOWvUA,4199
6
6
  planar/ai/models.py,sha256=bZd4MoBBJMqzXJqsmsbMdZtOaRrNeX438CHAqOvmpfw,4598
7
7
  planar/ai/pydantic_ai.py,sha256=FpD0pE7wWNYwmEUZ90D7_J8gbAoqKmWtrLr2fhAd7rg,23503
8
8
  planar/ai/state.py,sha256=6vQ8VMLxJYB75QXkm8AVPkdXHUMwwjKxBWH-hGIK9p0,278
9
- planar/ai/test_agent_serialization.py,sha256=zYLIxhYdFhOZzBrEBoQNyYLyNcNxWwaMTkjt_ARTkZk,8073
10
- planar/ai/test_agent_tool_step_display.py,sha256=7OeKLnimItAkAFcArufDfiL3rXnQ1GwEtrE58_557hM,2588
11
9
  planar/ai/utils.py,sha256=WVBW0TGaoKytC4bNd_a9lXrBf5QsDRut4GBcA53U2Ww,3116
12
- planar/app.py,sha256=G5MtKJ4QPrUbiN9ykWCavVo2Kq4cMawZAxiS3uDyFaI,18673
10
+ planar/app.py,sha256=SKP7xinjQZfVKssKdfq0eK7VaJru99ReGJpn5KeTD3w,19108
13
11
  planar/cli.py,sha256=SIyQOY3MbNDuohNcXFRIrHJuGxFNNC8C_ihfCXIUvbE,9900
14
12
  planar/config.py,sha256=6J42G9rEVUiOyCAY3EwUTU3PPmWthGTnrHMzST9TMcc,17809
15
13
  planar/data/__init__.py,sha256=LwrWl925w1CN0aW645Wpj_kDp0B8j5SsPzjr9iyrcmI,285
@@ -17,7 +15,6 @@ planar/data/config.py,sha256=zp6ChI_2MUMbupEVQNY-BxzcdLvejXG33DCp0BujGVU,1209
17
15
  planar/data/connection.py,sha256=OGBVVapxDu1-vxAHjlEmSQiapBXBegetb4Nu0wg4n2Q,3846
18
16
  planar/data/dataset.py,sha256=FKvuM2-xI101mEf1eeOYfNjSpHk8XjFXnAktTNR8Nig,5800
19
17
  planar/data/exceptions.py,sha256=AlhGQ_TReyEzfPSlqoXCjoZ1206Ut7dS4lrukVfGHaw,358
20
- planar/data/test_dataset.py,sha256=_tYvzw4y2-8rlRELdJxItcG_0OCFlOv7tBC7QI1j21o,10196
21
18
  planar/data/utils.py,sha256=hhHKpYBvzLdzjNy_OElpAqIU-3XuHqUkFCiF5YWsShs,2556
22
19
  planar/db/__init__.py,sha256=SNgB6unQ1f1E9dB9O-KrsPsYM17KLsgOW1u0ajqs57I,318
23
20
  planar/db/alembic/env.py,sha256=4yX_aCqrXScVN3q7ISiMEDz40-5Dmociqh6d7VCUPwc,6051
@@ -35,29 +32,20 @@ planar/files/storage/config.py,sha256=jE9Dn6cG_a4x9pdaZkasOxjyWkK6hmplLrPjEsRXGL
35
32
  planar/files/storage/context.py,sha256=Qadlz4T-FQ1A0TdGsOfuNmM679ohayU7oaexUpT8AY0,361
36
33
  planar/files/storage/local_directory.py,sha256=1SsEfr0Ud9TvSQJneDl_M-D7AFYixLE9t-bVIiY3aSI,7395
37
34
  planar/files/storage/s3.py,sha256=1861rSw3kplXtugUWD7mdSD_EnPSHME1mGc82V69r5g,8234
38
- planar/files/storage/test_azure_blob.py,sha256=OFYpns6JyeCCBHCoLz56uUHR6tWWeSZldUant5llczI,14200
39
- planar/files/storage/test_local_directory.py,sha256=KtzRfjtZUew1U-KETtD2mb6ywwX6HmjzaaeixOP0Ebg,5751
40
- planar/files/storage/test_s3.py,sha256=QG-CH7fiaRmQRwffnqG2mLRrw9LIlR2-xRyHs6Wuspo,10565
41
- planar/files/test_files.py,sha256=nclsbLnbijCWQ-Aj8Yvo06hs72PygL1Wps7uk7716sc,8957
42
35
  planar/human/__init__.py,sha256=FwpV-FFssKKlvKSjWoI4gJB1XTMaNb1UNCSBxjAtIBw,147
43
36
  planar/human/human.py,sha256=-oRtN_8bCtSV7Sxku7yG4rof7T5pr4j18Cfm3u4Z3PM,14925
44
37
  planar/human/models.py,sha256=Cec1Y9NGGtuAl1ZhqNc9PWIq__BbiWVTh7IYKR4yl3w,2317
45
- planar/human/test_human.py,sha256=GlbTjCNeAAfcwVIPq1TRiH0ojSgAU0i3oMc2yQe0xVo,13226
46
38
  planar/logging/__init__.py,sha256=BW101gskQS3C2agKSDiJEy4L-j5z6BP1AwEfd2JMXHM,254
47
39
  planar/logging/attributes.py,sha256=SB-lC-Osa55zXkX2UAWm56vKXvHgbF7TmhGnSiBWejk,1529
48
40
  planar/logging/context.py,sha256=mGVpL-276VWpRUIGlkGwbn9gkATqFS7lcFn1aM0v-eQ,384
49
41
  planar/logging/formatter.py,sha256=NMDhpo7ezsfGO7cPwOWbClqODviEgSjqHCoqrCCIu2c,3560
50
42
  planar/logging/logger.py,sha256=vj3-z2ea0J1ppgDpJBsrueA3jg5kjk9ceIo-4tWarXk,2650
51
43
  planar/logging/otel.py,sha256=bD1wgKTon1ooq3JIWc_i0_d_ejxw8_a5YPFuizRa5ic,1734
52
- planar/logging/test_formatter.py,sha256=nzlZFUC1JD4Imcy1H7VaFhwjrHzRc_oQLXWZXiN7iGA,11695
53
44
  planar/modeling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
45
  planar/modeling/field_helpers.py,sha256=9SOHTWPzjlaiq7RF88wjug3NvAywFurcHn651YL_SNY,1903
55
46
  planar/modeling/json_schema_generator.py,sha256=NDqPkWQA_I7ywQXCEQfj5ub9u5KAFEcSQpXVkrCluV4,2864
56
47
  planar/modeling/mixins/__init__.py,sha256=Lwg5eL4VFfv61FRBvH5OZqIyfrSogxQlYLUDnWnSorg,320
57
48
  planar/modeling/mixins/auditable.py,sha256=WP7aDWVn1j22ZffKzYRpu23JQJ4vvHCU1qxcbgChwhc,1619
58
- planar/modeling/mixins/test_auditable.py,sha256=RSYesWWBysFEWTD39-yDhww3wCe1OPN9Yt3ywGmx4d8,2912
59
- planar/modeling/mixins/test_timestamp.py,sha256=oLKPvr8oUdjJPJRif81nn4YV_uwbxql_ojjXdKI7j7E,4366
60
- planar/modeling/mixins/test_uuid_primary_key.py,sha256=t9ZoB0dS4jjJVrHic7EeEh_g3eZeYV3mr0ylv3Kr1Io,1575
61
49
  planar/modeling/mixins/timestamp.py,sha256=-eHndCWztDiOxfCI2UknmphGeoHMJVDJG1Lz4KtkQUA,1641
62
50
  planar/modeling/mixins/uuid_primary_key.py,sha256=O1BtuXk5GdsfpwTS6nGZm1GNi0pdXKQ6Kt2f5ZjiuMc,453
63
51
  planar/modeling/orm/__init__.py,sha256=QwPgToKEf_gCdjAjKKmgh0xLTHGsboK1kH1a1a0tSy0,339
@@ -77,55 +65,28 @@ planar/routers/entity_router.py,sha256=7Y1LDSqI_ovoOGr9DGylGM8BmRxF-WSPQSwITJHc6
77
65
  planar/routers/event.py,sha256=yvzzMQaKRoS2A0KSjQTyWcfmBzpt8xPNDfVW50XUSCw,2961
78
66
  planar/routers/files.py,sha256=udu6PeZ9AuOpNyJete21rWAVQyE0qnC7tnSyJ97AH4Y,5644
79
67
  planar/routers/human.py,sha256=m_CbH97_QGmzriui_xopLOq-2D1kR0WgvO92fMaFeBQ,5010
80
- planar/routers/info.py,sha256=HQa-mumw4zitG61V9isJlZ3cMr8pEwlB54Ct_LrpJDo,4473
68
+ planar/routers/info.py,sha256=_7edvZjWbI65htI8nrQdHNxjMfSn2u-JHmxnpNWUAiU,5396
81
69
  planar/routers/models.py,sha256=zknkVs9-B4UJlMe9fl2EpXx7sdzfjQfwAbNoL1a0wmI,4694
82
70
  planar/routers/object_config_router.py,sha256=zA8-gGBQp1-Gm3uCC4WJ6nLicFwt4CsCqCYLFp1lRN8,4802
83
71
  planar/routers/rule.py,sha256=d6giUwYRKzxQFPeoWbe8Ylp2Cxd71_uK8yoS9NrOOBg,3563
84
- planar/routers/test_agents_router.py,sha256=jzRCLB21YcEfhaFUos_gfRp9WDdP38_cozTQkHbi9b4,6099
85
- planar/routers/test_dataset_router.py,sha256=8Dm6ZFlIXQgwWLG3y00mz5cMsZvmIw7yM2-w-Qe_sCQ,13058
86
- planar/routers/test_files_router.py,sha256=HfZF1zeJ9BD2jhE6s698Jo7sDpO55RJF5g1Ksup4jtM,1576
87
- planar/routers/test_object_config_router.py,sha256=JpzoNlNONgljmGJYtrXnhtGhKjUT3YQMhP89fnt7dn4,11406
88
- planar/routers/test_routes_security.py,sha256=lXHeYg_th4UaDWeANM-dzONF8p2bEtwXJYYUlftE9R8,5556
89
- planar/routers/test_rule_router.py,sha256=08fa4sc7RaXvQzPCQQ4LaftfXuQwoPEDzcS4lesPG2Q,17220
90
- planar/routers/test_workflow_router.py,sha256=h2XTJrlO3vAshd45abtMaE0kigWTNxXjfJiuGcjcA7s,17463
91
72
  planar/routers/workflow.py,sha256=I9wJLqcFf52eEREhwqMsYICBEVD7I07g7ILWzxR5x9Y,18151
92
73
  planar/rules/__init__.py,sha256=lF3F8Rdf2ottjiJu0IeBdqhg1bckLhOqZFI2t-8KItM,474
93
74
  planar/rules/decorator.py,sha256=nxT17n9uwfXMOlk5lliw_cRS7Y83gMI6CQdrf_pB5yk,6666
94
75
  planar/rules/models.py,sha256=vC38JLeGzmU87L8BX4AyVJLJHmRYjWRmoHQ6S6ZlhPg,10186
95
76
  planar/rules/rule_configuration.py,sha256=B2G6mPnfxA277nF-Gr-B_Uely-ZOhz2jAhiwQMZuY-k,6508
96
77
  planar/rules/runner.py,sha256=KIPrt_ri50qotvDLOY9xly40bNTWRh8GVT2kEJFFtFo,1714
97
- planar/rules/test_data/account_dormancy_management.json,sha256=9aMMELZrF5DTBluMKUXJptxwULEcva4GHEyaapIeerY,4776
98
- planar/rules/test_data/airline_loyalty_points_calculator.json,sha256=7S1koMe60yR3h2VQys34oLy5ynhsEQ5wadMLPHCRQZA,5689
99
- planar/rules/test_data/applicant_risk_assessment.json,sha256=rj-Q13NczdNt00x5wrvGLalw5IfdT1j-_RvpwCZa7Fc,9994
100
- planar/rules/test_data/booking_fraud_detection.json,sha256=HCAsUIIlaPL8Ph3jtUFVi2vTZxZW53-XggjDWEoXmi4,10027
101
- planar/rules/test_data/cellular_data_rollover_system.json,sha256=8GjkKqqzK7usSa5KAvUwkD2CzW5JtLGECjlD5gH-vHk,6317
102
- planar/rules/test_data/clinical_trial_eligibility_screener.json,sha256=VV0RzWdThBNlIffuyklqAcKtRq-aOLHpcXX28IR9qOU,11152
103
- planar/rules/test_data/customer_lifetime_value.json,sha256=ZYeISNOsgT7zLcddeNrrrHlrocDlDBmSfOlQqhu3PTE,3562
104
- planar/rules/test_data/import_duties_calculator.json,sha256=JsXuvkiGQF6MxrgrIhFC1UxDCtO9nQpxtKKXwOQ1VYs,9062
105
- planar/rules/test_data/insurance_prior_authorization.json,sha256=27_jET4mmpuzLeOzVF5xi7c9IexX1UJTKsIoQGQ0n8c,12145
106
- planar/rules/test_data/online_check_in_eligibility_system.json,sha256=c6zCGojFN6c-CnuvG93zqlCZIrMccnOGAPMZVoMfirg,6114
107
- planar/rules/test_data/order_consolidation_system.json,sha256=kWJuVHAfAqsDW2xVdxHbtJTlfolQ_SM6_GISgnxsYB8,9803
108
- planar/rules/test_data/portfolio_risk_monitor.json,sha256=tTvQOJJLhakGxG4CnA9fdBIECstJnp0B8ogFADkdy8s,15168
109
- planar/rules/test_data/supply_chain_risk.json,sha256=fO0wV5ZnsZQpOP19Zp2troTMADaX0-KMpCxG_uHG198,7263
110
- planar/rules/test_data/warehouse_cross_docking.json,sha256=IPfcgNkY2sds301BeW6CjgFtK_zRyr27gI3UcqCB2Uo,5549
111
- planar/rules/test_rules.py,sha256=6M7CSg1bwn7O7DOoNi38vyVG4UmPQfRFxEO9qGE6rz0,52011
112
78
  planar/scaffold_templates/app/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
79
  planar/scaffold_templates/app/db/entities.py.j2,sha256=wg9O3JtRaRMKlDtoWHHodyNRL0s1UILvsr9fCQ_O2-4,279
114
80
  planar/scaffold_templates/app/flows/process_invoice.py.j2,sha256=R3EII_O2DHV1kvffW_AApZyaS6rR9eikcpxI08XH9dI,1691
115
81
  planar/scaffold_templates/main.py.j2,sha256=zrqsuv3Fp4lcknvB37RrRHy11msdFB1yDguYmTLLPhw,398
116
82
  planar/scaffold_templates/planar.dev.yaml.j2,sha256=I5-IqX7GJm6qA91WtUMw43L4hKACqgnER_H2racim4c,998
117
83
  planar/scaffold_templates/planar.prod.yaml.j2,sha256=FahJ2atDtvVH7IUCatGq6h9hmyF8meeiWC8RLfWphOQ,867
118
- planar/scaffold_templates/pyproject.toml.j2,sha256=nFfHWLp0sFK8cqjkdwBm6Hi6xsPzTNkaBeSgdTWTS-Q,183
84
+ planar/scaffold_templates/pyproject.toml.j2,sha256=HpBc3cuTr-VeVN3BQ8y1Vr2eOtkL7TLH5mNz_T-akpA,190
119
85
  planar/security/auth_context.py,sha256=i63JkHQ3oXNlTis7GIKRkZJbkcvZhD2jVDuO7blgbSc,5068
120
86
  planar/security/auth_middleware.py,sha256=Grrm0i2bstWZ83ukrNZsHvFbNzffN0rvbbCcb2OxRY0,5746
121
87
  planar/security/authorization.py,sha256=zTh5rLmVJZnGDq540_1WYljJ7Hws-BY_P6VqUCONqLE,12760
122
88
  planar/security/default_policies.cedar,sha256=j7stjEfmgzthsDCjw1NwlE3_SxXQmLOGNRONM_2htoE,1492
123
89
  planar/security/security_context.py,sha256=vzfwiDJRzq8snaENrJddG5Ei-UUq_a5hkInMEOZf0mM,496
124
- planar/security/tests/test_auth_middleware.py,sha256=GjAyJYAR6jF_zGG85ZKY-E_e_aoqjK8qiHSalv2T2rk,6206
125
- planar/security/tests/test_authorization_context.py,sha256=cnsC3V13NBJwzyIwZaM9wu_vergcnmVhwB3khVH7G-o,2364
126
- planar/security/tests/test_cedar_basics.py,sha256=i1jLPjlJT1n_97onbeDYVpnwAzU2PmHvIPvaJSH1J2U,1026
127
- planar/security/tests/test_cedar_policies.py,sha256=-Vn_CQgCUAVg7YhdUd34FsOjNL1EmY_o92r-fzmknP8,4848
128
- planar/security/tests/test_jwt_principal_context.py,sha256=nGElTLtXbabkAxd3kXVpSFdH7kvSzHzSkp89g5Vu5Hc,4691
129
90
  planar/session.py,sha256=xLS9WPvaiy9nr2Olju1-C-7_sU5VXK8RuNdjuKndul4,1020
130
91
  planar/sse/constants.py,sha256=jE3SooTEWPuuL_Bi6DisJYMR9pKOiHVfboU2h5QTJRg,22
131
92
  planar/sse/example.html,sha256=SgTJbdJ3B1F1DxLC2YWuX2F1XVwKcTjX34CbJCXoCTM,4144
@@ -133,19 +94,11 @@ planar/sse/hub.py,sha256=5jhfk7zdCivau3TT1MxU2qtvETSskhqEiXzt-t0sRpE,6859
133
94
  planar/sse/model.py,sha256=fU_Fx9LS2ouS6-Dj1TIF-PLGul9YratKWafoWfZR1gc,123
134
95
  planar/sse/proxy.py,sha256=aJGo_-JIeQ0xSmE4HJdulZxIgCVRsBMMXqqSqtPvTvo,9177
135
96
  planar/task_local.py,sha256=pyvT0bdzAn15HL2yQUs9YrU5MVXh9njQt9MH51AGljs,1102
136
- planar/test_app.py,sha256=5dYhOW6lRbAx2X270DfqktkJ5IfuqfowX6bwxM1WQAM,4865
137
- planar/test_cli.py,sha256=faR6CSuooHHyyB5Yt-p8CIr7mGtKrrU2TLQbc4Oe9bA,13834
138
- planar/test_config.py,sha256=HcmDu1nwKZZhzHQLGVyP9oxje-_g_XubEsvzRj28QPg,14328
139
- planar/test_object_config.py,sha256=izn4s2HmSDWpGtgpOTDmKeUYN2-63WDR1QtVQrT-x00,20135
140
- planar/test_object_registry.py,sha256=R7IwbB2GACm2HUuVZTeVY4V12XB9_JgSSeppPxiCdfs,480
141
- planar/test_sqlalchemy.py,sha256=QTloaipWiFmlLTBGH6YCRkwi1R27gmQZnwprO7lPLfU,7058
142
- planar/test_utils.py,sha256=gKenXotj36SN_bb3bQpYPfD8t06IjnGBQqEgWpujHcA,3086
143
97
  planar/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
144
- planar/testing/fixtures.py,sha256=2Pa5pdqrpcLNAxEsaZiDiLaivD4KWAL8pin22H-XuuM,9694
98
+ planar/testing/fixtures.py,sha256=z0VkKTqAg9z_1L_ojB0pTzKSaLPcDeNxRq_RB36rUU8,9740
145
99
  planar/testing/memory_storage.py,sha256=apcuFisC3hW9KiU3kO8zwHQ6oK9Lu20NSX5fJ0LSZUY,2824
146
100
  planar/testing/planar_test_client.py,sha256=2atx5zER5bGrCVoa-lcag8xP2ZcbnOLKoGRKpfOJCg8,1992
147
101
  planar/testing/synchronizable_tracer.py,sha256=SWeta1CgwGsN5duC0FR8NyXOQ1b1L8nDpvGdjZVJ9Bg,4938
148
- planar/testing/test_memory_storage.py,sha256=So32XL0gbLDFMTl-WJN445x9jL6O8Qsqw8IRaiZnsPs,4797
149
102
  planar/testing/workflow_observer.py,sha256=0Q2xsYuZzNGXHZVwvXBqL9KXPsdIXuSZGBJAxHopzJw,2976
150
103
  planar/utils.py,sha256=YP37-ODS8nYOIfHPo11CwCpQRsg8oc57lQ0wkXwqCyo,3607
151
104
  planar/workflows/__init__.py,sha256=yFrrtKYUCx4jBPpHdEWDfKQgZXzGyr9voj5lFe9C-_w,826
@@ -166,14 +119,9 @@ planar/workflows/step_core.py,sha256=e-O-SP_1ufr3EYwuvu3O4I06aoqMxu_tpvHAUO-pMWg
166
119
  planar/workflows/step_metadata.py,sha256=7hwcIm6ot8m-iUXSYCbPmkg6bWegF6_RJ1stInvaFII,10903
167
120
  planar/workflows/step_testing_utils.py,sha256=WiTwxB4mM2y6dW7CJ3PlIR1BkBodSxQV7-S25pQ3Ycs,2361
168
121
  planar/workflows/sub_workflow_runner.py,sha256=EpS7DhhXRbC6ABm-Sho6Uyxh2TqCjcTPDYvcTQN4FjY,8313
169
- planar/workflows/test_concurrency_detection.py,sha256=yfgvLOMkPaK7EiW4ihm1KQx82Y-s9pB6uJhBfDi7PwQ,4528
170
- planar/workflows/test_lock_timeout.py,sha256=H78N090wJtiEg6SaJosfRWijpX6HwnyWyNNb7WaGPe0,5746
171
- planar/workflows/test_serialization.py,sha256=JfaveBRQTNMkucqkTorIMGcvi8S0j6uRtboFaWpCmes,39586
172
- planar/workflows/test_suspend_deserialization.py,sha256=ddw2jToSJ-ebQ0RfT7KWTRMCOs1nis1lprQiGIGuaJ0,7751
173
- planar/workflows/test_workflow.py,sha256=hBLPQYqUsWEQ_SopKgi69ckRC5OpmQEBlsPcftGMu_Q,65266
174
122
  planar/workflows/tracing.py,sha256=E7E_kj2VBQisDqrllviIshbvOmB9QcEeRwMapunqio4,2732
175
123
  planar/workflows/wrappers.py,sha256=dY_3NqkzGMG4jgX2lkAqvHTYFA1lBzhkQCw7N5CyaQM,1174
176
- planar-0.10.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
177
- planar-0.10.0.dist-info/entry_points.txt,sha256=L3T0w9u2UPKWXv6JbXFWKU1d5xyEAq1xVWbpYS6mLNg,96
178
- planar-0.10.0.dist-info/METADATA,sha256=yXTyA50EXqt_mZqhbAzm8Z2YRnrSmVzuCJLjvzf1qik,12348
179
- planar-0.10.0.dist-info/RECORD,,
124
+ planar-0.11.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
125
+ planar-0.11.0.dist-info/entry_points.txt,sha256=L3T0w9u2UPKWXv6JbXFWKU1d5xyEAq1xVWbpYS6mLNg,96
126
+ planar-0.11.0.dist-info/METADATA,sha256=jlCvhwa-3Djk8_A05TJqgcGxE4Hj5U3wbYrDp5hNSi4,12556
127
+ planar-0.11.0.dist-info/RECORD,,
@@ -1,229 +0,0 @@
1
- """
2
- Tests for agent serialization functionality.
3
-
4
- This module tests the serialization of agents including configuration
5
- management and schema validation warnings.
6
- """
7
-
8
- import pytest
9
- from pydantic import BaseModel
10
- from sqlmodel.ext.asyncio.session import AsyncSession
11
-
12
- from planar.ai.agent import Agent
13
- from planar.ai.agent_utils import AgentConfig, agent_configuration
14
- from planar.ai.models import AgentSerializeable
15
- from planar.ai.utils import serialize_agent
16
- from planar.object_config.object_config import ObjectConfigurationBase
17
-
18
-
19
- class InputModelForTest(BaseModel):
20
- """Test input model for agents."""
21
-
22
- text: str
23
- value: int
24
-
25
-
26
- class OutputModelForTest(BaseModel):
27
- """Test output model for agents."""
28
-
29
- result: str
30
- score: float
31
-
32
-
33
- @pytest.fixture
34
- def test_agent():
35
- """Create a test agent with various configurations."""
36
- return Agent(
37
- name="test_serialization_agent",
38
- system_prompt="Test system prompt",
39
- user_prompt="Test user prompt: {input}",
40
- model="openai:gpt-4o",
41
- max_turns=3,
42
- input_type=InputModelForTest,
43
- output_type=OutputModelForTest,
44
- )
45
-
46
-
47
- @pytest.fixture
48
- def test_agent_with_tools():
49
- """Create a test agent with tools."""
50
-
51
- async def test_tool(param: str) -> str:
52
- """A test tool."""
53
- return f"Processed: {param}"
54
-
55
- return Agent(
56
- name="test_agent_with_tools",
57
- system_prompt="System with tools",
58
- user_prompt="User: {input}",
59
- model="anthropic:claude-3-5-sonnet-latest",
60
- max_turns=5,
61
- tools=[test_tool],
62
- )
63
-
64
-
65
- async def test_serialize_agent_basic(session: AsyncSession, test_agent):
66
- """Test basic agent serialization without any configurations."""
67
-
68
- # Serialize the agent
69
- serialized = await serialize_agent(test_agent)
70
-
71
- # Verify basic fields
72
- assert isinstance(serialized, AgentSerializeable)
73
- assert serialized.name == "test_serialization_agent"
74
- assert serialized.input_schema is not None
75
- assert serialized.output_schema is not None
76
- assert serialized.tool_definitions == []
77
-
78
- # Verify configs field exists and contains the default config (at least one config always present)
79
- assert hasattr(serialized, "configs")
80
- assert len(serialized.configs) == 1
81
-
82
- # Verify the default config is present and correct
83
- default_config = serialized.configs[-1]
84
- assert isinstance(default_config, ObjectConfigurationBase)
85
- assert default_config.version == 0
86
- assert default_config.data.system_prompt == test_agent.system_prompt
87
- assert default_config.data.user_prompt == test_agent.user_prompt
88
- assert default_config.data.model == str(test_agent.model)
89
- assert default_config.data.max_turns == test_agent.max_turns
90
- assert default_config.data.model_parameters == test_agent.model_parameters
91
-
92
- # Verify overwrites field is removed
93
- assert not hasattr(serialized, "overwrites")
94
-
95
-
96
- async def test_serialize_agent_with_configs(session: AsyncSession, test_agent):
97
- """Test agent serialization with multiple configurations."""
98
-
99
- # Create multiple configurations
100
- config1 = AgentConfig(
101
- system_prompt="Override system 1",
102
- user_prompt="Override user 1: {input}",
103
- model="openai:gpt-4o",
104
- max_turns=2,
105
- model_parameters={"temperature": 0.7},
106
- )
107
-
108
- config2 = AgentConfig(
109
- system_prompt="Override system 2",
110
- user_prompt="Override user 2: {input}",
111
- model="anthropic:claude-3-opus",
112
- max_turns=4,
113
- model_parameters={"temperature": 0.9},
114
- )
115
-
116
- # Write configurations
117
- await agent_configuration.write_config(test_agent.name, config1)
118
- await agent_configuration.write_config(test_agent.name, config2)
119
-
120
- # Serialize the agent
121
- serialized = await serialize_agent(test_agent)
122
-
123
- # Verify configs are included
124
- assert len(serialized.configs) == 3
125
-
126
- # Verify default config is included
127
- default_config = serialized.configs[-1]
128
- assert isinstance(default_config, ObjectConfigurationBase)
129
- assert default_config.version == 0
130
- assert default_config.data.system_prompt == test_agent.system_prompt
131
- assert default_config.data.user_prompt == test_agent.user_prompt
132
- assert default_config.data.model == str(test_agent.model)
133
- assert default_config.data.max_turns == test_agent.max_turns
134
- assert default_config.data.model_parameters == test_agent.model_parameters
135
-
136
- # Verify configs are ordered by version (descending)
137
- assert all(
138
- isinstance(config, ObjectConfigurationBase) for config in serialized.configs
139
- )
140
- assert serialized.configs[0].version == 2 # Latest version first
141
- assert serialized.configs[1].version == 1
142
-
143
- # Verify config data
144
- latest_config = serialized.configs[0]
145
- assert latest_config.data.system_prompt == "Override system 2"
146
- assert latest_config.data.user_prompt == "Override user 2: {input}"
147
- assert latest_config.data.model == "anthropic:claude-3-opus"
148
- assert latest_config.data.max_turns == 4
149
-
150
- older_config = serialized.configs[1]
151
- assert older_config.data.system_prompt == "Override system 1"
152
- assert older_config.data.user_prompt == "Override user 1: {input}"
153
-
154
-
155
- async def test_serialize_agent_with_tools(session: AsyncSession, test_agent_with_tools):
156
- """Test serialization of agent with tools."""
157
-
158
- # Serialize the agent
159
- serialized = await serialize_agent(test_agent_with_tools)
160
-
161
- # Verify tool definitions are included
162
- assert len(serialized.tool_definitions) == 1
163
- tool_def = serialized.tool_definitions[0]
164
- assert tool_def["name"] == "test_tool"
165
- assert tool_def["description"] == "A test tool."
166
- assert "parameters" in tool_def
167
-
168
-
169
- async def test_serialize_agent_no_duplicate_fields(session: AsyncSession, test_agent):
170
- """Test that AgentSerializeable doesn't duplicate fields from AgentConfig."""
171
-
172
- # Create a configuration
173
- config = AgentConfig(
174
- system_prompt="Config system",
175
- user_prompt="Config user: {input}",
176
- model="openai:gpt-3.5-turbo",
177
- max_turns=1,
178
- model_parameters={},
179
- )
180
-
181
- await agent_configuration.write_config(test_agent.name, config)
182
-
183
- # Serialize the agent
184
- serialized = await serialize_agent(test_agent)
185
-
186
- # Verify that system_prompt, user_prompt, model, max_turns are NOT in the serialized object
187
- # They should only be in the configs
188
- assert not hasattr(serialized, "system_prompt")
189
- assert not hasattr(serialized, "user_prompt")
190
- assert not hasattr(serialized, "model")
191
- assert not hasattr(serialized, "max_turns")
192
-
193
- # These fields should only be accessible through configs
194
- assert serialized.configs[0].data.system_prompt == "Config system"
195
- assert serialized.configs[0].data.user_prompt == "Config user: {input}"
196
- assert serialized.configs[0].data.model == "openai:gpt-3.5-turbo"
197
- assert serialized.configs[0].data.max_turns == 1
198
-
199
-
200
- async def test_agent_serializable_structure():
201
- """Test the structure of AgentSerializeable model."""
202
- # Verify the model has the expected fields
203
- fields = AgentSerializeable.model_fields.keys()
204
-
205
- # Should have these fields
206
- assert "name" in fields
207
- assert "input_schema" in fields
208
- assert "output_schema" in fields
209
- assert "tool_definitions" in fields
210
- assert "configs" in fields
211
- assert "built_in_vars" in fields
212
-
213
- # Should NOT have these fields (moved to configs)
214
- assert "system_prompt" not in fields
215
- assert "user_prompt" not in fields
216
- assert "model" not in fields
217
- assert "max_turns" not in fields
218
- assert "overwrites" not in fields
219
-
220
-
221
- async def test_configs_field_type():
222
- """Test that configs field has the correct type annotation."""
223
- # Get the type annotation for configs field
224
- configs_field = AgentSerializeable.model_fields["configs"]
225
-
226
- # The annotation should be list[ObjectConfigurationBase[AgentConfig]]
227
- # This is a complex type, so we'll check the string representation
228
- assert "ObjectConfigurationBase" in str(configs_field.annotation)
229
- assert "AgentConfig" in str(configs_field.annotation)