pyworkflow-engine 0.1.7__py3-none-any.whl → 0.1.9__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.
Files changed (145) hide show
  1. pyworkflow/__init__.py +10 -1
  2. pyworkflow/celery/tasks.py +272 -24
  3. pyworkflow/cli/__init__.py +4 -1
  4. pyworkflow/cli/commands/runs.py +4 -4
  5. pyworkflow/cli/commands/setup.py +203 -4
  6. pyworkflow/cli/utils/config_generator.py +76 -3
  7. pyworkflow/cli/utils/docker_manager.py +232 -0
  8. pyworkflow/context/__init__.py +13 -0
  9. pyworkflow/context/base.py +26 -0
  10. pyworkflow/context/local.py +80 -0
  11. pyworkflow/context/step_context.py +295 -0
  12. pyworkflow/core/registry.py +6 -1
  13. pyworkflow/core/step.py +141 -0
  14. pyworkflow/core/workflow.py +56 -0
  15. pyworkflow/engine/events.py +30 -0
  16. pyworkflow/engine/replay.py +39 -0
  17. pyworkflow/primitives/child_workflow.py +1 -1
  18. pyworkflow/runtime/local.py +1 -1
  19. pyworkflow/storage/__init__.py +14 -0
  20. pyworkflow/storage/base.py +35 -0
  21. pyworkflow/storage/cassandra.py +1747 -0
  22. pyworkflow/storage/config.py +69 -0
  23. pyworkflow/storage/dynamodb.py +31 -2
  24. pyworkflow/storage/file.py +28 -0
  25. pyworkflow/storage/memory.py +18 -0
  26. pyworkflow/storage/mysql.py +1159 -0
  27. pyworkflow/storage/postgres.py +27 -2
  28. pyworkflow/storage/schemas.py +4 -3
  29. pyworkflow/storage/sqlite.py +25 -2
  30. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/METADATA +7 -4
  31. pyworkflow_engine-0.1.9.dist-info/RECORD +91 -0
  32. pyworkflow_engine-0.1.9.dist-info/top_level.txt +1 -0
  33. dashboard/backend/app/__init__.py +0 -1
  34. dashboard/backend/app/config.py +0 -32
  35. dashboard/backend/app/controllers/__init__.py +0 -6
  36. dashboard/backend/app/controllers/run_controller.py +0 -86
  37. dashboard/backend/app/controllers/workflow_controller.py +0 -33
  38. dashboard/backend/app/dependencies/__init__.py +0 -5
  39. dashboard/backend/app/dependencies/storage.py +0 -50
  40. dashboard/backend/app/repositories/__init__.py +0 -6
  41. dashboard/backend/app/repositories/run_repository.py +0 -80
  42. dashboard/backend/app/repositories/workflow_repository.py +0 -27
  43. dashboard/backend/app/rest/__init__.py +0 -8
  44. dashboard/backend/app/rest/v1/__init__.py +0 -12
  45. dashboard/backend/app/rest/v1/health.py +0 -33
  46. dashboard/backend/app/rest/v1/runs.py +0 -133
  47. dashboard/backend/app/rest/v1/workflows.py +0 -41
  48. dashboard/backend/app/schemas/__init__.py +0 -23
  49. dashboard/backend/app/schemas/common.py +0 -16
  50. dashboard/backend/app/schemas/event.py +0 -24
  51. dashboard/backend/app/schemas/hook.py +0 -25
  52. dashboard/backend/app/schemas/run.py +0 -54
  53. dashboard/backend/app/schemas/step.py +0 -28
  54. dashboard/backend/app/schemas/workflow.py +0 -31
  55. dashboard/backend/app/server.py +0 -87
  56. dashboard/backend/app/services/__init__.py +0 -6
  57. dashboard/backend/app/services/run_service.py +0 -240
  58. dashboard/backend/app/services/workflow_service.py +0 -155
  59. dashboard/backend/main.py +0 -18
  60. docs/concepts/cancellation.mdx +0 -362
  61. docs/concepts/continue-as-new.mdx +0 -434
  62. docs/concepts/events.mdx +0 -266
  63. docs/concepts/fault-tolerance.mdx +0 -370
  64. docs/concepts/hooks.mdx +0 -552
  65. docs/concepts/limitations.mdx +0 -167
  66. docs/concepts/schedules.mdx +0 -775
  67. docs/concepts/sleep.mdx +0 -312
  68. docs/concepts/steps.mdx +0 -301
  69. docs/concepts/workflows.mdx +0 -255
  70. docs/guides/cli.mdx +0 -942
  71. docs/guides/configuration.mdx +0 -560
  72. docs/introduction.mdx +0 -155
  73. docs/quickstart.mdx +0 -279
  74. examples/__init__.py +0 -1
  75. examples/celery/__init__.py +0 -1
  76. examples/celery/durable/docker-compose.yml +0 -55
  77. examples/celery/durable/pyworkflow.config.yaml +0 -12
  78. examples/celery/durable/workflows/__init__.py +0 -122
  79. examples/celery/durable/workflows/basic.py +0 -87
  80. examples/celery/durable/workflows/batch_processing.py +0 -102
  81. examples/celery/durable/workflows/cancellation.py +0 -273
  82. examples/celery/durable/workflows/child_workflow_patterns.py +0 -240
  83. examples/celery/durable/workflows/child_workflows.py +0 -202
  84. examples/celery/durable/workflows/continue_as_new.py +0 -260
  85. examples/celery/durable/workflows/fault_tolerance.py +0 -210
  86. examples/celery/durable/workflows/hooks.py +0 -211
  87. examples/celery/durable/workflows/idempotency.py +0 -112
  88. examples/celery/durable/workflows/long_running.py +0 -99
  89. examples/celery/durable/workflows/retries.py +0 -101
  90. examples/celery/durable/workflows/schedules.py +0 -209
  91. examples/celery/transient/01_basic_workflow.py +0 -91
  92. examples/celery/transient/02_fault_tolerance.py +0 -257
  93. examples/celery/transient/__init__.py +0 -20
  94. examples/celery/transient/pyworkflow.config.yaml +0 -25
  95. examples/local/__init__.py +0 -1
  96. examples/local/durable/01_basic_workflow.py +0 -94
  97. examples/local/durable/02_file_storage.py +0 -132
  98. examples/local/durable/03_retries.py +0 -169
  99. examples/local/durable/04_long_running.py +0 -119
  100. examples/local/durable/05_event_log.py +0 -145
  101. examples/local/durable/06_idempotency.py +0 -148
  102. examples/local/durable/07_hooks.py +0 -334
  103. examples/local/durable/08_cancellation.py +0 -233
  104. examples/local/durable/09_child_workflows.py +0 -198
  105. examples/local/durable/10_child_workflow_patterns.py +0 -265
  106. examples/local/durable/11_continue_as_new.py +0 -249
  107. examples/local/durable/12_schedules.py +0 -198
  108. examples/local/durable/__init__.py +0 -1
  109. examples/local/transient/01_quick_tasks.py +0 -87
  110. examples/local/transient/02_retries.py +0 -130
  111. examples/local/transient/03_sleep.py +0 -141
  112. examples/local/transient/__init__.py +0 -1
  113. pyworkflow_engine-0.1.7.dist-info/RECORD +0 -196
  114. pyworkflow_engine-0.1.7.dist-info/top_level.txt +0 -5
  115. tests/examples/__init__.py +0 -0
  116. tests/integration/__init__.py +0 -0
  117. tests/integration/test_cancellation.py +0 -330
  118. tests/integration/test_child_workflows.py +0 -439
  119. tests/integration/test_continue_as_new.py +0 -428
  120. tests/integration/test_dynamodb_storage.py +0 -1146
  121. tests/integration/test_fault_tolerance.py +0 -369
  122. tests/integration/test_schedule_storage.py +0 -484
  123. tests/unit/__init__.py +0 -0
  124. tests/unit/backends/__init__.py +0 -1
  125. tests/unit/backends/test_dynamodb_storage.py +0 -1554
  126. tests/unit/backends/test_postgres_storage.py +0 -1281
  127. tests/unit/backends/test_sqlite_storage.py +0 -1460
  128. tests/unit/conftest.py +0 -41
  129. tests/unit/test_cancellation.py +0 -364
  130. tests/unit/test_child_workflows.py +0 -680
  131. tests/unit/test_continue_as_new.py +0 -441
  132. tests/unit/test_event_limits.py +0 -316
  133. tests/unit/test_executor.py +0 -320
  134. tests/unit/test_fault_tolerance.py +0 -334
  135. tests/unit/test_hooks.py +0 -495
  136. tests/unit/test_registry.py +0 -261
  137. tests/unit/test_replay.py +0 -420
  138. tests/unit/test_schedule_schemas.py +0 -285
  139. tests/unit/test_schedule_utils.py +0 -286
  140. tests/unit/test_scheduled_workflow.py +0 -274
  141. tests/unit/test_step.py +0 -353
  142. tests/unit/test_workflow.py +0 -243
  143. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/WHEEL +0 -0
  144. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/entry_points.txt +0 -0
  145. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,243 +0,0 @@
1
- """
2
- Unit tests for @workflow decorator and workflow execution.
3
- """
4
-
5
- import pytest
6
-
7
- from pyworkflow.context import LocalContext, get_context, has_context, set_context
8
- from pyworkflow.core.workflow import execute_workflow_with_context, workflow
9
- from pyworkflow.storage.file import FileStorageBackend
10
-
11
-
12
- class TestWorkflowDecorator:
13
- """Test the @workflow decorator."""
14
-
15
- def test_workflow_decorator_basic(self):
16
- """Test basic workflow decoration."""
17
-
18
- @workflow()
19
- async def simple_workflow():
20
- return "success"
21
-
22
- # Check that workflow attributes are set
23
- assert hasattr(simple_workflow, "__workflow__")
24
- assert simple_workflow.__workflow__ is True
25
- assert simple_workflow.__workflow_name__ == "simple_workflow"
26
-
27
- def test_workflow_decorator_with_name(self):
28
- """Test workflow decorator with custom name."""
29
-
30
- @workflow(name="custom_name")
31
- async def my_workflow():
32
- return "success"
33
-
34
- assert my_workflow.__workflow_name__ == "custom_name"
35
-
36
- def test_workflow_decorator_with_max_duration(self):
37
- """Test workflow decorator with max_duration."""
38
-
39
- @workflow(max_duration="2h")
40
- async def timed_workflow():
41
- return "success"
42
-
43
- assert timed_workflow.__workflow_max_duration__ == "2h"
44
-
45
- def test_workflow_decorator_with_tags(self):
46
- """Test workflow decorator with tags."""
47
- tags = ["backend", "critical"]
48
-
49
- @workflow(tags=tags)
50
- async def tagged_workflow():
51
- return "success"
52
-
53
- assert tagged_workflow.__workflow_tags__ == tags
54
-
55
- def test_workflow_decorator_with_too_many_tags(self):
56
- """Test workflow decorator rejects more than 3 tags."""
57
- with pytest.raises(ValueError, match="at most 3 tags"):
58
-
59
- @workflow(tags=["a", "b", "c", "d"])
60
- async def too_many_tags_workflow():
61
- return "success"
62
-
63
- @pytest.mark.asyncio
64
- async def test_workflow_execution(self):
65
- """Test basic workflow execution."""
66
-
67
- @workflow()
68
- async def test_workflow(x: int):
69
- return x * 2
70
-
71
- result = await test_workflow(5)
72
- assert result == 10
73
-
74
- def test_workflow_registration(self):
75
- """Test that workflow is registered in global registry."""
76
- from pyworkflow.core.registry import get_workflow
77
-
78
- @workflow(name="registered_workflow")
79
- async def my_workflow():
80
- return "success"
81
-
82
- # Check that it's registered
83
- workflow_meta = get_workflow("registered_workflow")
84
- assert workflow_meta is not None
85
- assert workflow_meta.name == "registered_workflow"
86
-
87
- @pytest.mark.asyncio
88
- async def test_workflow_with_args_and_kwargs(self):
89
- """Test workflow with various argument types."""
90
-
91
- @workflow()
92
- async def args_workflow(a: int, b: int, c: int = 10):
93
- return a + b + c
94
-
95
- result = await args_workflow(1, 2, c=3)
96
- assert result == 6
97
-
98
-
99
- class TestWorkflowExecution:
100
- """Test workflow execution with context."""
101
-
102
- @pytest.mark.asyncio
103
- async def test_execute_workflow_with_context(self, tmp_path):
104
- """Test executing a workflow with proper context setup."""
105
-
106
- @workflow()
107
- async def context_workflow(value: str):
108
- ctx = get_context()
109
- assert ctx.run_id == "test_run_123"
110
- assert ctx.workflow_name == "test_workflow"
111
- return f"processed: {value}"
112
-
113
- # Create storage backend
114
- storage = FileStorageBackend(base_path=str(tmp_path))
115
-
116
- # Execute with context
117
- result = await execute_workflow_with_context(
118
- workflow_func=context_workflow,
119
- run_id="test_run_123",
120
- workflow_name="test_workflow",
121
- storage=storage,
122
- args=("test_value",),
123
- kwargs={},
124
- )
125
-
126
- assert result == "processed: test_value"
127
-
128
- @pytest.mark.asyncio
129
- async def test_context_cleared_after_execution(self, tmp_path):
130
- """Test that context is cleared after workflow execution."""
131
-
132
- @workflow()
133
- async def cleanup_workflow():
134
- return "done"
135
-
136
- storage = FileStorageBackend(base_path=str(tmp_path))
137
-
138
- await execute_workflow_with_context(
139
- workflow_func=cleanup_workflow,
140
- run_id="test_run",
141
- workflow_name="cleanup_test",
142
- storage=storage,
143
- args=(),
144
- kwargs={},
145
- )
146
-
147
- # Context should be cleared
148
- assert not has_context()
149
-
150
- @pytest.mark.asyncio
151
- async def test_workflow_exception_handling(self, tmp_path):
152
- """Test that workflow exceptions are properly handled."""
153
-
154
- @workflow()
155
- async def failing_workflow():
156
- raise ValueError("Test error")
157
-
158
- storage = FileStorageBackend(base_path=str(tmp_path))
159
-
160
- with pytest.raises(ValueError, match="Test error"):
161
- await execute_workflow_with_context(
162
- workflow_func=failing_workflow,
163
- run_id="test_run",
164
- workflow_name="failing_test",
165
- storage=storage,
166
- args=(),
167
- kwargs={},
168
- )
169
-
170
- # Context should still be cleared after exception
171
- assert not has_context()
172
-
173
- @pytest.mark.asyncio
174
- async def test_workflow_event_recording(self, tmp_path):
175
- """Test that workflow execution records events."""
176
-
177
- @workflow()
178
- async def event_workflow():
179
- return "completed"
180
-
181
- storage = FileStorageBackend(base_path=str(tmp_path))
182
- run_id = "test_run_events"
183
-
184
- await execute_workflow_with_context(
185
- workflow_func=event_workflow,
186
- run_id=run_id,
187
- workflow_name="event_test",
188
- storage=storage,
189
- args=(),
190
- kwargs={},
191
- )
192
-
193
- # Check that events were recorded
194
- events = await storage.get_events(run_id)
195
- assert len(events) >= 1
196
-
197
- # Should have workflow.completed event
198
- event_types = [e.type.value for e in events]
199
- assert "workflow.completed" in event_types
200
-
201
- @pytest.mark.asyncio
202
- async def test_workflow_with_nested_context(self, tmp_path):
203
- """Test workflow execution restores previous context."""
204
- from pyworkflow.context import reset_context
205
-
206
- # Set up an initial context
207
- initial_storage = FileStorageBackend(base_path=str(tmp_path / "initial"))
208
- initial_ctx = LocalContext(
209
- run_id="initial_run",
210
- workflow_name="initial_workflow",
211
- storage=initial_storage,
212
- )
213
- initial_token = set_context(initial_ctx)
214
-
215
- try:
216
-
217
- @workflow()
218
- async def nested_workflow():
219
- ctx = get_context()
220
- # This should be the new context
221
- assert ctx.run_id == "nested_run"
222
- return "nested"
223
-
224
- storage = FileStorageBackend(base_path=str(tmp_path / "nested"))
225
-
226
- result = await execute_workflow_with_context(
227
- workflow_func=nested_workflow,
228
- run_id="nested_run",
229
- workflow_name="nested_test",
230
- storage=storage,
231
- args=(),
232
- kwargs={},
233
- )
234
-
235
- assert result == "nested"
236
-
237
- # After execution, context should be restored to initial
238
- # (token-based reset restores previous context)
239
- assert has_context()
240
- ctx = get_context()
241
- assert ctx.run_id == "initial_run"
242
- finally:
243
- reset_context(initial_token)