planar 0.11.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.
@@ -1,331 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: planar
3
- Version: 0.11.0
4
- Summary: Add your description here
5
- License-Expression: LicenseRef-Proprietary
6
- Requires-Dist: aiofiles>=24.1.0
7
- Requires-Dist: aiosqlite>=0.21.0
8
- Requires-Dist: alembic>=1.14.1
9
- Requires-Dist: anthropic>=0.49.0
10
- Requires-Dist: asyncpg
11
- Requires-Dist: boto3>=1.39.15
12
- Requires-Dist: cedarpy>=4.1.0
13
- Requires-Dist: fastapi[standard]>=0.115.7
14
- Requires-Dist: inflection>=0.5.1
15
- Requires-Dist: openai>=1.75
16
- Requires-Dist: pydantic-ai-slim[anthropic,bedrock,google,openai]>=0.7.5
17
- Requires-Dist: pygments>=2.19.1
18
- Requires-Dist: pyjwt[crypto]
19
- Requires-Dist: python-multipart>=0.0.20
20
- Requires-Dist: sqlalchemy[asyncio]>=2.0.37
21
- Requires-Dist: sqlmodel>=0.0.22
22
- Requires-Dist: typer>=0.15.2
23
- Requires-Dist: typing-extensions>=4.12.2
24
- Requires-Dist: zen-engine>=0.40.0
25
- Requires-Dist: azure-storage-blob>=12.19.0 ; extra == 'azure'
26
- Requires-Dist: azure-identity>=1.15.0 ; extra == 'azure'
27
- Requires-Dist: aiohttp>=3.8.0 ; extra == 'azure'
28
- Requires-Dist: ducklake>=0.1.1 ; extra == 'data'
29
- Requires-Dist: ibis-framework[duckdb]>=10.8.0 ; extra == 'data'
30
- Requires-Dist: polars>=1.31.0 ; extra == 'data'
31
- Requires-Dist: opentelemetry-api>=1.34.1 ; extra == 'otel'
32
- Requires-Dist: opentelemetry-exporter-otlp>=1.34.1 ; extra == 'otel'
33
- Requires-Dist: opentelemetry-instrumentation-logging>=0.55b1 ; extra == 'otel'
34
- Requires-Dist: opentelemetry-sdk>=1.34.1 ; extra == 'otel'
35
- Requires-Python: >=3.12
36
- Provides-Extra: azure
37
- Provides-Extra: data
38
- Provides-Extra: otel
39
- Description-Content-Type: text/markdown
40
-
41
- # Planar
42
-
43
- Planar is a Python framework built on FastAPI and SQLModel that lets you build web services with advanced workflow capabilities. Its core features
44
- include:
45
- 1. Automatic CRUD API generation for your entities
46
- 2. A workflow orchestration system for building complex, resumable business processes
47
- 3. File attachment handling with flexible storage options
48
- 4. Database integration with migration support via Alembic
49
- The framework is designed for building services that need both standard REST API endpoints and more complex stateful workflows. The examples show it
50
- being used for services that manage entities with status transitions and attached files, like invoice processing systems.
51
-
52
- ## Workflow System
53
- The workflow system in Planar is a sophisticated orchestration framework that enables defining, executing, and managing long-running workflows with
54
- persistence. Here's what it does:
55
- 1. Core Concept: Implements a durable workflow system that can survive process restarts by storing workflow state in a database. It allows workflows to
56
- be suspended and resumed.
57
- 2. Key Features:
58
- - Persistent Steps: Each step in a workflow is tracked in the database
59
- - Automatic Retries: Failed steps can be retried automatically
60
- - Suspendable Workflows: Workflows can be suspended and resumed later
61
- - Concurrency Control: Uses a locking mechanism to prevent multiple executions
62
- - Recovery: Can recover from crashes by detecting stalled workflows
63
- 3. Main Components:
64
- - `@workflow` decorator: Marks a function as a workflow with persistence
65
- - `@step` decorator: Wraps function calls inside a workflow to make them resumable
66
- - Suspend class: Allows pausing workflow execution
67
- - workflow_orchestrator: Background task that finds and resumes suspended workflows
68
- 4. REST API Integration:
69
- - Automatically creates API endpoints for starting workflows
70
- - Provides status endpoints to check workflow progress
71
-
72
- This is essentially a state machine for managing long-running business processes that need to be resilient to failures and can span multiple
73
- requests/processes.
74
-
75
- ### Coroutines and the suspension mechanism
76
- Coroutines are the heart of Planar's workflow system. Here's how they work:
77
-
78
- #### Coroutine Usage
79
-
80
- Planar builds on Python's async/await system but adds durability. When you create a workflow:
81
-
82
- ```python
83
- @workflow
84
- async def process_order(order_id: str):
85
- # workflow steps
86
- ```
87
-
88
- The system:
89
-
90
- 1. Enforces that all workflows and steps must be coroutines (`async def`)
91
- 2. Accesses the underlying generator of the coroutine via `coro.__await__()`
92
- 3. Manually drives this generator by calling `next(gen)` and `gen.send(result)`
93
- 4. Intercepts any values yielded from the coroutine to implement suspension
94
-
95
- The `execute()` function (lines 278-335) is the core that drives coroutine execution. It:
96
- - Takes control of the coroutine's generator
97
- - Processes each yielded value
98
- - Handles regular awaits vs. suspensions differently
99
- - Persists workflow state at suspension points
100
-
101
- Suspend Mechanism
102
-
103
- The Suspend class (lines 55-72) enables pausing workflows:
104
-
105
- ```python
106
- class Suspend:
107
- def __init__(self, *, wakeup_at=None, interval=None):
108
- # Set when to wake up
109
-
110
- def __await__(self):
111
- result = yield self
112
- return result
113
- ```
114
-
115
- When you call:
116
- ```python
117
- await suspend(interval=timedelta(minutes=5))
118
- ```
119
-
120
- What happens:
121
- 1. The `suspend()` function uses the `@step()` decorator to mark it as resumable
122
- 2. Inside it creates and awaits a Suspend object
123
- 3. The `__await__` method yields self (the Suspend instance) to the executor
124
- 4. The `execute()` function detects this is a Suspend object (lines 303-307)
125
- 5. It sets the workflow status to SUSPENDED and persists the wake-up time
126
- 6. Later, the orchestrator finds workflows ready to resume based on `wakeup_at`
127
- 7. When resumed, execution continues right after the suspension point
128
-
129
- YieldWrapper
130
-
131
- The YieldWrapper class (lines 48-53) is crucial for handling regular async operations:
132
-
133
- ```python
134
- class YieldWrapper:
135
- def __init__(self, value):
136
- self.value = value
137
- def __await__(self):
138
- return (yield self.value)
139
- ```
140
-
141
- For non-Suspend yields (regular awaits), the system:
142
- 1. Wraps the yielded value in `YieldWrapper`
143
- 2. Awaits it to get the result from `asyncio`
144
- 3. Sends the result back to the workflow's generator
145
-
146
- This allows you to use normal async functions inside workflows:
147
-
148
- ```python
149
- @workflow
150
- async def my_workflow():
151
- # This works because YieldWrapper passes through regular awaits
152
- data = await fetch_data_from_api()
153
- # This suspends the workflow
154
- await suspend(interval=timedelta(hours=1))
155
- # When resumed days later, continues here
156
- return process_result(data)
157
- ```
158
-
159
- The magic is that the workflow appears to be a normal async function, but the state is persisted across suspensions, allowing workflows to survive
160
- process restarts or even server reboots.
161
-
162
-
163
- ## Getting started
164
-
165
- Install dependencies: `uv sync --extra otel`
166
-
167
- ## Using the Planar CLI
168
-
169
- Planar includes a command-line interface (CLI) for running applications with environment-specific configurations:
170
-
171
- ### Creating a New Project
172
-
173
- ```bash
174
- planar scaffold [OPTIONS]
175
- ```
176
-
177
- Options:
178
- - `--name TEXT`: Name of the new project (will prompt if not provided)
179
- - `--directory PATH`: Target directory for the project (default: current directory)
180
-
181
- The scaffold command creates a new Planar project with:
182
- - Basic project structure with `app/` directory
183
- - Example invoice processing workflow with AI agent integration
184
- - Database entity definitions
185
- - Development and production configuration files
186
- - Ready-to-use pyproject.toml with Planar dependency
187
-
188
- ### Running in Development Mode
189
-
190
- ```bash
191
- planar dev [PATH] [OPTIONS]
192
- ```
193
-
194
- Arguments:
195
- - `[PATH]`: Optional path to the Python file containing the Planar app instance. Defaults to searching for `app.py` or `main.py` in the current directory.
196
-
197
- Options:
198
- - `--port INTEGER`: Port to run on (default: 8000)
199
- - `--host TEXT`: Host to run on (default: 127.0.0.1)
200
- - `--config PATH`: Path to config file. If set, overrides default config file lookup.
201
- - `--app TEXT`: Name of the PlanarApp instance variable within the file (default: 'app').
202
-
203
- Development mode enables:
204
- - Hot reloading on code changes
205
- - Defaults to development-friendly CORS settings (allows localhost origins)
206
- - Sets `PLANAR_ENV=dev`
207
-
208
- ### Running in Production Mode
209
-
210
- ```bash
211
- planar prod [PATH] [OPTIONS]
212
- ```
213
-
214
- Arguments & Options:
215
- - Same as `dev` mode, but with production defaults.
216
- - Default host is 0.0.0.0 (accessible externally)
217
- - Defaults to stricter CORS settings for production use
218
- - Hot reloading disabled for better performance
219
- - Sets `PLANAR_ENV=prod`
220
-
221
- ### Configuration Loading Logic
222
-
223
- When the Planar application starts (typically via the `planar dev` or `planar prod` CLI commands), it determines the configuration settings using the following process:
224
-
225
- 1. **Determine Base Configuration:** Based on the environment (`dev` or `prod`, controlled by `PLANAR_ENV` or the CLI command used), Planar establishes a set of built-in default settings (e.g., default database path, CORS settings, debug flags).
226
- 2. **Configuration Override File:** Planar searches for a single YAML configuration file to use for overriding the defaults, checking in this specific order:
227
- * **a. Explicit Path:** Checks if the `--config PATH` option was used or if the `PLANAR_CONFIG` environment variable is set. If either is present and points to an existing file, that file is selected as the config override file.
228
- * **b. Environment-Specific File:** If no explicit path was provided, it looks for `planar.{env}.yaml` (e.g., `planar.dev.yaml` for the `dev` environment) in both the app directory and the current directory. If found, this file is selected.
229
- * **c. Generic File:** If neither an explicit path nor an environment-specific file was found, it looks for `planar.yaml` in the current directory. If found, this file is selected.
230
-
231
- **Important Note:** This configuration loading logic is bypassed entirely if you initialize the `PlanarApp` instance in your Python code by directly passing a `PlanarConfig` object to its `config` parameter.
232
-
233
- Example override file (`planar.dev.yaml` or `planar.yaml`):
234
- This file only needs to contain the settings you wish to override from the defaults.
235
-
236
- ```yaml
237
- # Example: Only override AI provider keys and SQLAlchemy debug setting
238
-
239
- # Settings not specified here (like db_connections, app config, cors)
240
- # will retain their default values for the 'dev' environment after merging.
241
-
242
- ai_providers:
243
- openai:
244
- api_key: ${OPENAI_API_KEY} # Read API key from environment variable
245
-
246
- # Optional: Override a specific nested value
247
- # storage:
248
- # directory: .custom_dev_files
249
-
250
-
251
- # Optional: setup logging config
252
- logging:
253
- planar:
254
- level: INFO # enable INFO level logging for all modules in the "planar" package.
255
- planar.workflows:
256
- level: DEBUG # enable DEBUG level logging for all modules in the "planar.workflows" package.
257
- ```
258
-
259
- ## To run the examples
260
-
261
- - `uv run planar dev examples/expense_approval_workflow/main.py`
262
- - `uv run planar dev examples/event_based_workflow/main.py`
263
- - `uv run planar dev examples/simple_service/main.py`
264
-
265
- The API docs can then be accessed on http://127.0.0.1:8000/docs
266
-
267
-
268
- ## Planar Development
269
-
270
- ### Testing
271
- We use pytest for testing Planar:
272
-
273
- - To run the tests: `uv run pytest`
274
- - By default, tests only run on SQLite using temporary databases
275
- - In CI/CD we also test PostgreSQL
276
-
277
- ### Testing with PostgreSQL locally
278
-
279
- To test with PostgreSQL locally, you'll need a PostgreSQL container running:
280
-
281
- ```bash
282
- docker run --restart=always --name planar-postgres -e POSTGRES_PASSWORD=postgres -p 127.0.0.1:5432:5432 -d docker.io/library/postgres
283
- ```
284
-
285
- Ensure the container name is `planar-postgres`.
286
-
287
- To run tests with PostgreSQL:
288
-
289
- ```bash
290
- PLANAR_TEST_POSTGRESQL=1 PLANAR_TEST_POSTGRESQL_CONTAINER=planar-postgres uv run pytest -s
291
- ```
292
-
293
- To disable SQLite testing:
294
-
295
- ```bash
296
- PLANAR_TEST_SQLITE=0 uv run pytest
297
- ```
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
-
307
- ### Pre-commit hooks
308
-
309
- We use [pre-commit](https://pre-commit.com/) to manage pre-commit hooks. To install the pre-commit hooks, run the following command:
310
-
311
- ```bash
312
- uv tool install pre-commit
313
- uv tool run pre-commit install
314
- ```
315
-
316
- ### Cairo SVG
317
-
318
- For some tests we use Cairo SVG to PNG conversion. This is done using the [cairosvg](https://cairosvg.org/) library, but requires the core Cairo libraries to be installed in the system.
319
-
320
- #### Linux
321
-
322
- Cairo dependencies should already be installed.
323
-
324
- #### MacOS
325
-
326
- To install cairo, run the following command:
327
-
328
- ```bash
329
- brew install cairo libffi pkg-config
330
- export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib:${DYLD_FALLBACK_LIBRARY_PATH}"
331
- ```