pyworkflow-engine 0.1.7__py3-none-any.whl → 0.1.10__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 (146) 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/config.py +94 -17
  9. pyworkflow/context/__init__.py +13 -0
  10. pyworkflow/context/base.py +26 -0
  11. pyworkflow/context/local.py +80 -0
  12. pyworkflow/context/step_context.py +295 -0
  13. pyworkflow/core/registry.py +6 -1
  14. pyworkflow/core/step.py +141 -0
  15. pyworkflow/core/workflow.py +56 -0
  16. pyworkflow/engine/events.py +30 -0
  17. pyworkflow/engine/replay.py +39 -0
  18. pyworkflow/primitives/child_workflow.py +1 -1
  19. pyworkflow/runtime/local.py +1 -1
  20. pyworkflow/storage/__init__.py +14 -0
  21. pyworkflow/storage/base.py +35 -0
  22. pyworkflow/storage/cassandra.py +1747 -0
  23. pyworkflow/storage/config.py +69 -0
  24. pyworkflow/storage/dynamodb.py +31 -2
  25. pyworkflow/storage/file.py +28 -0
  26. pyworkflow/storage/memory.py +18 -0
  27. pyworkflow/storage/mysql.py +1159 -0
  28. pyworkflow/storage/postgres.py +27 -2
  29. pyworkflow/storage/schemas.py +4 -3
  30. pyworkflow/storage/sqlite.py +25 -2
  31. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.dist-info}/METADATA +7 -4
  32. pyworkflow_engine-0.1.10.dist-info/RECORD +91 -0
  33. pyworkflow_engine-0.1.10.dist-info/top_level.txt +1 -0
  34. dashboard/backend/app/__init__.py +0 -1
  35. dashboard/backend/app/config.py +0 -32
  36. dashboard/backend/app/controllers/__init__.py +0 -6
  37. dashboard/backend/app/controllers/run_controller.py +0 -86
  38. dashboard/backend/app/controllers/workflow_controller.py +0 -33
  39. dashboard/backend/app/dependencies/__init__.py +0 -5
  40. dashboard/backend/app/dependencies/storage.py +0 -50
  41. dashboard/backend/app/repositories/__init__.py +0 -6
  42. dashboard/backend/app/repositories/run_repository.py +0 -80
  43. dashboard/backend/app/repositories/workflow_repository.py +0 -27
  44. dashboard/backend/app/rest/__init__.py +0 -8
  45. dashboard/backend/app/rest/v1/__init__.py +0 -12
  46. dashboard/backend/app/rest/v1/health.py +0 -33
  47. dashboard/backend/app/rest/v1/runs.py +0 -133
  48. dashboard/backend/app/rest/v1/workflows.py +0 -41
  49. dashboard/backend/app/schemas/__init__.py +0 -23
  50. dashboard/backend/app/schemas/common.py +0 -16
  51. dashboard/backend/app/schemas/event.py +0 -24
  52. dashboard/backend/app/schemas/hook.py +0 -25
  53. dashboard/backend/app/schemas/run.py +0 -54
  54. dashboard/backend/app/schemas/step.py +0 -28
  55. dashboard/backend/app/schemas/workflow.py +0 -31
  56. dashboard/backend/app/server.py +0 -87
  57. dashboard/backend/app/services/__init__.py +0 -6
  58. dashboard/backend/app/services/run_service.py +0 -240
  59. dashboard/backend/app/services/workflow_service.py +0 -155
  60. dashboard/backend/main.py +0 -18
  61. docs/concepts/cancellation.mdx +0 -362
  62. docs/concepts/continue-as-new.mdx +0 -434
  63. docs/concepts/events.mdx +0 -266
  64. docs/concepts/fault-tolerance.mdx +0 -370
  65. docs/concepts/hooks.mdx +0 -552
  66. docs/concepts/limitations.mdx +0 -167
  67. docs/concepts/schedules.mdx +0 -775
  68. docs/concepts/sleep.mdx +0 -312
  69. docs/concepts/steps.mdx +0 -301
  70. docs/concepts/workflows.mdx +0 -255
  71. docs/guides/cli.mdx +0 -942
  72. docs/guides/configuration.mdx +0 -560
  73. docs/introduction.mdx +0 -155
  74. docs/quickstart.mdx +0 -279
  75. examples/__init__.py +0 -1
  76. examples/celery/__init__.py +0 -1
  77. examples/celery/durable/docker-compose.yml +0 -55
  78. examples/celery/durable/pyworkflow.config.yaml +0 -12
  79. examples/celery/durable/workflows/__init__.py +0 -122
  80. examples/celery/durable/workflows/basic.py +0 -87
  81. examples/celery/durable/workflows/batch_processing.py +0 -102
  82. examples/celery/durable/workflows/cancellation.py +0 -273
  83. examples/celery/durable/workflows/child_workflow_patterns.py +0 -240
  84. examples/celery/durable/workflows/child_workflows.py +0 -202
  85. examples/celery/durable/workflows/continue_as_new.py +0 -260
  86. examples/celery/durable/workflows/fault_tolerance.py +0 -210
  87. examples/celery/durable/workflows/hooks.py +0 -211
  88. examples/celery/durable/workflows/idempotency.py +0 -112
  89. examples/celery/durable/workflows/long_running.py +0 -99
  90. examples/celery/durable/workflows/retries.py +0 -101
  91. examples/celery/durable/workflows/schedules.py +0 -209
  92. examples/celery/transient/01_basic_workflow.py +0 -91
  93. examples/celery/transient/02_fault_tolerance.py +0 -257
  94. examples/celery/transient/__init__.py +0 -20
  95. examples/celery/transient/pyworkflow.config.yaml +0 -25
  96. examples/local/__init__.py +0 -1
  97. examples/local/durable/01_basic_workflow.py +0 -94
  98. examples/local/durable/02_file_storage.py +0 -132
  99. examples/local/durable/03_retries.py +0 -169
  100. examples/local/durable/04_long_running.py +0 -119
  101. examples/local/durable/05_event_log.py +0 -145
  102. examples/local/durable/06_idempotency.py +0 -148
  103. examples/local/durable/07_hooks.py +0 -334
  104. examples/local/durable/08_cancellation.py +0 -233
  105. examples/local/durable/09_child_workflows.py +0 -198
  106. examples/local/durable/10_child_workflow_patterns.py +0 -265
  107. examples/local/durable/11_continue_as_new.py +0 -249
  108. examples/local/durable/12_schedules.py +0 -198
  109. examples/local/durable/__init__.py +0 -1
  110. examples/local/transient/01_quick_tasks.py +0 -87
  111. examples/local/transient/02_retries.py +0 -130
  112. examples/local/transient/03_sleep.py +0 -141
  113. examples/local/transient/__init__.py +0 -1
  114. pyworkflow_engine-0.1.7.dist-info/RECORD +0 -196
  115. pyworkflow_engine-0.1.7.dist-info/top_level.txt +0 -5
  116. tests/examples/__init__.py +0 -0
  117. tests/integration/__init__.py +0 -0
  118. tests/integration/test_cancellation.py +0 -330
  119. tests/integration/test_child_workflows.py +0 -439
  120. tests/integration/test_continue_as_new.py +0 -428
  121. tests/integration/test_dynamodb_storage.py +0 -1146
  122. tests/integration/test_fault_tolerance.py +0 -369
  123. tests/integration/test_schedule_storage.py +0 -484
  124. tests/unit/__init__.py +0 -0
  125. tests/unit/backends/__init__.py +0 -1
  126. tests/unit/backends/test_dynamodb_storage.py +0 -1554
  127. tests/unit/backends/test_postgres_storage.py +0 -1281
  128. tests/unit/backends/test_sqlite_storage.py +0 -1460
  129. tests/unit/conftest.py +0 -41
  130. tests/unit/test_cancellation.py +0 -364
  131. tests/unit/test_child_workflows.py +0 -680
  132. tests/unit/test_continue_as_new.py +0 -441
  133. tests/unit/test_event_limits.py +0 -316
  134. tests/unit/test_executor.py +0 -320
  135. tests/unit/test_fault_tolerance.py +0 -334
  136. tests/unit/test_hooks.py +0 -495
  137. tests/unit/test_registry.py +0 -261
  138. tests/unit/test_replay.py +0 -420
  139. tests/unit/test_schedule_schemas.py +0 -285
  140. tests/unit/test_schedule_utils.py +0 -286
  141. tests/unit/test_scheduled_workflow.py +0 -274
  142. tests/unit/test_step.py +0 -353
  143. tests/unit/test_workflow.py +0 -243
  144. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.dist-info}/WHEEL +0 -0
  145. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.dist-info}/entry_points.txt +0 -0
  146. {pyworkflow_engine-0.1.7.dist-info → pyworkflow_engine-0.1.10.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)