planar 0.5.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.
- planar/.__init__.py.un~ +0 -0
- planar/._version.py.un~ +0 -0
- planar/.app.py.un~ +0 -0
- planar/.cli.py.un~ +0 -0
- planar/.config.py.un~ +0 -0
- planar/.context.py.un~ +0 -0
- planar/.db.py.un~ +0 -0
- planar/.di.py.un~ +0 -0
- planar/.engine.py.un~ +0 -0
- planar/.files.py.un~ +0 -0
- planar/.log_context.py.un~ +0 -0
- planar/.log_metadata.py.un~ +0 -0
- planar/.logging.py.un~ +0 -0
- planar/.object_registry.py.un~ +0 -0
- planar/.otel.py.un~ +0 -0
- planar/.server.py.un~ +0 -0
- planar/.session.py.un~ +0 -0
- planar/.sqlalchemy.py.un~ +0 -0
- planar/.task_local.py.un~ +0 -0
- planar/.test_app.py.un~ +0 -0
- planar/.test_config.py.un~ +0 -0
- planar/.test_object_config.py.un~ +0 -0
- planar/.test_sqlalchemy.py.un~ +0 -0
- planar/.test_utils.py.un~ +0 -0
- planar/.util.py.un~ +0 -0
- planar/.utils.py.un~ +0 -0
- planar/__init__.py +26 -0
- planar/_version.py +1 -0
- planar/ai/.__init__.py.un~ +0 -0
- planar/ai/._models.py.un~ +0 -0
- planar/ai/.agent.py.un~ +0 -0
- planar/ai/.agent_utils.py.un~ +0 -0
- planar/ai/.events.py.un~ +0 -0
- planar/ai/.files.py.un~ +0 -0
- planar/ai/.models.py.un~ +0 -0
- planar/ai/.providers.py.un~ +0 -0
- planar/ai/.pydantic_ai.py.un~ +0 -0
- planar/ai/.pydantic_ai_agent.py.un~ +0 -0
- planar/ai/.pydantic_ai_provider.py.un~ +0 -0
- planar/ai/.step.py.un~ +0 -0
- planar/ai/.test_agent.py.un~ +0 -0
- planar/ai/.test_agent_serialization.py.un~ +0 -0
- planar/ai/.test_providers.py.un~ +0 -0
- planar/ai/.utils.py.un~ +0 -0
- planar/ai/__init__.py +15 -0
- planar/ai/agent.py +457 -0
- planar/ai/agent_utils.py +205 -0
- planar/ai/models.py +140 -0
- planar/ai/providers.py +1088 -0
- planar/ai/test_agent.py +1298 -0
- planar/ai/test_agent_serialization.py +229 -0
- planar/ai/test_providers.py +463 -0
- planar/ai/utils.py +102 -0
- planar/app.py +494 -0
- planar/cli.py +282 -0
- planar/config.py +544 -0
- planar/db/.db.py.un~ +0 -0
- planar/db/__init__.py +17 -0
- planar/db/alembic/env.py +136 -0
- planar/db/alembic/script.py.mako +28 -0
- planar/db/alembic/versions/3476068c153c_initial_system_tables_migration.py +339 -0
- planar/db/alembic.ini +128 -0
- planar/db/db.py +318 -0
- planar/files/.config.py.un~ +0 -0
- planar/files/.local.py.un~ +0 -0
- planar/files/.local_filesystem.py.un~ +0 -0
- planar/files/.model.py.un~ +0 -0
- planar/files/.models.py.un~ +0 -0
- planar/files/.s3.py.un~ +0 -0
- planar/files/.storage.py.un~ +0 -0
- planar/files/.test_files.py.un~ +0 -0
- planar/files/__init__.py +2 -0
- planar/files/models.py +162 -0
- planar/files/storage/.__init__.py.un~ +0 -0
- planar/files/storage/.base.py.un~ +0 -0
- planar/files/storage/.config.py.un~ +0 -0
- planar/files/storage/.context.py.un~ +0 -0
- planar/files/storage/.local_directory.py.un~ +0 -0
- planar/files/storage/.test_local_directory.py.un~ +0 -0
- planar/files/storage/.test_s3.py.un~ +0 -0
- planar/files/storage/base.py +61 -0
- planar/files/storage/config.py +44 -0
- planar/files/storage/context.py +15 -0
- planar/files/storage/local_directory.py +188 -0
- planar/files/storage/s3.py +220 -0
- planar/files/storage/test_local_directory.py +162 -0
- planar/files/storage/test_s3.py +299 -0
- planar/files/test_files.py +283 -0
- planar/human/.human.py.un~ +0 -0
- planar/human/.test_human.py.un~ +0 -0
- planar/human/__init__.py +2 -0
- planar/human/human.py +458 -0
- planar/human/models.py +80 -0
- planar/human/test_human.py +385 -0
- planar/logging/.__init__.py.un~ +0 -0
- planar/logging/.attributes.py.un~ +0 -0
- planar/logging/.formatter.py.un~ +0 -0
- planar/logging/.logger.py.un~ +0 -0
- planar/logging/.otel.py.un~ +0 -0
- planar/logging/.tracer.py.un~ +0 -0
- planar/logging/__init__.py +10 -0
- planar/logging/attributes.py +54 -0
- planar/logging/context.py +14 -0
- planar/logging/formatter.py +113 -0
- planar/logging/logger.py +114 -0
- planar/logging/otel.py +51 -0
- planar/modeling/.mixin.py.un~ +0 -0
- planar/modeling/.storage.py.un~ +0 -0
- planar/modeling/__init__.py +0 -0
- planar/modeling/field_helpers.py +59 -0
- planar/modeling/json_schema_generator.py +94 -0
- planar/modeling/mixins/__init__.py +10 -0
- planar/modeling/mixins/auditable.py +52 -0
- planar/modeling/mixins/test_auditable.py +97 -0
- planar/modeling/mixins/test_timestamp.py +134 -0
- planar/modeling/mixins/test_uuid_primary_key.py +52 -0
- planar/modeling/mixins/timestamp.py +53 -0
- planar/modeling/mixins/uuid_primary_key.py +19 -0
- planar/modeling/orm/.planar_base_model.py.un~ +0 -0
- planar/modeling/orm/__init__.py +18 -0
- planar/modeling/orm/planar_base_entity.py +29 -0
- planar/modeling/orm/query_filter_builder.py +122 -0
- planar/modeling/orm/reexports.py +15 -0
- planar/object_config/.object_config.py.un~ +0 -0
- planar/object_config/__init__.py +11 -0
- planar/object_config/models.py +114 -0
- planar/object_config/object_config.py +378 -0
- planar/object_registry.py +100 -0
- planar/registry_items.py +65 -0
- planar/routers/.__init__.py.un~ +0 -0
- planar/routers/.agents_router.py.un~ +0 -0
- planar/routers/.crud.py.un~ +0 -0
- planar/routers/.decision.py.un~ +0 -0
- planar/routers/.event.py.un~ +0 -0
- planar/routers/.file_attachment.py.un~ +0 -0
- planar/routers/.files.py.un~ +0 -0
- planar/routers/.files_router.py.un~ +0 -0
- planar/routers/.human.py.un~ +0 -0
- planar/routers/.info.py.un~ +0 -0
- planar/routers/.models.py.un~ +0 -0
- planar/routers/.object_config_router.py.un~ +0 -0
- planar/routers/.rule.py.un~ +0 -0
- planar/routers/.test_object_config_router.py.un~ +0 -0
- planar/routers/.test_workflow_router.py.un~ +0 -0
- planar/routers/.workflow.py.un~ +0 -0
- planar/routers/__init__.py +13 -0
- planar/routers/agents_router.py +197 -0
- planar/routers/entity_router.py +143 -0
- planar/routers/event.py +91 -0
- planar/routers/files.py +142 -0
- planar/routers/human.py +151 -0
- planar/routers/info.py +131 -0
- planar/routers/models.py +170 -0
- planar/routers/object_config_router.py +133 -0
- planar/routers/rule.py +108 -0
- planar/routers/test_agents_router.py +174 -0
- planar/routers/test_object_config_router.py +367 -0
- planar/routers/test_routes_security.py +169 -0
- planar/routers/test_rule_router.py +470 -0
- planar/routers/test_workflow_router.py +274 -0
- planar/routers/workflow.py +468 -0
- planar/rules/.decorator.py.un~ +0 -0
- planar/rules/.runner.py.un~ +0 -0
- planar/rules/.test_rules.py.un~ +0 -0
- planar/rules/__init__.py +23 -0
- planar/rules/decorator.py +184 -0
- planar/rules/models.py +355 -0
- planar/rules/rule_configuration.py +191 -0
- planar/rules/runner.py +64 -0
- planar/rules/test_rules.py +750 -0
- planar/scaffold_templates/app/__init__.py.j2 +0 -0
- planar/scaffold_templates/app/db/entities.py.j2 +11 -0
- planar/scaffold_templates/app/flows/process_invoice.py.j2 +67 -0
- planar/scaffold_templates/main.py.j2 +13 -0
- planar/scaffold_templates/planar.dev.yaml.j2 +34 -0
- planar/scaffold_templates/planar.prod.yaml.j2 +28 -0
- planar/scaffold_templates/pyproject.toml.j2 +10 -0
- planar/security/.jwt_middleware.py.un~ +0 -0
- planar/security/auth_context.py +148 -0
- planar/security/authorization.py +388 -0
- planar/security/default_policies.cedar +77 -0
- planar/security/jwt_middleware.py +116 -0
- planar/security/security_context.py +18 -0
- planar/security/tests/test_authorization_context.py +78 -0
- planar/security/tests/test_cedar_basics.py +41 -0
- planar/security/tests/test_cedar_policies.py +158 -0
- planar/security/tests/test_jwt_principal_context.py +179 -0
- planar/session.py +40 -0
- planar/sse/.constants.py.un~ +0 -0
- planar/sse/.example.html.un~ +0 -0
- planar/sse/.hub.py.un~ +0 -0
- planar/sse/.model.py.un~ +0 -0
- planar/sse/.proxy.py.un~ +0 -0
- planar/sse/constants.py +1 -0
- planar/sse/example.html +126 -0
- planar/sse/hub.py +216 -0
- planar/sse/model.py +8 -0
- planar/sse/proxy.py +257 -0
- planar/task_local.py +37 -0
- planar/test_app.py +51 -0
- planar/test_cli.py +372 -0
- planar/test_config.py +512 -0
- planar/test_object_config.py +527 -0
- planar/test_object_registry.py +14 -0
- planar/test_sqlalchemy.py +158 -0
- planar/test_utils.py +105 -0
- planar/testing/.client.py.un~ +0 -0
- planar/testing/.memory_storage.py.un~ +0 -0
- planar/testing/.planar_test_client.py.un~ +0 -0
- planar/testing/.predictable_tracer.py.un~ +0 -0
- planar/testing/.synchronizable_tracer.py.un~ +0 -0
- planar/testing/.test_memory_storage.py.un~ +0 -0
- planar/testing/.workflow_observer.py.un~ +0 -0
- planar/testing/__init__.py +0 -0
- planar/testing/memory_storage.py +78 -0
- planar/testing/planar_test_client.py +54 -0
- planar/testing/synchronizable_tracer.py +153 -0
- planar/testing/test_memory_storage.py +143 -0
- planar/testing/workflow_observer.py +73 -0
- planar/utils.py +70 -0
- planar/workflows/.__init__.py.un~ +0 -0
- planar/workflows/.builtin_steps.py.un~ +0 -0
- planar/workflows/.concurrency_tracing.py.un~ +0 -0
- planar/workflows/.context.py.un~ +0 -0
- planar/workflows/.contrib.py.un~ +0 -0
- planar/workflows/.decorators.py.un~ +0 -0
- planar/workflows/.durable_test.py.un~ +0 -0
- planar/workflows/.errors.py.un~ +0 -0
- planar/workflows/.events.py.un~ +0 -0
- planar/workflows/.exceptions.py.un~ +0 -0
- planar/workflows/.execution.py.un~ +0 -0
- planar/workflows/.human.py.un~ +0 -0
- planar/workflows/.lock.py.un~ +0 -0
- planar/workflows/.misc.py.un~ +0 -0
- planar/workflows/.model.py.un~ +0 -0
- planar/workflows/.models.py.un~ +0 -0
- planar/workflows/.notifications.py.un~ +0 -0
- planar/workflows/.orchestrator.py.un~ +0 -0
- planar/workflows/.runtime.py.un~ +0 -0
- planar/workflows/.serialization.py.un~ +0 -0
- planar/workflows/.step.py.un~ +0 -0
- planar/workflows/.step_core.py.un~ +0 -0
- planar/workflows/.sub_workflow_runner.py.un~ +0 -0
- planar/workflows/.sub_workflow_scheduler.py.un~ +0 -0
- planar/workflows/.test_concurrency.py.un~ +0 -0
- planar/workflows/.test_concurrency_detection.py.un~ +0 -0
- planar/workflows/.test_human.py.un~ +0 -0
- planar/workflows/.test_lock_timeout.py.un~ +0 -0
- planar/workflows/.test_orchestrator.py.un~ +0 -0
- planar/workflows/.test_race_conditions.py.un~ +0 -0
- planar/workflows/.test_serialization.py.un~ +0 -0
- planar/workflows/.test_suspend_deserialization.py.un~ +0 -0
- planar/workflows/.test_workflow.py.un~ +0 -0
- planar/workflows/.tracing.py.un~ +0 -0
- planar/workflows/.types.py.un~ +0 -0
- planar/workflows/.util.py.un~ +0 -0
- planar/workflows/.utils.py.un~ +0 -0
- planar/workflows/.workflow.py.un~ +0 -0
- planar/workflows/.workflow_wrapper.py.un~ +0 -0
- planar/workflows/.wrappers.py.un~ +0 -0
- planar/workflows/__init__.py +42 -0
- planar/workflows/context.py +44 -0
- planar/workflows/contrib.py +190 -0
- planar/workflows/decorators.py +217 -0
- planar/workflows/events.py +185 -0
- planar/workflows/exceptions.py +34 -0
- planar/workflows/execution.py +198 -0
- planar/workflows/lock.py +229 -0
- planar/workflows/misc.py +5 -0
- planar/workflows/models.py +154 -0
- planar/workflows/notifications.py +96 -0
- planar/workflows/orchestrator.py +383 -0
- planar/workflows/query.py +256 -0
- planar/workflows/serialization.py +409 -0
- planar/workflows/step_core.py +373 -0
- planar/workflows/step_metadata.py +357 -0
- planar/workflows/step_testing_utils.py +86 -0
- planar/workflows/sub_workflow_runner.py +191 -0
- planar/workflows/test_concurrency_detection.py +120 -0
- planar/workflows/test_lock_timeout.py +140 -0
- planar/workflows/test_serialization.py +1195 -0
- planar/workflows/test_suspend_deserialization.py +231 -0
- planar/workflows/test_workflow.py +1967 -0
- planar/workflows/tracing.py +106 -0
- planar/workflows/wrappers.py +41 -0
- planar-0.5.0.dist-info/METADATA +285 -0
- planar-0.5.0.dist-info/RECORD +289 -0
- planar-0.5.0.dist-info/WHEEL +4 -0
- planar-0.5.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
5
|
+
|
6
|
+
from planar.workflows.decorators import step, workflow
|
7
|
+
from planar.workflows.execution import execute
|
8
|
+
from planar.workflows.models import Workflow, WorkflowStatus
|
9
|
+
from planar.workflows.step_core import Suspend, suspend
|
10
|
+
|
11
|
+
|
12
|
+
class ModelData(BaseModel):
|
13
|
+
name: str
|
14
|
+
value: int
|
15
|
+
created_at: datetime
|
16
|
+
|
17
|
+
|
18
|
+
async def test_suspend_with_pydantic_return_value(session: AsyncSession):
|
19
|
+
"""Test that a step returning a pydantic model properly deserializes after suspension."""
|
20
|
+
|
21
|
+
@step()
|
22
|
+
async def data_step() -> ModelData:
|
23
|
+
"""Step that returns a pydantic model."""
|
24
|
+
return ModelData(name="test-data", value=42, created_at=datetime.now())
|
25
|
+
|
26
|
+
@workflow()
|
27
|
+
async def suspend_workflow():
|
28
|
+
# First get the pydantic model from the step
|
29
|
+
data = await data_step()
|
30
|
+
|
31
|
+
await suspend(interval=timedelta(seconds=0.1))
|
32
|
+
|
33
|
+
# After resume, verify we can still access the pydantic model's properties
|
34
|
+
# This verifies the model was properly deserialized on re-execution
|
35
|
+
return ModelData(name=data.name, value=data.value, created_at=data.created_at)
|
36
|
+
|
37
|
+
# Start the workflow
|
38
|
+
wf = await suspend_workflow.start()
|
39
|
+
|
40
|
+
# First execution should suspend
|
41
|
+
result = await execute(wf)
|
42
|
+
assert result is not None
|
43
|
+
assert isinstance(result, Suspend)
|
44
|
+
|
45
|
+
# Resume workflow after suspend
|
46
|
+
result = await execute(wf)
|
47
|
+
assert result is not None
|
48
|
+
assert isinstance(result, ModelData)
|
49
|
+
# Check that the result contains our expected data with proper types
|
50
|
+
assert result.name == "test-data"
|
51
|
+
assert result.value == 42
|
52
|
+
assert isinstance(result.created_at, datetime)
|
53
|
+
|
54
|
+
# Verify workflow completed successfully
|
55
|
+
updated_wf = await session.get(Workflow, wf.id)
|
56
|
+
assert updated_wf is not None
|
57
|
+
assert updated_wf.status == WorkflowStatus.SUCCEEDED
|
58
|
+
|
59
|
+
|
60
|
+
async def test_suspend_with_generic_model_deserialization(session: AsyncSession):
|
61
|
+
"""Test deserialization of a generic model (like Agent's CompletionResponse) after workflow suspension."""
|
62
|
+
|
63
|
+
class ToolCall(BaseModel):
|
64
|
+
"""Simplified version of a tool call."""
|
65
|
+
|
66
|
+
name: str
|
67
|
+
arguments: dict[str, str]
|
68
|
+
|
69
|
+
# Define a model similar to what an Agent might return
|
70
|
+
class GenericResponse[DataT](BaseModel):
|
71
|
+
"""Similar structure to CompletionResponse used by Agent."""
|
72
|
+
|
73
|
+
content: DataT | None = None
|
74
|
+
tool_calls: list[ToolCall] | None = None
|
75
|
+
|
76
|
+
class ResultData(BaseModel):
|
77
|
+
"""Example result data model."""
|
78
|
+
|
79
|
+
title: str
|
80
|
+
score: int
|
81
|
+
timestamp: datetime
|
82
|
+
|
83
|
+
@step()
|
84
|
+
async def generic_data_step[DataT](payload: DataT) -> GenericResponse[DataT]:
|
85
|
+
"""Step that returns a generic response with a pydantic model."""
|
86
|
+
return GenericResponse[DataT](
|
87
|
+
content=payload,
|
88
|
+
tool_calls=[ToolCall(name="test_tool", arguments={"param": "value"})],
|
89
|
+
)
|
90
|
+
|
91
|
+
@workflow()
|
92
|
+
async def generic_suspend_workflow():
|
93
|
+
# Get the generic response from the step
|
94
|
+
result_data = ResultData(
|
95
|
+
title="Test Generic Response",
|
96
|
+
score=95,
|
97
|
+
timestamp=datetime(2021, 1, 1, 12, 0, 0),
|
98
|
+
)
|
99
|
+
response = await generic_data_step(result_data)
|
100
|
+
|
101
|
+
# Suspend the workflow
|
102
|
+
await suspend(interval=timedelta(seconds=0.1))
|
103
|
+
|
104
|
+
# After resuming, verify we can still access properties of the generic response
|
105
|
+
# This confirms proper deserialization of the generic model
|
106
|
+
return response
|
107
|
+
|
108
|
+
# Start the workflow
|
109
|
+
wf = await generic_suspend_workflow.start()
|
110
|
+
|
111
|
+
# First execution should suspend
|
112
|
+
result = await execute(wf)
|
113
|
+
assert result is not None
|
114
|
+
assert isinstance(result, Suspend)
|
115
|
+
|
116
|
+
# Resume workflow after suspend
|
117
|
+
final_result = await execute(wf)
|
118
|
+
assert final_result is not None
|
119
|
+
# Check against the origin type first
|
120
|
+
assert isinstance(final_result, GenericResponse)
|
121
|
+
assert final_result.content is not None
|
122
|
+
assert isinstance(final_result.content, ResultData)
|
123
|
+
assert final_result.content.title == "Test Generic Response"
|
124
|
+
assert final_result.content.score == 95
|
125
|
+
assert isinstance(final_result.content.timestamp, datetime)
|
126
|
+
assert final_result.tool_calls is not None
|
127
|
+
assert len(final_result.tool_calls) == 1
|
128
|
+
assert final_result.tool_calls[0].name == "test_tool"
|
129
|
+
|
130
|
+
# Verify workflow completed successfully
|
131
|
+
updated_wf = await session.get(Workflow, wf.id)
|
132
|
+
assert updated_wf is not None
|
133
|
+
assert updated_wf.status == WorkflowStatus.SUCCEEDED
|
134
|
+
|
135
|
+
|
136
|
+
async def test_suspend_with_generic_list_deserialization(session: AsyncSession):
|
137
|
+
"""Test deserialization of a generic model containing a list after suspension."""
|
138
|
+
|
139
|
+
# Define a model similar to what an Agent might return
|
140
|
+
class GenericListResponse[DataT](BaseModel):
|
141
|
+
"""Generic response containing a list."""
|
142
|
+
|
143
|
+
items: list[DataT] | None = None
|
144
|
+
description: str = ""
|
145
|
+
|
146
|
+
@step()
|
147
|
+
async def generic_list_step[DataT](
|
148
|
+
payload: list[DataT],
|
149
|
+
) -> GenericListResponse[DataT]:
|
150
|
+
"""Step that returns a generic response with a list."""
|
151
|
+
return GenericListResponse[DataT](items=payload, description="List of items")
|
152
|
+
|
153
|
+
@workflow()
|
154
|
+
async def generic_list_suspend_workflow():
|
155
|
+
# Example list data
|
156
|
+
str_list = ["apple", "banana", "cherry"]
|
157
|
+
response = await generic_list_step(str_list)
|
158
|
+
|
159
|
+
# Suspend the workflow
|
160
|
+
await suspend(interval=timedelta(seconds=0.1))
|
161
|
+
|
162
|
+
# After resuming, verify we can still access properties
|
163
|
+
return response
|
164
|
+
|
165
|
+
# Start the workflow
|
166
|
+
wf = await generic_list_suspend_workflow.start()
|
167
|
+
|
168
|
+
# First execution should suspend
|
169
|
+
result = await execute(wf)
|
170
|
+
assert result is not None
|
171
|
+
assert isinstance(result, Suspend)
|
172
|
+
|
173
|
+
# Resume workflow after suspend
|
174
|
+
final_result = await execute(wf)
|
175
|
+
assert final_result is not None
|
176
|
+
assert isinstance(final_result, GenericListResponse)
|
177
|
+
assert final_result.items is not None
|
178
|
+
assert isinstance(final_result.items, list)
|
179
|
+
assert final_result.items == ["apple", "banana", "cherry"]
|
180
|
+
assert final_result.description == "List of items"
|
181
|
+
|
182
|
+
# Verify workflow completed successfully
|
183
|
+
updated_wf = await session.get(Workflow, wf.id)
|
184
|
+
assert updated_wf is not None
|
185
|
+
assert updated_wf.status == WorkflowStatus.SUCCEEDED
|
186
|
+
|
187
|
+
|
188
|
+
async def test_suspend_with_dict_deserialization(session: AsyncSession):
|
189
|
+
"""Test deserialization of a dict after suspension."""
|
190
|
+
|
191
|
+
dict_payload = {
|
192
|
+
"a": 1,
|
193
|
+
"b": "string",
|
194
|
+
"c": {"d": "nested", "e": [1, 2, 3]},
|
195
|
+
"f": [{"g": "nested_list"}],
|
196
|
+
# "h": datetime(2021, 1, 1, 12, 0, 0), # Not supported in dict
|
197
|
+
# "i": uuid.uuid4(), # Not supported in dict
|
198
|
+
"j": True,
|
199
|
+
"k": False,
|
200
|
+
"l": None,
|
201
|
+
# "m": Decimal(100), # Not supported in dict
|
202
|
+
"n": [1, 2, 3],
|
203
|
+
"o": 1.1,
|
204
|
+
}
|
205
|
+
|
206
|
+
@step()
|
207
|
+
async def dict_step(
|
208
|
+
payload: dict,
|
209
|
+
) -> dict:
|
210
|
+
"""Step that returns a generic response with a list."""
|
211
|
+
return payload
|
212
|
+
|
213
|
+
@workflow()
|
214
|
+
async def dict_suspend_workflow(dict_payload: dict):
|
215
|
+
response = await dict_step(dict_payload)
|
216
|
+
|
217
|
+
# Suspend the workflow
|
218
|
+
await suspend(interval=timedelta(seconds=0.1))
|
219
|
+
|
220
|
+
# After resuming, verify we can still access properties
|
221
|
+
return response
|
222
|
+
|
223
|
+
wf = await dict_suspend_workflow.start(dict_payload)
|
224
|
+
|
225
|
+
result = await execute(wf)
|
226
|
+
assert result is not None
|
227
|
+
assert isinstance(result, Suspend)
|
228
|
+
|
229
|
+
final_result = await execute(wf)
|
230
|
+
assert final_result is not None
|
231
|
+
assert final_result == dict_payload
|