processes 1.0.4__tar.gz → 2.0.1__tar.gz
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.
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/docs.yml +11 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/lint.yml +4 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/mypy.yml +4 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/publish.yml +0 -3
- {processes-1.0.4 → processes-2.0.1}/.gitignore +2 -0
- processes-2.0.1/CHANGELOG.md +65 -0
- {processes-1.0.4 → processes-2.0.1}/PKG-INFO +112 -40
- processes-2.0.1/README.md +210 -0
- {processes-1.0.4 → processes-2.0.1}/assets/banner.svg +53 -22
- processes-2.0.1/assets/logo.png +0 -0
- processes-2.0.1/assets/logo.svg +71 -0
- processes-2.0.1/assets/social_banner.png +0 -0
- processes-2.0.1/assets/social_banner.svg +100 -0
- processes-2.0.1/docs/assets/favicon.svg +26 -0
- processes-2.0.1/docs/examples/advanced.md +215 -0
- processes-2.0.1/docs/examples/basic.md +160 -0
- processes-2.0.1/docs/examples/intro.md +13 -0
- processes-1.0.4/README.md → processes-2.0.1/docs/index.md +1 -4
- processes-2.0.1/docs/reference.md +9 -0
- {processes-1.0.4 → processes-2.0.1}/examples/02_task_dependencies_result_passing/README.md +1 -1
- processes-2.0.1/examples/README.md +40 -0
- processes-2.0.1/mkdocs.yml +53 -0
- {processes-1.0.4 → processes-2.0.1}/pyproject.toml +7 -0
- processes-2.0.1/src/processes/exception_html_formatter.py +351 -0
- {processes-1.0.4 → processes-2.0.1}/src/processes/html_logging.py +42 -83
- {processes-1.0.4 → processes-2.0.1}/src/processes/process.py +9 -4
- {processes-1.0.4 → processes-2.0.1}/src/processes/task.py +21 -16
- processes-2.0.1/src/processes/themes/languages/de.json +15 -0
- processes-2.0.1/src/processes/themes/languages/en.json +15 -0
- processes-2.0.1/src/processes/themes/languages/es.json +15 -0
- processes-2.0.1/src/processes/themes/languages/fr.json +15 -0
- processes-2.0.1/src/processes/themes/languages/it.json +15 -0
- processes-2.0.1/src/processes/themes/languages/pt.json +15 -0
- processes-2.0.1/src/processes/themes/palettes/catppuccin.css +11 -0
- processes-2.0.1/src/processes/themes/palettes/neobones.css +11 -0
- processes-2.0.1/src/processes/themes/palettes/neutral.css +11 -0
- processes-2.0.1/src/processes/themes/palettes/slate.css +11 -0
- processes-2.0.1/src/processes/themes/styles/classic.html +49 -0
- processes-2.0.1/src/processes/themes/styles/compact.html +67 -0
- processes-2.0.1/src/processes/themes/styles/modern.html +115 -0
- processes-2.0.1/tests/conftest.py +9 -0
- processes-2.0.1/tests/manual_tests/__init__.py +0 -0
- processes-2.0.1/tests/manual_tests/manual_pipeline_inspect.py +423 -0
- processes-2.0.1/tests/manual_tests/manual_themed_tracebacks.py +302 -0
- processes-2.0.1/tests/test_complex_dag_failures.py +361 -0
- processes-2.0.1/tests/test_email_themes.py +489 -0
- processes-2.0.1/tests/test_examples.py +48 -0
- {processes-1.0.4 → processes-2.0.1}/tests/test_normal_run_no_errors.py +22 -14
- processes-2.0.1/tests/test_parallel_race_conditions.py +251 -0
- processes-1.0.4/CHANGELOG.md +0 -35
- processes-1.0.4/docs/index.md +0 -127
- processes-1.0.4/docs/reference.md +0 -6
- processes-1.0.4/examples/README.md +0 -112
- processes-1.0.4/mkdocs.yml +0 -31
- processes-1.0.4/tests/mail_config.example.toml +0 -13
- processes-1.0.4/tests/manual_test_email.py +0 -103
- processes-1.0.4/tests/test_examples.py +0 -17
- {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/custom.md +0 -0
- {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/lint-pr.yml +0 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/tags.yml +0 -0
- {processes-1.0.4 → processes-2.0.1}/.github/workflows/tests.yml +0 -0
- {processes-1.0.4 → processes-2.0.1}/LICENSE +0 -0
- {processes-1.0.4 → processes-2.0.1}/examples/01_basic_tasks_and_dependencies/README.md +0 -0
- {processes-1.0.4 → processes-2.0.1}/examples/01_basic_tasks_and_dependencies/example1.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/examples/02_task_dependencies_result_passing/example2.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/pytest.ini +0 -0
- {processes-1.0.4 → processes-2.0.1}/src/processes/__init__.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/__init__.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/log_cleaner.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/test_args_kwargs.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/test_dependencies.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/test_logfiles.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/tests/test_unique_name.py +0 -0
- {processes-1.0.4 → processes-2.0.1}/uv.lock +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## v2.0.1 (2026-06-12)
|
|
2
|
+
|
|
3
|
+
### Fix
|
|
4
|
+
|
|
5
|
+
- **publish.yml**: remove unneeded step ruff format check in quality-gate
|
|
6
|
+
|
|
7
|
+
## v2.0.0 (2026-06-12)
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGE
|
|
10
|
+
|
|
11
|
+
- log records no longer carry
|
|
12
|
+
``post_traceback_html_body``. Consumers introspecting that attribute
|
|
13
|
+
must read ``record.task_context`` instead.
|
|
14
|
+
|
|
15
|
+
### Feat
|
|
16
|
+
|
|
17
|
+
- **email_alerting**: Traced Variables section with file:line reference in email body
|
|
18
|
+
- **email_alerting**: language alternatives for HTML email body
|
|
19
|
+
- **email_alerting**: template-driven HTML body from pure metadata
|
|
20
|
+
|
|
21
|
+
### Fix
|
|
22
|
+
|
|
23
|
+
- **process**: parallel runner no longer raises on unrunnable tail
|
|
24
|
+
|
|
25
|
+
## v1.0.5 (2026-01-19)
|
|
26
|
+
|
|
27
|
+
### Fix
|
|
28
|
+
|
|
29
|
+
- **docs**: added urls to pyproject file
|
|
30
|
+
|
|
31
|
+
## v1.0.4 (2026-01-19)
|
|
32
|
+
|
|
33
|
+
### Fix
|
|
34
|
+
|
|
35
|
+
- **docs**: added pypi badge and pip install instruction to readme
|
|
36
|
+
|
|
37
|
+
## v1.0.3 (2026-01-19)
|
|
38
|
+
|
|
39
|
+
### Fix
|
|
40
|
+
|
|
41
|
+
- **docs**: changes in readme and index of docs to show banner
|
|
42
|
+
|
|
43
|
+
## v1.0.2 (2026-01-19)
|
|
44
|
+
|
|
45
|
+
### Fix
|
|
46
|
+
|
|
47
|
+
- **ci**: added a workflow for publishing to pypi
|
|
48
|
+
|
|
49
|
+
## v1.0.1 (2026-01-19)
|
|
50
|
+
|
|
51
|
+
### Fix
|
|
52
|
+
|
|
53
|
+
- **lint**: fix ruff formatting
|
|
54
|
+
|
|
55
|
+
## v1.0.0 (2026-01-19)
|
|
56
|
+
|
|
57
|
+
### BREAKING CHANGE
|
|
58
|
+
|
|
59
|
+
- Task.run method had one of its kwargs removed
|
|
60
|
+
|
|
61
|
+
### Fix
|
|
62
|
+
|
|
63
|
+
- **Task-can-no-longer-pass-logger-to-its-function.-Changed-some-examples,-documentations-and-better-type-hints**: Task.run method no longer can pass logger to its function as kwarg
|
|
64
|
+
|
|
65
|
+
## v0.1.0 (2026-01-18)
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: processes
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: A Python library for managing and executing dependent tasks in parallel or sequential order with automatic dependency resolution and topological sorting
|
|
5
|
+
Project-URL: Homepage, https://github.com/oliverm91/processes
|
|
6
|
+
Project-URL: Documentation, https://oliverm91.github.io/processes/
|
|
7
|
+
Project-URL: Repository, https://github.com/oliverm91/processes
|
|
8
|
+
Project-URL: Issues, https://github.com/oliverm91/processes/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/oliverm91/processes/blob/main/CHANGELOG.md
|
|
5
10
|
Author-email: Oliver Mohr Bonometti <oliver.mohr.b@gmail.com>
|
|
6
11
|
License-Expression: MIT
|
|
7
12
|
License-File: LICENSE
|
|
@@ -21,7 +26,7 @@ Description-Content-Type: text/markdown
|
|
|
21
26
|
<img src="https://raw.githubusercontent.com/oliverm91/processes/refs/heads/main/assets/banner.svg" width="100%" alt="Processes - Smart Task Orchestration">
|
|
22
27
|
</div>
|
|
23
28
|
|
|
24
|
-
# 🚀 Processes:
|
|
29
|
+
# 🚀 Processes: Robust Routines Management
|
|
25
30
|
|
|
26
31
|
[](https://www.python.org/)
|
|
27
32
|

|
|
@@ -87,53 +92,120 @@ The library operates on two main primitives:
|
|
|
87
92
|
---
|
|
88
93
|
|
|
89
94
|
## 💻 Quick Start
|
|
90
|
-
|
|
95
|
+
|
|
96
|
+
Four short examples — read top to bottom.
|
|
97
|
+
|
|
98
|
+
### 1. One task
|
|
91
99
|
|
|
92
100
|
```python
|
|
93
|
-
from
|
|
101
|
+
from processes import Process, Task
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
def hello():
|
|
104
|
+
return 42
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
tasks = [Task("greet", "run.log", hello)]
|
|
107
|
+
|
|
108
|
+
with Process(tasks) as process:
|
|
109
|
+
result = process.run()
|
|
110
|
+
|
|
111
|
+
print(result.passed_tasks_results["greet"].result) # 42
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2. `args` and `kwargs`
|
|
115
|
+
|
|
116
|
+
`args` and `kwargs` are forwarded to your function — like `func(*args, **kwargs)`.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from processes import Process, Task
|
|
120
|
+
|
|
121
|
+
def fetch(source, *, limit=100, dry_run=False):
|
|
122
|
+
return ["row1", "row2"]
|
|
102
123
|
|
|
103
|
-
# 2. If necessary, create wrappers for your Tasks.
|
|
104
|
-
def get_previous_working_day():
|
|
105
|
-
return date(2025, 12, 30)
|
|
106
|
-
def indep_task():
|
|
107
|
-
return "foo"
|
|
108
|
-
def search_and_sum_csv(t: date):
|
|
109
|
-
return 10
|
|
110
|
-
def sum_data_from_csv_and_x(x, a=1, b=2):
|
|
111
|
-
return x + a + b
|
|
112
|
-
|
|
113
|
-
# 3. Create the Task Graph (order is irrelevant, that is handled by Process)
|
|
114
124
|
tasks = [
|
|
115
|
-
Task(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
use_result_as_additional_args=True) # Adds result of t-1 task to search_and_sum_csv function as aditional args
|
|
121
|
-
]
|
|
122
|
-
),
|
|
123
|
-
Task("sum_x_and_csv", "etl.log", sum_data_from_csv_and_x,
|
|
124
|
-
args = (10,), kwargs = {"b": 100},
|
|
125
|
-
dependencies=[
|
|
126
|
-
TaskDependency("sum_csv",
|
|
127
|
-
use_result_as_additional_kwargs=True,
|
|
128
|
-
additional_kwarg_name="a")
|
|
129
|
-
]
|
|
130
|
-
)
|
|
125
|
+
Task(
|
|
126
|
+
"fetch_orders", "run.log", fetch,
|
|
127
|
+
args=("orders_api",),
|
|
128
|
+
kwargs={"limit": 500, "dry_run": True},
|
|
129
|
+
),
|
|
131
130
|
]
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
with Process(tasks) as process:
|
|
133
|
+
process.run()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 3. Dependencies + result injection
|
|
137
|
+
|
|
138
|
+
`TaskDependency` orders tasks. To also pipe the upstream result into the downstream function, pick one:
|
|
139
|
+
|
|
140
|
+
* `use_result_as_additional_args=True` — appended as the next **positional** argument.
|
|
141
|
+
* `use_result_as_additional_kwargs=True` + `additional_kwarg_name="..."` — passed as a **keyword** argument.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from processes import Process, Task, TaskDependency
|
|
145
|
+
|
|
146
|
+
def load_users():
|
|
147
|
+
return [{"id": 1}, {"id": 2}, {"id": 3}]
|
|
148
|
+
|
|
149
|
+
def enrich(api_key, users): # `users` injected positionally
|
|
150
|
+
return [{**u, "name": f"user-{u['id']}"} for u in users]
|
|
151
|
+
|
|
152
|
+
def summarize(*, enriched, label="report"): # `enriched` injected as kwarg
|
|
153
|
+
return f"{label}: {len(enriched)} users"
|
|
154
|
+
|
|
155
|
+
tasks = [
|
|
156
|
+
Task("load", "run.log", load_users),
|
|
157
|
+
|
|
158
|
+
Task(
|
|
159
|
+
"enrich", "run.log", enrich,
|
|
160
|
+
args=("MY_API_KEY",),
|
|
161
|
+
dependencies=[
|
|
162
|
+
TaskDependency("load", use_result_as_additional_args=True),
|
|
163
|
+
],
|
|
164
|
+
),
|
|
165
|
+
|
|
166
|
+
Task(
|
|
167
|
+
"summary", "run.log", summarize,
|
|
168
|
+
kwargs={"label": "daily"},
|
|
169
|
+
dependencies=[
|
|
170
|
+
TaskDependency(
|
|
171
|
+
"enrich",
|
|
172
|
+
use_result_as_additional_kwargs=True,
|
|
173
|
+
additional_kwarg_name="enriched",
|
|
174
|
+
),
|
|
175
|
+
],
|
|
176
|
+
),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
with Process(tasks) as process:
|
|
180
|
+
result = process.run(parallel=True)
|
|
181
|
+
|
|
182
|
+
print(result.passed_tasks_results["summary"].result)
|
|
183
|
+
# "daily: 3 users"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
> Task order doesn't matter — `Process` sorts them. A failure only skips its own dependents; the rest keeps running.
|
|
187
|
+
|
|
188
|
+
### 4. Email alerts on failure
|
|
189
|
+
|
|
190
|
+
Attach an `HTMLSMTPHandler` to any task. If it raises, an HTML email is sent.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from processes import HTMLSMTPHandler, Process, Task
|
|
194
|
+
|
|
195
|
+
smtp = HTMLSMTPHandler(
|
|
196
|
+
mailhost=("smtp.example.com", 587),
|
|
197
|
+
fromaddr="alerts@example.com",
|
|
198
|
+
toaddrs=["oncall@example.com"],
|
|
199
|
+
credentials=("user", "pass"),
|
|
200
|
+
secure=(), # () = STARTTLS; omit for no encryption
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
tasks = [
|
|
204
|
+
Task("risky_step", "run.log", lambda: 1 / 0, html_mail_handler=smtp),
|
|
205
|
+
]
|
|
136
206
|
|
|
207
|
+
with Process(tasks) as process:
|
|
208
|
+
process.run()
|
|
137
209
|
```
|
|
138
210
|
|
|
139
211
|
---
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/oliverm91/processes/refs/heads/main/assets/banner.svg" width="100%" alt="Processes - Smart Task Orchestration">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
# 🚀 Processes: Robust Routines Management
|
|
6
|
+
|
|
7
|
+
[](https://www.python.org/)
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
[](https://oliverm91.github.io/processes/)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
[](https://github.com/oliverm91/processes/actions/workflows/tests.yml)
|
|
16
|
+
[](https://github.com/oliverm91/processes/actions/workflows/lint.yml)
|
|
17
|
+
[](https://github.com/oliverm91/processes/actions/workflows/mypy.yml)
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/processes/)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
**Processes** is a lightweight, high-performance Python library designed to execute complex task graphs. It manages **dependencies**, handles **parallel execution**, and ensures system resilience without any external libraries.
|
|
25
|
+
|
|
26
|
+
File logging and **email notification** is supported.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 📑 Table of Contents
|
|
31
|
+
* [✨ Features](#-features)
|
|
32
|
+
* [⚙️ Core Concepts](#️-core-concepts)
|
|
33
|
+
* [🛠️ Use Cases](#️-use-cases)
|
|
34
|
+
* [💻 Quick Start](#-quick-start)
|
|
35
|
+
* [🛡️ Fault Tolerance & Logs](#️-fault-tolerance--logs)
|
|
36
|
+
* [📦 Installation](#-installation)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ✨ Features
|
|
41
|
+
|
|
42
|
+
* **🐍 Pure Python:** Zero external dependencies. Built entirely on the **Python Standard Library**.
|
|
43
|
+
* **⚡ Parallel Execution:** Built-in support for parallelization to maximize throughput.
|
|
44
|
+
* **🔗 Dependency Resolution:** Automatically sorts and executes tasks based on their requirements, regardless of input order.
|
|
45
|
+
* **📝 Shared Logging:** Multiple tasks can write to the same logfile or maintain separate ones seamlessly.
|
|
46
|
+
* **📧 Email Notifications:** Integrated SMTP support (including HTML) to alert you the moment an exception occurs.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ⚙️ Core Concepts
|
|
51
|
+
|
|
52
|
+
The library operates on two main primitives:
|
|
53
|
+
|
|
54
|
+
1. **Task**: The atomic unit of work. It encapsulates a function, its parameters, its specific logfile, and its relationship with other tasks.
|
|
55
|
+
2. **Process**: The orchestrator. It builds the execution graph, validates dependencies, and manages the lifecycle of the entire workflow.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🛠️ Use Cases
|
|
61
|
+
- **ETL Pipelines:** Fetch data from an API, transform it, and load it into a database as separate, dependent tasks.
|
|
62
|
+
|
|
63
|
+
- **System Maintenance:** Run parallel cleanup scripts, check server health, and receive email alerts if a specific check fails.
|
|
64
|
+
|
|
65
|
+
- **Automated Reporting:** Generate multiple data parts in parallel, aggregate them into a final report, and distribute via SMTP.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 💻 Quick Start
|
|
71
|
+
|
|
72
|
+
Four short examples — read top to bottom.
|
|
73
|
+
|
|
74
|
+
### 1. One task
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from processes import Process, Task
|
|
78
|
+
|
|
79
|
+
def hello():
|
|
80
|
+
return 42
|
|
81
|
+
|
|
82
|
+
tasks = [Task("greet", "run.log", hello)]
|
|
83
|
+
|
|
84
|
+
with Process(tasks) as process:
|
|
85
|
+
result = process.run()
|
|
86
|
+
|
|
87
|
+
print(result.passed_tasks_results["greet"].result) # 42
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. `args` and `kwargs`
|
|
91
|
+
|
|
92
|
+
`args` and `kwargs` are forwarded to your function — like `func(*args, **kwargs)`.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from processes import Process, Task
|
|
96
|
+
|
|
97
|
+
def fetch(source, *, limit=100, dry_run=False):
|
|
98
|
+
return ["row1", "row2"]
|
|
99
|
+
|
|
100
|
+
tasks = [
|
|
101
|
+
Task(
|
|
102
|
+
"fetch_orders", "run.log", fetch,
|
|
103
|
+
args=("orders_api",),
|
|
104
|
+
kwargs={"limit": 500, "dry_run": True},
|
|
105
|
+
),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
with Process(tasks) as process:
|
|
109
|
+
process.run()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. Dependencies + result injection
|
|
113
|
+
|
|
114
|
+
`TaskDependency` orders tasks. To also pipe the upstream result into the downstream function, pick one:
|
|
115
|
+
|
|
116
|
+
* `use_result_as_additional_args=True` — appended as the next **positional** argument.
|
|
117
|
+
* `use_result_as_additional_kwargs=True` + `additional_kwarg_name="..."` — passed as a **keyword** argument.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from processes import Process, Task, TaskDependency
|
|
121
|
+
|
|
122
|
+
def load_users():
|
|
123
|
+
return [{"id": 1}, {"id": 2}, {"id": 3}]
|
|
124
|
+
|
|
125
|
+
def enrich(api_key, users): # `users` injected positionally
|
|
126
|
+
return [{**u, "name": f"user-{u['id']}"} for u in users]
|
|
127
|
+
|
|
128
|
+
def summarize(*, enriched, label="report"): # `enriched` injected as kwarg
|
|
129
|
+
return f"{label}: {len(enriched)} users"
|
|
130
|
+
|
|
131
|
+
tasks = [
|
|
132
|
+
Task("load", "run.log", load_users),
|
|
133
|
+
|
|
134
|
+
Task(
|
|
135
|
+
"enrich", "run.log", enrich,
|
|
136
|
+
args=("MY_API_KEY",),
|
|
137
|
+
dependencies=[
|
|
138
|
+
TaskDependency("load", use_result_as_additional_args=True),
|
|
139
|
+
],
|
|
140
|
+
),
|
|
141
|
+
|
|
142
|
+
Task(
|
|
143
|
+
"summary", "run.log", summarize,
|
|
144
|
+
kwargs={"label": "daily"},
|
|
145
|
+
dependencies=[
|
|
146
|
+
TaskDependency(
|
|
147
|
+
"enrich",
|
|
148
|
+
use_result_as_additional_kwargs=True,
|
|
149
|
+
additional_kwarg_name="enriched",
|
|
150
|
+
),
|
|
151
|
+
],
|
|
152
|
+
),
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
with Process(tasks) as process:
|
|
156
|
+
result = process.run(parallel=True)
|
|
157
|
+
|
|
158
|
+
print(result.passed_tasks_results["summary"].result)
|
|
159
|
+
# "daily: 3 users"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> Task order doesn't matter — `Process` sorts them. A failure only skips its own dependents; the rest keeps running.
|
|
163
|
+
|
|
164
|
+
### 4. Email alerts on failure
|
|
165
|
+
|
|
166
|
+
Attach an `HTMLSMTPHandler` to any task. If it raises, an HTML email is sent.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from processes import HTMLSMTPHandler, Process, Task
|
|
170
|
+
|
|
171
|
+
smtp = HTMLSMTPHandler(
|
|
172
|
+
mailhost=("smtp.example.com", 587),
|
|
173
|
+
fromaddr="alerts@example.com",
|
|
174
|
+
toaddrs=["oncall@example.com"],
|
|
175
|
+
credentials=("user", "pass"),
|
|
176
|
+
secure=(), # () = STARTTLS; omit for no encryption
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
tasks = [
|
|
180
|
+
Task("risky_step", "run.log", lambda: 1 / 0, html_mail_handler=smtp),
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
with Process(tasks) as process:
|
|
184
|
+
process.run()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🛡️ Fault Tolerance & Logs
|
|
190
|
+
### Resilience by Design
|
|
191
|
+
If a `Task` raises an exception, the `Process` **does not stop**. It intelligently skips any tasks that depend on the failed one but continues to execute all other independent branches of your workflow.
|
|
192
|
+
|
|
193
|
+
### Advanced Logging
|
|
194
|
+
All tasks record their execution flow to their assigned logfiles. You can share a single logfile across the whole process or isolate specific tasks for easier debugging.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 📦 Installation
|
|
200
|
+
|
|
201
|
+
Registered in PyPI: https://pypi.org/project/processes/
|
|
202
|
+
```bash
|
|
203
|
+
pip install processes
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Also, since it's a pure Python library, you can install it directly from the repository:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
pip install git+https://github.com/oliverm91/processes.git
|
|
210
|
+
```
|
|
@@ -28,30 +28,61 @@
|
|
|
28
28
|
<path d="M0,0 L1200,0 M0,100 L1200,100 M0,200 L1200,200 M0,300 L1200,300 M0,400 L1200,400" stroke="#4ecca3" stroke-width="1"/>
|
|
29
29
|
</g>
|
|
30
30
|
|
|
31
|
-
<!-- Task flow visualization (left side) -->
|
|
32
|
-
<g opacity="0.
|
|
33
|
-
<!--
|
|
34
|
-
<
|
|
35
|
-
|
|
31
|
+
<!-- Task flow visualization (left side) - New logo design -->
|
|
32
|
+
<g opacity="0.9" transform="translate(30, 80)">
|
|
33
|
+
<!-- Connections (edges) -->
|
|
34
|
+
<g stroke-width="3" stroke-linecap="round">
|
|
35
|
+
<!-- Blue branches -->
|
|
36
|
+
<g stroke="#4ecca3" fill="none">
|
|
37
|
+
<!-- Top to second level -->
|
|
38
|
+
<path d="M 125 20 L 80 60"/>
|
|
39
|
+
<path d="M 125 20 L 170 60"/>
|
|
40
|
+
|
|
41
|
+
<!-- Second to third level -->
|
|
42
|
+
<path d="M 80 60 L 125 100"/>
|
|
43
|
+
<path d="M 170 60 L 125 100"/>
|
|
44
|
+
<path d="M 80 60 L 60 120"/>
|
|
45
|
+
|
|
46
|
+
<!-- Vertical parallel branches -->
|
|
47
|
+
<path d="M 125 100 L 125 185"/>
|
|
48
|
+
<path d="M 60 120 L 60 185"/>
|
|
49
|
+
</g>
|
|
50
|
+
|
|
51
|
+
<!-- Connection to failing task (blue) -->
|
|
52
|
+
<g stroke="#4ecca3" fill="none">
|
|
53
|
+
<path d="M 170 60 L 190 120"/>
|
|
54
|
+
</g>
|
|
55
|
+
|
|
56
|
+
<!-- Failed branch (red) -->
|
|
57
|
+
<g stroke="#ef4444" fill="none">
|
|
58
|
+
<path d="M 190 120 L 190 185"/>
|
|
59
|
+
</g>
|
|
60
|
+
</g>
|
|
36
61
|
|
|
37
|
-
<!--
|
|
38
|
-
<
|
|
62
|
+
<!-- Nodes (tasks) -->
|
|
63
|
+
<g fill="#4ecca3" filter="url(#glow)">
|
|
64
|
+
<!-- Top node -->
|
|
65
|
+
<circle cx="125" cy="20" r="8"/>
|
|
66
|
+
|
|
67
|
+
<!-- Second level -->
|
|
68
|
+
<circle cx="80" cy="60" r="8"/>
|
|
69
|
+
<circle cx="170" cy="60" r="8"/>
|
|
70
|
+
|
|
71
|
+
<!-- Third level -->
|
|
72
|
+
<circle cx="125" cy="100" r="8"/>
|
|
73
|
+
<circle cx="60" cy="120" r="7"/>
|
|
74
|
+
|
|
75
|
+
<!-- Final nodes -->
|
|
76
|
+
<circle cx="60" cy="190" r="10"/>
|
|
77
|
+
<circle cx="125" cy="190" r="10"/>
|
|
78
|
+
</g>
|
|
39
79
|
|
|
40
|
-
<!--
|
|
41
|
-
<
|
|
42
|
-
|
|
80
|
+
<!-- Failed branch nodes (red) -->
|
|
81
|
+
<g fill="#ef4444" filter="url(#glow)">
|
|
82
|
+
<circle cx="190" cy="120" r="7"/>
|
|
83
|
+
<circle cx="190" cy="190" r="10"/>
|
|
84
|
+
</g>
|
|
43
85
|
|
|
44
|
-
<!-- Merge point -->
|
|
45
|
-
<circle cx="200" cy="200" r="8" fill="#4ecca3" filter="url(#glow)"/>
|
|
46
|
-
|
|
47
|
-
<!-- Arrow to right -->
|
|
48
|
-
<line x1="200" y1="200" x2="314" y2="200" stroke="#4ecca3" stroke-width="3" stroke-linecap="round">
|
|
49
|
-
<animate attributeName="stroke-dasharray" values="0 400; 400 0" dur="2s" repeatCount="indefinite"/>
|
|
50
|
-
<animate attributeName="stroke-dashoffset" values="0; -400" dur="2s" repeatCount="indefinite"/>
|
|
51
|
-
</line>
|
|
52
|
-
<polygon points="320,200 305,193 305,207" fill="#4ecca3" filter="url(#glow)">
|
|
53
|
-
<animate attributeName="opacity" values="0.5;1;0.5" dur="2s" repeatCount="indefinite"/>
|
|
54
|
-
</polygon>
|
|
55
86
|
</g>
|
|
56
87
|
|
|
57
88
|
<!-- Main Title -->
|
|
@@ -61,7 +92,7 @@
|
|
|
61
92
|
|
|
62
93
|
<!-- Subtitle -->
|
|
63
94
|
<text x="600" y="210" font-family="'SF Mono', 'Monaco', 'Courier New', monospace" font-size="28" fill="#ffffff" text-anchor="middle" opacity="0.9">
|
|
64
|
-
|
|
95
|
+
Robust Routines Management
|
|
65
96
|
</text>
|
|
66
97
|
|
|
67
98
|
<!-- Feature badges -->
|
|
Binary file
|