planar 0.9.2__py3-none-any.whl → 0.9.3__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.
- planar/ai/agent.py +1 -0
- planar/ai/test_agent_tool_step_display.py +78 -0
- planar/cli.py +9 -1
- planar/db/alembic/versions/8855a78a408f_message_step_type.py +30 -0
- planar/db/alembic.ini +5 -5
- planar/files/__init__.py +3 -0
- planar/scaffold_templates/main.py.j2 +6 -1
- planar/testing/fixtures.py +2 -2
- planar/utils.py +17 -0
- planar/workflows/contrib.py +8 -0
- planar/workflows/models.py +1 -0
- planar/workflows/test_workflow.py +39 -1
- {planar-0.9.2.dist-info → planar-0.9.3.dist-info}/METADATA +16 -17
- {planar-0.9.2.dist-info → planar-0.9.3.dist-info}/RECORD +16 -14
- {planar-0.9.2.dist-info → planar-0.9.3.dist-info}/WHEEL +1 -1
- {planar-0.9.2.dist-info → planar-0.9.3.dist-info}/entry_points.txt +0 -0
planar/ai/agent.py
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
import os
|
2
|
+
from unittest.mock import patch
|
3
|
+
|
4
|
+
from sqlmodel import col, select
|
5
|
+
|
6
|
+
from planar.ai import models as m
|
7
|
+
from planar.ai.agent import Agent
|
8
|
+
from planar.ai.pydantic_ai import ModelRunResponse
|
9
|
+
from planar.workflows.decorators import workflow
|
10
|
+
from planar.workflows.execution import execute
|
11
|
+
from planar.workflows.models import StepType, WorkflowStep
|
12
|
+
|
13
|
+
|
14
|
+
async def test_agent_tool_step_has_display_name(session):
|
15
|
+
async def add(a: int, b: int) -> int:
|
16
|
+
return a + b
|
17
|
+
|
18
|
+
# Prepare mocked model responses: first triggers a tool call, then returns final content
|
19
|
+
first = ModelRunResponse[str](
|
20
|
+
response=m.CompletionResponse[str](
|
21
|
+
content=None,
|
22
|
+
tool_calls=[
|
23
|
+
m.ToolCall(id="call_1", name="add", arguments={"a": 2, "b": 3})
|
24
|
+
],
|
25
|
+
text_content="",
|
26
|
+
reasoning_content=None,
|
27
|
+
),
|
28
|
+
extra_turns_used=0,
|
29
|
+
)
|
30
|
+
second = ModelRunResponse[str](
|
31
|
+
response=m.CompletionResponse[str](
|
32
|
+
content="5",
|
33
|
+
tool_calls=[],
|
34
|
+
text_content="5",
|
35
|
+
reasoning_content=None,
|
36
|
+
),
|
37
|
+
extra_turns_used=0,
|
38
|
+
)
|
39
|
+
|
40
|
+
responses = [first, second]
|
41
|
+
|
42
|
+
async def fake_model_run(*args, **kwargs):
|
43
|
+
assert responses, "No more fake responses configured"
|
44
|
+
return responses.pop(0)
|
45
|
+
|
46
|
+
# Patch the model run to avoid any network/model dependency
|
47
|
+
# Use unittest.mock.patch context managers to ensure cleanup
|
48
|
+
with (
|
49
|
+
patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False),
|
50
|
+
patch("planar.ai.agent.model_run", side_effect=fake_model_run),
|
51
|
+
):
|
52
|
+
agent = Agent[str, str](
|
53
|
+
name="test_agent",
|
54
|
+
system_prompt="",
|
55
|
+
user_prompt="",
|
56
|
+
model="openai:gpt-4o-mini",
|
57
|
+
tools=[add],
|
58
|
+
max_turns=3,
|
59
|
+
)
|
60
|
+
|
61
|
+
@workflow()
|
62
|
+
async def run_agent():
|
63
|
+
result = await agent("please add")
|
64
|
+
return result.output
|
65
|
+
|
66
|
+
wf = await run_agent.start()
|
67
|
+
result = await execute(wf)
|
68
|
+
assert result == "5"
|
69
|
+
|
70
|
+
steps = (
|
71
|
+
await session.exec(select(WorkflowStep).order_by(col(WorkflowStep.step_id)))
|
72
|
+
).all()
|
73
|
+
# Ensure there is a tool call step with the display name set to the tool name
|
74
|
+
tool_steps = [s for s in steps if s.step_type == StepType.TOOL_CALL]
|
75
|
+
assert tool_steps, "Expected at least one TOOL_CALL step recorded"
|
76
|
+
assert any(s.display_name == "add" for s in tool_steps), (
|
77
|
+
f"Expected a TOOL_CALL step with display_name 'add', got {[s.display_name for s in tool_steps]}"
|
78
|
+
)
|
planar/cli.py
CHANGED
@@ -175,9 +175,17 @@ def run_command(
|
|
175
175
|
|
176
176
|
try:
|
177
177
|
result = subprocess.run(
|
178
|
-
["uv", "run", str(app_path)], env=os.environ.copy(), check=
|
178
|
+
["uv", "run", str(app_path)], env=os.environ.copy(), check=False
|
179
179
|
)
|
180
|
+
if result.returncode != 0:
|
181
|
+
typer.echo(
|
182
|
+
f"Error running script: Process exited with code {result.returncode}",
|
183
|
+
err=True,
|
184
|
+
)
|
180
185
|
raise typer.Exit(code=result.returncode)
|
186
|
+
except typer.Exit:
|
187
|
+
# Re-raise typer.Exit without modification
|
188
|
+
raise
|
181
189
|
except subprocess.CalledProcessError as e:
|
182
190
|
typer.echo(f"Error running script: {e}", err=True)
|
183
191
|
raise typer.Exit(code=e.returncode)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
"""Add MESSAGE to step_type enum
|
2
|
+
|
3
|
+
Revision ID: 8855a78a408f
|
4
|
+
Revises: 3476068c153c
|
5
|
+
Create Date: 2025-09-16 16:19:25.917861
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
from typing import Sequence, Union
|
10
|
+
|
11
|
+
from alembic import op
|
12
|
+
|
13
|
+
# revision identifiers, used by Alembic.
|
14
|
+
revision: str = "8855a78a408f"
|
15
|
+
down_revision: Union[str, None] = "3476068c153c"
|
16
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
17
|
+
depends_on: Union[str, Sequence[str], None] = None
|
18
|
+
|
19
|
+
|
20
|
+
def upgrade() -> None:
|
21
|
+
if op.get_context().dialect.name != "sqlite":
|
22
|
+
op.execute("ALTER TYPE steptype ADD VALUE 'MESSAGE'")
|
23
|
+
|
24
|
+
|
25
|
+
def downgrade() -> None:
|
26
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
27
|
+
# Rolling this back would require updating any MESSAGE `WorkflowStep` rows to a different
|
28
|
+
# step type or deleting them before running a migration to drop the MESSAGE value.
|
29
|
+
pass
|
30
|
+
# ### end Alembic commands ###
|
planar/db/alembic.ini
CHANGED
@@ -62,12 +62,12 @@ version_path_separator = os
|
|
62
62
|
# output_encoding = utf-8
|
63
63
|
|
64
64
|
# Development database for generating system migrations
|
65
|
-
# It's safer for us to use a local postgres database for generating and testing migrations rather than sqlite,
|
65
|
+
# It's safer for us to use a local postgres database for generating and testing migrations rather than sqlite,
|
66
66
|
# to be sure they'll work in production deployments.
|
67
|
-
# Using postgres as the dev database for autogenerating revisions also is better because
|
68
|
-
# we don't have the weird schema issues of Sqlite. Alembic doesn't fully support `schema_translate_map`
|
69
|
-
# feature in SA that we use to remap `planar`->None in SQLite (due to it not supporting schemas),
|
70
|
-
# so it sometimes incorrectly thinks it needs to re-generate things (like indices) that already
|
67
|
+
# Using postgres as the dev database for autogenerating revisions also is better because
|
68
|
+
# we don't have the weird schema issues of Sqlite. Alembic doesn't fully support `schema_translate_map`
|
69
|
+
# feature in SA that we use to remap `planar`->None in SQLite (due to it not supporting schemas),
|
70
|
+
# so it sometimes incorrectly thinks it needs to re-generate things (like indices) that already
|
71
71
|
# exist in the database from a prior migration. Using postgres obviates that issue.
|
72
72
|
# https://github.com/sqlalchemy/alembic/issues/555
|
73
73
|
sqlalchemy.url = postgresql+psycopg2://postgres:postgres@localhost:5432/postgres
|
planar/files/__init__.py
CHANGED
planar/testing/fixtures.py
CHANGED
@@ -132,7 +132,7 @@ def tmp_postgresql_container():
|
|
132
132
|
"--name",
|
133
133
|
container_name,
|
134
134
|
"-e",
|
135
|
-
"POSTGRES_PASSWORD=
|
135
|
+
"POSTGRES_PASSWORD=postgres",
|
136
136
|
"-p",
|
137
137
|
"127.0.0.1:5432:5432",
|
138
138
|
"docker.io/library/postgres",
|
@@ -205,7 +205,7 @@ def tmp_postgresql_url(request):
|
|
205
205
|
if process.returncode != 0:
|
206
206
|
raise Exception("Failed to create database")
|
207
207
|
|
208
|
-
url = f"postgresql+asyncpg://postgres:
|
208
|
+
url = f"postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/{db_name}"
|
209
209
|
|
210
210
|
try:
|
211
211
|
yield url
|
planar/utils.py
CHANGED
@@ -108,3 +108,20 @@ def partition[T](
|
|
108
108
|
false_items.append(item)
|
109
109
|
|
110
110
|
return false_items, true_items
|
111
|
+
|
112
|
+
|
113
|
+
def one_or_raise[T](iterable: Iterable[T]) -> T:
|
114
|
+
"""Extract the single element from an iterable or raise an exception."""
|
115
|
+
iterator = iter(iterable)
|
116
|
+
try:
|
117
|
+
value = next(iterator)
|
118
|
+
except StopIteration:
|
119
|
+
raise ValueError("Expected exactly one element, but iterable is empty")
|
120
|
+
|
121
|
+
try:
|
122
|
+
next(iterator)
|
123
|
+
raise ValueError(
|
124
|
+
"Expected exactly one element, but iterable contains multiple elements"
|
125
|
+
)
|
126
|
+
except StopIteration:
|
127
|
+
return value
|
planar/workflows/contrib.py
CHANGED
@@ -2,12 +2,15 @@ from datetime import datetime, timedelta
|
|
2
2
|
from functools import wraps
|
3
3
|
from typing import Any, Callable, Coroutine, Dict
|
4
4
|
|
5
|
+
from pydantic.main import BaseModel
|
6
|
+
|
5
7
|
from planar.logging import get_logger
|
6
8
|
from planar.session import get_session
|
7
9
|
from planar.utils import P, T, U, utc_now
|
8
10
|
from planar.workflows import step
|
9
11
|
from planar.workflows.context import get_context
|
10
12
|
from planar.workflows.events import check_event_exists, get_latest_event
|
13
|
+
from planar.workflows.models import StepType
|
11
14
|
from planar.workflows.step_core import Suspend, suspend_workflow
|
12
15
|
from planar.workflows.tracing import trace
|
13
16
|
|
@@ -19,6 +22,11 @@ async def get_deadline(max_wait_time: float) -> datetime:
|
|
19
22
|
return utc_now() + timedelta(seconds=max_wait_time)
|
20
23
|
|
21
24
|
|
25
|
+
@step(step_type=StepType.MESSAGE)
|
26
|
+
async def message(message: str | BaseModel):
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
22
30
|
@step(display_name="Wait for event")
|
23
31
|
async def wait_for_event(
|
24
32
|
event_key: str,
|
planar/workflows/models.py
CHANGED
@@ -14,7 +14,8 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|
14
14
|
|
15
15
|
from planar.session import get_session
|
16
16
|
from planar.testing.workflow_observer import WorkflowObserver
|
17
|
-
from planar.utils import utc_now
|
17
|
+
from planar.utils import one_or_raise, utc_now
|
18
|
+
from planar.workflows.contrib import message
|
18
19
|
from planar.workflows.decorators import (
|
19
20
|
__AS_STEP_CACHE,
|
20
21
|
__is_workflow_step,
|
@@ -1965,3 +1966,40 @@ async def test_child_workflow_called_as_start_step(session: AsyncSession):
|
|
1965
1966
|
assert child_wf.parent_id is None
|
1966
1967
|
assert child_wf.status == WorkflowStatus.SUCCEEDED
|
1967
1968
|
assert child_wf.result == "child_result"
|
1969
|
+
|
1970
|
+
|
1971
|
+
# =============================================================================
|
1972
|
+
# Test for message steps
|
1973
|
+
# =============================================================================
|
1974
|
+
class Example(BaseModel):
|
1975
|
+
id: int
|
1976
|
+
msg: str
|
1977
|
+
|
1978
|
+
|
1979
|
+
@pytest.mark.parametrize("input", ["hello", Example(id=1, msg="hello")])
|
1980
|
+
async def test_message(session: AsyncSession, input: str | BaseModel):
|
1981
|
+
@workflow()
|
1982
|
+
async def msg_workflow(msg: str | BaseModel):
|
1983
|
+
await message(msg)
|
1984
|
+
|
1985
|
+
async with WorkflowOrchestrator.ensure_started() as orchestrator:
|
1986
|
+
wf = await msg_workflow.start(input)
|
1987
|
+
await orchestrator.wait_for_completion(wf.id)
|
1988
|
+
|
1989
|
+
await session.refresh(wf)
|
1990
|
+
steps = (
|
1991
|
+
await session.exec(
|
1992
|
+
select(WorkflowStep).where(WorkflowStep.workflow_id == wf.id)
|
1993
|
+
)
|
1994
|
+
).all()
|
1995
|
+
|
1996
|
+
step = one_or_raise(steps)
|
1997
|
+
# We recorded a single `WorkflowStep` of type `MESSAGE` to the DB.
|
1998
|
+
assert step.status is StepStatus.SUCCEEDED
|
1999
|
+
assert step.step_type is StepType.MESSAGE
|
2000
|
+
if isinstance(input, str):
|
2001
|
+
assert step.args == [input]
|
2002
|
+
else:
|
2003
|
+
assert step.args == [input.model_dump()]
|
2004
|
+
assert not step.kwargs
|
2005
|
+
assert step.result is None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: planar
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.3
|
4
4
|
Summary: Add your description here
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
6
6
|
Requires-Dist: aiofiles>=24.1.0
|
@@ -55,21 +55,22 @@ The workflow system in Planar is a sophisticated orchestration framework that en
|
|
55
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
56
|
be suspended and resumed.
|
57
57
|
2. Key Features:
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
63
|
3. Main Components:
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
68
|
4. REST API Integration:
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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.
|
73
74
|
|
74
75
|
### Coroutines and the suspension mechanism
|
75
76
|
Coroutines are the heart of Planar's workflow system. Here's how they work:
|
@@ -278,7 +279,7 @@ We use pytest for testing Planar:
|
|
278
279
|
To test with PostgreSQL locally, you'll need a PostgreSQL container running:
|
279
280
|
|
280
281
|
```bash
|
281
|
-
docker run --restart=always --name planar-postgres -e POSTGRES_PASSWORD=
|
282
|
+
docker run --restart=always --name planar-postgres -e POSTGRES_PASSWORD=postgres -p 127.0.0.1:5432:5432 -d docker.io/library/postgres
|
282
283
|
```
|
283
284
|
|
284
285
|
Ensure the container name is `planar-postgres`.
|
@@ -320,5 +321,3 @@ To install cairo, run the following command:
|
|
320
321
|
brew install cairo libffi pkg-config
|
321
322
|
export DYLD_FALLBACK_LIBRARY_PATH="/opt/homebrew/lib:${DYLD_FALLBACK_LIBRARY_PATH}"
|
322
323
|
```
|
323
|
-
|
324
|
-
|
@@ -1,14 +1,15 @@
|
|
1
1
|
planar/__init__.py,sha256=FAYRGjuJOH2Y_XYFA0-BrRFjuKdPzIShNbaYwJbtu6A,499
|
2
2
|
planar/ai/__init__.py,sha256=ABOKvqQOLlVJkptcvXcuLjVZZWEsK8h-1RyFGK7kib8,231
|
3
|
-
planar/ai/agent.py,sha256=
|
3
|
+
planar/ai/agent.py,sha256=5U2dKIr_vy8ItLaj91uSbVX90DIy1OGRBzLbMyk2gbQ,12481
|
4
4
|
planar/ai/agent_base.py,sha256=rdK5ExCpkPf5sdVy-Wo5MKAx2O_GULFCwA24s0XO6Ek,5462
|
5
5
|
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/test_agent_serialization.py,sha256=zYLIxhYdFhOZzBrEBoQNyYLyNcNxWwaMTkjt_ARTkZk,8073
|
9
|
+
planar/ai/test_agent_tool_step_display.py,sha256=GswT9wET4-vFnohOwIgP6-0r_-wt1vwpThmAD9ubATw,2582
|
9
10
|
planar/ai/utils.py,sha256=WVBW0TGaoKytC4bNd_a9lXrBf5QsDRut4GBcA53U2Ww,3116
|
10
11
|
planar/app.py,sha256=VEs4jDlcisyOy9I9zEGMG_-Qm8ULKT36CSHjqrYit3o,18491
|
11
|
-
planar/cli.py,sha256=
|
12
|
+
planar/cli.py,sha256=SIyQOY3MbNDuohNcXFRIrHJuGxFNNC8C_ihfCXIUvbE,9900
|
12
13
|
planar/config.py,sha256=6J42G9rEVUiOyCAY3EwUTU3PPmWthGTnrHMzST9TMcc,17809
|
13
14
|
planar/data/__init__.py,sha256=LwrWl925w1CN0aW645Wpj_kDp0B8j5SsPzjr9iyrcmI,285
|
14
15
|
planar/data/config.py,sha256=zp6ChI_2MUMbupEVQNY-BxzcdLvejXG33DCp0BujGVU,1209
|
@@ -19,10 +20,11 @@ planar/db/__init__.py,sha256=SNgB6unQ1f1E9dB9O-KrsPsYM17KLsgOW1u0ajqs57I,318
|
|
19
20
|
planar/db/alembic/env.py,sha256=UlOrLBfFJ-WbNK0R1cgS2MC3yrqeE4-6rIirB3rGLYo,5344
|
20
21
|
planar/db/alembic/script.py.mako,sha256=BgXfi4ClINnJU-PaaWqh1-Sjqu4brkWpbVd-0rEPzLU,665
|
21
22
|
planar/db/alembic/versions/3476068c153c_initial_system_tables_migration.py,sha256=1FbzJyfapjegM-Mxd3HMMVA-8zVU6AnrnzEgIoc6eoQ,13204
|
22
|
-
planar/db/alembic.
|
23
|
+
planar/db/alembic/versions/8855a78a408f_message_step_type.py,sha256=iH13r8swy79lw8icGNKW1lqN09TX93MvR1zi-qvWNlU,869
|
24
|
+
planar/db/alembic.ini,sha256=FI1S0DlTn7IVp3-eT17PNxbVBbqhn84k2VzwRHpNz_Q,4304
|
23
25
|
planar/db/db.py,sha256=VNpHH1R62tdWVLIV1I2ULmw3B8M6-RsM2ALG3VAVjSg,12790
|
24
26
|
planar/dependencies.py,sha256=PH78fGk3bQfGnz-AphxH49307Y0XVgl3EY0LdGJnoik,1008
|
25
|
-
planar/files/__init__.py,sha256=
|
27
|
+
planar/files/__init__.py,sha256=uXqwnoIaJAuDYXFA-9gqcSq1R4mZLNyfem1zZyGI5Ek,206
|
26
28
|
planar/files/models.py,sha256=zbZvMkoqoSnn7yOo26SRtEgtlHJbFIvwSht75APHQXk,6145
|
27
29
|
planar/files/storage/azure_blob.py,sha256=PzCm8ZpyAMH9-N6VscTlLpud-CBLcQX9qC6YjbOSfZg,12316
|
28
30
|
planar/files/storage/base.py,sha256=KO7jyKwjKg5fNSLvhxJWE-lsypv6LXXf7bgA34aflwY,2495
|
@@ -105,7 +107,7 @@ planar/rules/test_rules.py,sha256=6M7CSg1bwn7O7DOoNi38vyVG4UmPQfRFxEO9qGE6rz0,52
|
|
105
107
|
planar/scaffold_templates/app/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
106
108
|
planar/scaffold_templates/app/db/entities.py.j2,sha256=wg9O3JtRaRMKlDtoWHHodyNRL0s1UILvsr9fCQ_O2-4,279
|
107
109
|
planar/scaffold_templates/app/flows/process_invoice.py.j2,sha256=R3EII_O2DHV1kvffW_AApZyaS6rR9eikcpxI08XH9dI,1691
|
108
|
-
planar/scaffold_templates/main.py.j2,sha256=
|
110
|
+
planar/scaffold_templates/main.py.j2,sha256=zrqsuv3Fp4lcknvB37RrRHy11msdFB1yDguYmTLLPhw,398
|
109
111
|
planar/scaffold_templates/planar.dev.yaml.j2,sha256=I5-IqX7GJm6qA91WtUMw43L4hKACqgnER_H2racim4c,998
|
110
112
|
planar/scaffold_templates/planar.prod.yaml.j2,sha256=FahJ2atDtvVH7IUCatGq6h9hmyF8meeiWC8RLfWphOQ,867
|
111
113
|
planar/scaffold_templates/pyproject.toml.j2,sha256=nFfHWLp0sFK8cqjkdwBm6Hi6xsPzTNkaBeSgdTWTS-Q,183
|
@@ -134,23 +136,23 @@ planar/test_object_registry.py,sha256=R7IwbB2GACm2HUuVZTeVY4V12XB9_JgSSeppPxiCdf
|
|
134
136
|
planar/test_sqlalchemy.py,sha256=QTloaipWiFmlLTBGH6YCRkwi1R27gmQZnwprO7lPLfU,7058
|
135
137
|
planar/test_utils.py,sha256=gKenXotj36SN_bb3bQpYPfD8t06IjnGBQqEgWpujHcA,3086
|
136
138
|
planar/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
137
|
-
planar/testing/fixtures.py,sha256=
|
139
|
+
planar/testing/fixtures.py,sha256=YtSGbSUlGXdreDfTVNKZUaCflvAftYQegaTOQQLOrCA,8672
|
138
140
|
planar/testing/memory_storage.py,sha256=apcuFisC3hW9KiU3kO8zwHQ6oK9Lu20NSX5fJ0LSZUY,2824
|
139
141
|
planar/testing/planar_test_client.py,sha256=qPkI_ZHZho_38PpdSmEjcRBO1iHcIx3dOwo7c02Am10,1979
|
140
142
|
planar/testing/synchronizable_tracer.py,sha256=SWeta1CgwGsN5duC0FR8NyXOQ1b1L8nDpvGdjZVJ9Bg,4938
|
141
143
|
planar/testing/test_memory_storage.py,sha256=So32XL0gbLDFMTl-WJN445x9jL6O8Qsqw8IRaiZnsPs,4797
|
142
144
|
planar/testing/workflow_observer.py,sha256=0Q2xsYuZzNGXHZVwvXBqL9KXPsdIXuSZGBJAxHopzJw,2976
|
143
|
-
planar/utils.py,sha256=
|
145
|
+
planar/utils.py,sha256=YP37-ODS8nYOIfHPo11CwCpQRsg8oc57lQ0wkXwqCyo,3607
|
144
146
|
planar/workflows/__init__.py,sha256=yFrrtKYUCx4jBPpHdEWDfKQgZXzGyr9voj5lFe9C-_w,826
|
145
147
|
planar/workflows/context.py,sha256=93kPSmYniqjX_lv6--eUUPnzZEKZJi6IPaAjrT-hFRY,1271
|
146
|
-
planar/workflows/contrib.py,sha256=
|
148
|
+
planar/workflows/contrib.py,sha256=tUqMZ42Jh8KMy1JP1VFJOD4rsiYxzMTd5pJfe2t3yzk,6650
|
147
149
|
planar/workflows/decorators.py,sha256=Lsq9ZZdY60rv8-9Ok029x_kE4bHBvRqbfWZ8O0QRNfw,7960
|
148
150
|
planar/workflows/events.py,sha256=xYGGTwbKFnqhFFI7SuoFIaEeS5oWOLS-1nP9MW0uOhs,6007
|
149
151
|
planar/workflows/exceptions.py,sha256=G2Q4ZhaJwybMLpnpzXJNvKtFqUsEw7Vh0cRMkVxP7PU,927
|
150
152
|
planar/workflows/execution.py,sha256=8c4a2L1qRMPQrCEJ8-sEk-gJi_xKq5gYKDSWSbSspVI,7479
|
151
153
|
planar/workflows/lock.py,sha256=QU5_y_n8RHOC7ppLicH7yWX-Ni7N93hlB14D2zXOQ8A,8773
|
152
154
|
planar/workflows/misc.py,sha256=g3XVRMeU9mgDRi_6RgFdydLEqvTAg49wbIGlmC7kOu8,140
|
153
|
-
planar/workflows/models.py,sha256=
|
155
|
+
planar/workflows/models.py,sha256=SKBJTGhd4nVWxtlDkaQrU2RvRTtoj07PJhLT73cF_ac,5591
|
154
156
|
planar/workflows/notifications.py,sha256=JrObfWD-uRZJlZLMSDJDqjDuXfYAoRSLfgEeyoy98Vs,3795
|
155
157
|
planar/workflows/orchestrator.py,sha256=rneB1yOPDZiJcHFbD6UDZ4juU77iSBK1eu1gOFm58vM,15480
|
156
158
|
planar/workflows/query.py,sha256=38B5SLwXf3AlA_1ChR5DicFWdcUqzpQzMkuAUCNHafI,8838
|
@@ -163,10 +165,10 @@ planar/workflows/test_concurrency_detection.py,sha256=yfgvLOMkPaK7EiW4ihm1KQx82Y
|
|
163
165
|
planar/workflows/test_lock_timeout.py,sha256=H78N090wJtiEg6SaJosfRWijpX6HwnyWyNNb7WaGPe0,5746
|
164
166
|
planar/workflows/test_serialization.py,sha256=JfaveBRQTNMkucqkTorIMGcvi8S0j6uRtboFaWpCmes,39586
|
165
167
|
planar/workflows/test_suspend_deserialization.py,sha256=ddw2jToSJ-ebQ0RfT7KWTRMCOs1nis1lprQiGIGuaJ0,7751
|
166
|
-
planar/workflows/test_workflow.py,sha256=
|
168
|
+
planar/workflows/test_workflow.py,sha256=hBLPQYqUsWEQ_SopKgi69ckRC5OpmQEBlsPcftGMu_Q,65266
|
167
169
|
planar/workflows/tracing.py,sha256=E7E_kj2VBQisDqrllviIshbvOmB9QcEeRwMapunqio4,2732
|
168
170
|
planar/workflows/wrappers.py,sha256=KON6RGg1D6yStboNbuMEeTXRpPTEa8S6Elh1tOnMAlM,1149
|
169
|
-
planar-0.9.
|
170
|
-
planar-0.9.
|
171
|
-
planar-0.9.
|
172
|
-
planar-0.9.
|
171
|
+
planar-0.9.3.dist-info/WHEEL,sha256=Pi5uDq5Fdo_Rr-HD5h9BiPn9Et29Y9Sh8NhcJNnFU1c,79
|
172
|
+
planar-0.9.3.dist-info/entry_points.txt,sha256=L3T0w9u2UPKWXv6JbXFWKU1d5xyEAq1xVWbpYS6mLNg,96
|
173
|
+
planar-0.9.3.dist-info/METADATA,sha256=TjcslwOgn9XhcvGPMUOyQks5nVakgTeFOFSzahwxcmE,12347
|
174
|
+
planar-0.9.3.dist-info/RECORD,,
|
File without changes
|