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 CHANGED
@@ -289,6 +289,7 @@ class Agent[
289
289
  tool_result = await self.as_step_if_durable(
290
290
  tool_fn,
291
291
  step_type=StepType.TOOL_CALL,
292
+ display_name=tool_call.name,
292
293
  )(**tool_call.arguments)
293
294
  logger.info(
294
295
  "tool executed by agent",
@@ -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=True
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
@@ -1,2 +1,5 @@
1
1
  from .models import PlanarFile, PlanarFileMetadata # noqa: F401
2
2
  from .storage.context import get_storage # noqa: F401
3
+
4
+ # re-export PlanarFile
5
+ __all__ = ["PlanarFile", "PlanarFileMetadata", "get_storage"]
@@ -10,4 +10,9 @@ app = (
10
10
  .register_entity(Invoice)
11
11
  .register_workflow(process_invoice)
12
12
  .register_agent(invoice_agent)
13
- )
13
+ )
14
+
15
+
16
+ if __name__ == "__main__":
17
+ print("Planar app is ready!")
18
+ exit(0)
@@ -132,7 +132,7 @@ def tmp_postgresql_container():
132
132
  "--name",
133
133
  container_name,
134
134
  "-e",
135
- "POSTGRES_PASSWORD=123",
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:123@127.0.0.1:5432/{db_name}"
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
@@ -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,
@@ -30,6 +30,7 @@ class StepType(str, Enum):
30
30
  RULE = "rule"
31
31
  HUMAN_IN_THE_LOOP = "human_in_the_loop"
32
32
  TOOL_CALL = "tool_call"
33
+ MESSAGE = "message"
33
34
 
34
35
 
35
36
  class WorkflowStatus(str, Enum):
@@ -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.2
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
- - 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
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
- - `@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
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
- - Automatically creates API endpoints for starting workflows
70
- - Provides status endpoints to check workflow progress
71
- This is essentially a state machine for managing long-running business processes that need to be resilient to failures and can span multiple
72
- requests/processes.
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=123 -p 127.0.0.1:5432:5432 -d docker.io/library/postgres
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=flgHU00LRT-UcP0TjMqDigi2jwWq6UoMpmCZSOTyyB0,12428
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=2ObR5XkLGbdbnDqp5mrBzDVhSacHCNsVNSHnXkrMQzQ,9593
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.ini,sha256=8G9IWbmF61Vwp1BXbkNOXTTgCEUMBQhOK_e-nnpnSYY,4309
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=fms64l32M8hPK0SINXxNCykr2EpjBTcdgnezVgaCwkc,120
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=HcV0PVzcyRDaJvNdDQIFiDR1MJlLquNQzNO9oNkCKDQ,322
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=spK7iL1NSv-d8fd139ep-SDogZR2ZycGkD_voSAPPF4,8662
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=v7q9AJyWgQWl9VPSN_0qxw3rBvYe-_Pb_KcwqSsjOFU,3103
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=b7WhCancxNCKO63mJCez9MahwMQc5_3zQxr_soJoXCY,6478
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=54z19XaMp-OP9qE_HT2yhK12u8NC4ZD7SgwY8sGjyw4,5567
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=KArm9m44IBXKY9j4v_O74MAweFN6jEb7tVRomziaeFU,64011
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.2.dist-info/WHEEL,sha256=Jb20R3Ili4n9P1fcwuLup21eQ5r9WXhs4_qy7VTrgPI,79
170
- planar-0.9.2.dist-info/entry_points.txt,sha256=L3T0w9u2UPKWXv6JbXFWKU1d5xyEAq1xVWbpYS6mLNg,96
171
- planar-0.9.2.dist-info/METADATA,sha256=nF_zOc5hfhs8BYdm83nO8Fp480qDGfCuTbJpx8Yf5FE,12313
172
- planar-0.9.2.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.8.15
2
+ Generator: uv 0.8.17
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any