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,274 @@
|
|
1
|
+
from uuid import UUID, uuid4
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from pydantic import BaseModel, Field
|
5
|
+
from sqlmodel import select
|
6
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
7
|
+
|
8
|
+
from examples.expense_approval_workflow.models import (
|
9
|
+
Expense,
|
10
|
+
ExpenseStatus,
|
11
|
+
)
|
12
|
+
from planar import PlanarApp, get_session, sqlite_config
|
13
|
+
from planar.files.models import PlanarFile, PlanarFileMetadata
|
14
|
+
from planar.files.storage.base import Storage
|
15
|
+
from planar.testing.planar_test_client import PlanarTestClient
|
16
|
+
from planar.testing.workflow_observer import WorkflowObserver
|
17
|
+
from planar.workflows import step, workflow
|
18
|
+
from planar.workflows.models import StepType, Workflow, WorkflowStatus, WorkflowStep
|
19
|
+
|
20
|
+
# ------ SETUP ------
|
21
|
+
|
22
|
+
|
23
|
+
async def get_expense(expense_id: str) -> Expense:
|
24
|
+
session = get_session()
|
25
|
+
expense = (
|
26
|
+
await session.exec(select(Expense).where(Expense.id == UUID(expense_id)))
|
27
|
+
).first()
|
28
|
+
if not expense:
|
29
|
+
raise ValueError(f"Expense {expense_id} not found")
|
30
|
+
return expense
|
31
|
+
|
32
|
+
|
33
|
+
@workflow(name="test_expense_approval_workflow")
|
34
|
+
async def expense_approval_workflow(expense_id: str):
|
35
|
+
"""
|
36
|
+
Main workflow that orchestrates the expense approval process
|
37
|
+
"""
|
38
|
+
await validate_expense(expense_id)
|
39
|
+
|
40
|
+
expense = await get_expense(expense_id)
|
41
|
+
|
42
|
+
return expense
|
43
|
+
|
44
|
+
|
45
|
+
@step()
|
46
|
+
async def validate_expense(expense_id: str):
|
47
|
+
expense = await get_expense(expense_id)
|
48
|
+
|
49
|
+
if expense.status != ExpenseStatus.SUBMITTED:
|
50
|
+
raise ValueError(f"Expense {expense_id} is not in SUBMITTED status")
|
51
|
+
|
52
|
+
|
53
|
+
class FileProcessingResult(BaseModel):
|
54
|
+
"""Result of processing a text file."""
|
55
|
+
|
56
|
+
filename: str = Field(description="Original filename")
|
57
|
+
character_count: int = Field(description="Number of characters in the file")
|
58
|
+
content_preview: str = Field(description="Preview of the file content")
|
59
|
+
file_id: UUID = Field(description="ID of the processed file")
|
60
|
+
|
61
|
+
|
62
|
+
@workflow(name="test_file_processing_workflow")
|
63
|
+
async def file_processing_workflow(file: PlanarFile):
|
64
|
+
"""
|
65
|
+
Workflow that processes a text file and returns basic information about it.
|
66
|
+
"""
|
67
|
+
file_content = await file.get_content()
|
68
|
+
char_count = len(file_content)
|
69
|
+
preview = file_content[:100].decode("utf-8")
|
70
|
+
|
71
|
+
# Return structured result
|
72
|
+
return FileProcessingResult(
|
73
|
+
filename=file.filename,
|
74
|
+
character_count=char_count,
|
75
|
+
content_preview=preview,
|
76
|
+
file_id=file.id,
|
77
|
+
)
|
78
|
+
|
79
|
+
|
80
|
+
app = PlanarApp(
|
81
|
+
config=sqlite_config("test_workflow_router.db"),
|
82
|
+
title="Test Workflow Router API",
|
83
|
+
description="API for testing workflow routers",
|
84
|
+
)
|
85
|
+
|
86
|
+
|
87
|
+
# ------ TESTS ------
|
88
|
+
|
89
|
+
|
90
|
+
@pytest.fixture(name="app")
|
91
|
+
def app_fixture():
|
92
|
+
# Re-register workflows since ObjectRegistry gets reset before each test
|
93
|
+
app.register_workflow(expense_approval_workflow)
|
94
|
+
app.register_workflow(file_processing_workflow)
|
95
|
+
yield app
|
96
|
+
|
97
|
+
|
98
|
+
@pytest.fixture
|
99
|
+
async def planar_file(storage: Storage) -> PlanarFile:
|
100
|
+
"""Create a PlanarFile instance for testing."""
|
101
|
+
# Store test content
|
102
|
+
test_data = b"This is a test file for the workflow router API test."
|
103
|
+
mime_type = "text/plain"
|
104
|
+
|
105
|
+
# Store the file and get a reference
|
106
|
+
storage_ref = await storage.put_bytes(test_data, mime_type=mime_type)
|
107
|
+
|
108
|
+
# Create and store the file metadata
|
109
|
+
session = get_session()
|
110
|
+
file_metadata = PlanarFileMetadata(
|
111
|
+
filename="router_test_file.txt",
|
112
|
+
content_type=mime_type,
|
113
|
+
size=len(test_data),
|
114
|
+
storage_ref=storage_ref,
|
115
|
+
)
|
116
|
+
session.add(file_metadata)
|
117
|
+
await session.commit()
|
118
|
+
|
119
|
+
# Return a PlanarFile reference (not the full metadata)
|
120
|
+
return PlanarFile(
|
121
|
+
id=file_metadata.id,
|
122
|
+
filename=file_metadata.filename,
|
123
|
+
content_type=file_metadata.content_type,
|
124
|
+
size=file_metadata.size,
|
125
|
+
)
|
126
|
+
|
127
|
+
|
128
|
+
async def test_list_workflows(client: PlanarTestClient):
|
129
|
+
"""
|
130
|
+
Test that the workflow management router correctly lists registered workflows.
|
131
|
+
"""
|
132
|
+
# Call the workflow management endpoint to list workflows
|
133
|
+
response = await client.get("/planar/v1/workflows/")
|
134
|
+
|
135
|
+
# Verify the response status code
|
136
|
+
assert response.status_code == 200
|
137
|
+
|
138
|
+
# Parse the response data
|
139
|
+
data = response.json()
|
140
|
+
|
141
|
+
# Verify that two workflows are returned
|
142
|
+
assert data["total"] == 2
|
143
|
+
assert len(data["items"]) == 2
|
144
|
+
|
145
|
+
assert data["offset"] == 0
|
146
|
+
assert data["limit"] == 10
|
147
|
+
|
148
|
+
# Verify the expense workflow details
|
149
|
+
expense_workflow = next(
|
150
|
+
item
|
151
|
+
for item in data["items"]
|
152
|
+
if item["name"] == "test_expense_approval_workflow"
|
153
|
+
)
|
154
|
+
assert expense_workflow["fully_qualified_name"] == "test_expense_approval_workflow"
|
155
|
+
assert (
|
156
|
+
"Main workflow that orchestrates the expense approval process"
|
157
|
+
in expense_workflow["description"]
|
158
|
+
)
|
159
|
+
|
160
|
+
# Verify the file workflow details
|
161
|
+
file_workflow = next(
|
162
|
+
item
|
163
|
+
for item in data["items"]
|
164
|
+
if item["name"] == "test_file_processing_workflow"
|
165
|
+
)
|
166
|
+
assert file_workflow["fully_qualified_name"] == "test_file_processing_workflow"
|
167
|
+
assert "Workflow that processes a text file" in file_workflow["description"]
|
168
|
+
|
169
|
+
# Verify that the workflows have input and output schemas
|
170
|
+
assert "input_schema" in expense_workflow
|
171
|
+
assert "output_schema" in expense_workflow
|
172
|
+
assert "input_schema" in file_workflow
|
173
|
+
assert "output_schema" in file_workflow
|
174
|
+
|
175
|
+
# Verify that the file workflow input schema includes file parameter
|
176
|
+
assert "file" in file_workflow["input_schema"]["properties"]
|
177
|
+
|
178
|
+
# Verify run statistics are present
|
179
|
+
assert "total_runs" in expense_workflow
|
180
|
+
assert "run_statuses" in expense_workflow
|
181
|
+
assert "total_runs" in file_workflow
|
182
|
+
assert "run_statuses" in file_workflow
|
183
|
+
|
184
|
+
|
185
|
+
async def test_start_file_workflow(
|
186
|
+
client: PlanarTestClient,
|
187
|
+
planar_file: PlanarFile,
|
188
|
+
observer: WorkflowObserver,
|
189
|
+
session: AsyncSession,
|
190
|
+
):
|
191
|
+
"""Test starting a workflow with a PlanarFile through the API."""
|
192
|
+
# Prepare the request payload with the file reference
|
193
|
+
payload = {
|
194
|
+
"file": {
|
195
|
+
"id": str(planar_file.id),
|
196
|
+
"filename": planar_file.filename,
|
197
|
+
"content_type": planar_file.content_type,
|
198
|
+
"size": planar_file.size,
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
response = await client.post(
|
203
|
+
"/planar/v1/workflows/test_file_processing_workflow/start",
|
204
|
+
json=payload,
|
205
|
+
)
|
206
|
+
|
207
|
+
# Verify the response status code
|
208
|
+
assert response.status_code == 200
|
209
|
+
|
210
|
+
data = response.json()
|
211
|
+
|
212
|
+
assert "id" in data
|
213
|
+
workflow_id = data["id"]
|
214
|
+
|
215
|
+
await observer.wait("workflow-succeeded", workflow_id=workflow_id)
|
216
|
+
|
217
|
+
workflow = await session.get(Workflow, UUID(workflow_id))
|
218
|
+
await session.commit()
|
219
|
+
assert workflow
|
220
|
+
|
221
|
+
# Verify the workflow completed successfully
|
222
|
+
assert workflow.status == WorkflowStatus.SUCCEEDED
|
223
|
+
|
224
|
+
# Check the workflow result
|
225
|
+
result = workflow.result
|
226
|
+
assert result
|
227
|
+
assert result["filename"] == planar_file.filename
|
228
|
+
assert result["character_count"] == planar_file.size
|
229
|
+
assert "This is a test file" in result["content_preview"]
|
230
|
+
assert result["file_id"] == str(planar_file.id)
|
231
|
+
|
232
|
+
|
233
|
+
async def test_get_compute_step(
|
234
|
+
client: PlanarTestClient, session: AsyncSession, observer: WorkflowObserver
|
235
|
+
):
|
236
|
+
"""Ensure compute steps can be retrieved without metadata."""
|
237
|
+
|
238
|
+
expense = Expense(
|
239
|
+
title="Test Expense",
|
240
|
+
amount=100.0,
|
241
|
+
description="test",
|
242
|
+
status=ExpenseStatus.SUBMITTED,
|
243
|
+
submitter_id=uuid4(),
|
244
|
+
category="misc",
|
245
|
+
)
|
246
|
+
session.add(expense)
|
247
|
+
await session.commit()
|
248
|
+
|
249
|
+
payload = {"expense_id": str(expense.id)}
|
250
|
+
resp = await client.post(
|
251
|
+
"/planar/v1/workflows/test_expense_approval_workflow/start",
|
252
|
+
json=payload,
|
253
|
+
)
|
254
|
+
assert resp.status_code == 200
|
255
|
+
wf_id = resp.json()["id"]
|
256
|
+
|
257
|
+
await observer.wait("workflow-succeeded", workflow_id=wf_id)
|
258
|
+
|
259
|
+
step = (
|
260
|
+
await session.exec(
|
261
|
+
select(WorkflowStep).where(WorkflowStep.workflow_id == UUID(wf_id))
|
262
|
+
)
|
263
|
+
).first()
|
264
|
+
await session.commit()
|
265
|
+
assert step
|
266
|
+
assert step.step_type == StepType.COMPUTE
|
267
|
+
|
268
|
+
resp = await client.get(
|
269
|
+
f"/planar/v1/workflows/test_expense_approval_workflow/runs/{wf_id}/steps/{step.step_id}"
|
270
|
+
)
|
271
|
+
assert resp.status_code == 200
|
272
|
+
data = resp.json()
|
273
|
+
assert "meta" in data
|
274
|
+
assert data["meta"] is None
|