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.
Files changed (76) hide show
  1. {processes-1.0.4 → processes-2.0.1}/.github/workflows/docs.yml +11 -0
  2. {processes-1.0.4 → processes-2.0.1}/.github/workflows/lint.yml +4 -0
  3. {processes-1.0.4 → processes-2.0.1}/.github/workflows/mypy.yml +4 -0
  4. {processes-1.0.4 → processes-2.0.1}/.github/workflows/publish.yml +0 -3
  5. {processes-1.0.4 → processes-2.0.1}/.gitignore +2 -0
  6. processes-2.0.1/CHANGELOG.md +65 -0
  7. {processes-1.0.4 → processes-2.0.1}/PKG-INFO +112 -40
  8. processes-2.0.1/README.md +210 -0
  9. {processes-1.0.4 → processes-2.0.1}/assets/banner.svg +53 -22
  10. processes-2.0.1/assets/logo.png +0 -0
  11. processes-2.0.1/assets/logo.svg +71 -0
  12. processes-2.0.1/assets/social_banner.png +0 -0
  13. processes-2.0.1/assets/social_banner.svg +100 -0
  14. processes-2.0.1/docs/assets/favicon.svg +26 -0
  15. processes-2.0.1/docs/examples/advanced.md +215 -0
  16. processes-2.0.1/docs/examples/basic.md +160 -0
  17. processes-2.0.1/docs/examples/intro.md +13 -0
  18. processes-1.0.4/README.md → processes-2.0.1/docs/index.md +1 -4
  19. processes-2.0.1/docs/reference.md +9 -0
  20. {processes-1.0.4 → processes-2.0.1}/examples/02_task_dependencies_result_passing/README.md +1 -1
  21. processes-2.0.1/examples/README.md +40 -0
  22. processes-2.0.1/mkdocs.yml +53 -0
  23. {processes-1.0.4 → processes-2.0.1}/pyproject.toml +7 -0
  24. processes-2.0.1/src/processes/exception_html_formatter.py +351 -0
  25. {processes-1.0.4 → processes-2.0.1}/src/processes/html_logging.py +42 -83
  26. {processes-1.0.4 → processes-2.0.1}/src/processes/process.py +9 -4
  27. {processes-1.0.4 → processes-2.0.1}/src/processes/task.py +21 -16
  28. processes-2.0.1/src/processes/themes/languages/de.json +15 -0
  29. processes-2.0.1/src/processes/themes/languages/en.json +15 -0
  30. processes-2.0.1/src/processes/themes/languages/es.json +15 -0
  31. processes-2.0.1/src/processes/themes/languages/fr.json +15 -0
  32. processes-2.0.1/src/processes/themes/languages/it.json +15 -0
  33. processes-2.0.1/src/processes/themes/languages/pt.json +15 -0
  34. processes-2.0.1/src/processes/themes/palettes/catppuccin.css +11 -0
  35. processes-2.0.1/src/processes/themes/palettes/neobones.css +11 -0
  36. processes-2.0.1/src/processes/themes/palettes/neutral.css +11 -0
  37. processes-2.0.1/src/processes/themes/palettes/slate.css +11 -0
  38. processes-2.0.1/src/processes/themes/styles/classic.html +49 -0
  39. processes-2.0.1/src/processes/themes/styles/compact.html +67 -0
  40. processes-2.0.1/src/processes/themes/styles/modern.html +115 -0
  41. processes-2.0.1/tests/conftest.py +9 -0
  42. processes-2.0.1/tests/manual_tests/__init__.py +0 -0
  43. processes-2.0.1/tests/manual_tests/manual_pipeline_inspect.py +423 -0
  44. processes-2.0.1/tests/manual_tests/manual_themed_tracebacks.py +302 -0
  45. processes-2.0.1/tests/test_complex_dag_failures.py +361 -0
  46. processes-2.0.1/tests/test_email_themes.py +489 -0
  47. processes-2.0.1/tests/test_examples.py +48 -0
  48. {processes-1.0.4 → processes-2.0.1}/tests/test_normal_run_no_errors.py +22 -14
  49. processes-2.0.1/tests/test_parallel_race_conditions.py +251 -0
  50. processes-1.0.4/CHANGELOG.md +0 -35
  51. processes-1.0.4/docs/index.md +0 -127
  52. processes-1.0.4/docs/reference.md +0 -6
  53. processes-1.0.4/examples/README.md +0 -112
  54. processes-1.0.4/mkdocs.yml +0 -31
  55. processes-1.0.4/tests/mail_config.example.toml +0 -13
  56. processes-1.0.4/tests/manual_test_email.py +0 -103
  57. processes-1.0.4/tests/test_examples.py +0 -17
  58. {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  59. {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/custom.md +0 -0
  60. {processes-1.0.4 → processes-2.0.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  61. {processes-1.0.4 → processes-2.0.1}/.github/workflows/lint-pr.yml +0 -0
  62. {processes-1.0.4 → processes-2.0.1}/.github/workflows/tags.yml +0 -0
  63. {processes-1.0.4 → processes-2.0.1}/.github/workflows/tests.yml +0 -0
  64. {processes-1.0.4 → processes-2.0.1}/LICENSE +0 -0
  65. {processes-1.0.4 → processes-2.0.1}/examples/01_basic_tasks_and_dependencies/README.md +0 -0
  66. {processes-1.0.4 → processes-2.0.1}/examples/01_basic_tasks_and_dependencies/example1.py +0 -0
  67. {processes-1.0.4 → processes-2.0.1}/examples/02_task_dependencies_result_passing/example2.py +0 -0
  68. {processes-1.0.4 → processes-2.0.1}/pytest.ini +0 -0
  69. {processes-1.0.4 → processes-2.0.1}/src/processes/__init__.py +0 -0
  70. {processes-1.0.4 → processes-2.0.1}/tests/__init__.py +0 -0
  71. {processes-1.0.4 → processes-2.0.1}/tests/log_cleaner.py +0 -0
  72. {processes-1.0.4 → processes-2.0.1}/tests/test_args_kwargs.py +0 -0
  73. {processes-1.0.4 → processes-2.0.1}/tests/test_dependencies.py +0 -0
  74. {processes-1.0.4 → processes-2.0.1}/tests/test_logfiles.py +0 -0
  75. {processes-1.0.4 → processes-2.0.1}/tests/test_unique_name.py +0 -0
  76. {processes-1.0.4 → processes-2.0.1}/uv.lock +0 -0
@@ -3,6 +3,17 @@ on:
3
3
  push:
4
4
  branches:
5
5
  - main
6
+ paths:
7
+ - "**.py"
8
+ - "docs/**"
9
+ - "mkdocs.yml"
10
+ pull_request:
11
+ branches:
12
+ - main
13
+ paths:
14
+ - "**.py"
15
+ - "docs/**"
16
+ - "mkdocs.yml"
6
17
  permissions:
7
18
  contents: write
8
19
  jobs:
@@ -3,8 +3,12 @@ name: Lint
3
3
  on:
4
4
  push:
5
5
  branches: [main]
6
+ paths:
7
+ - "**.py"
6
8
  pull_request:
7
9
  branches: [main]
10
+ paths:
11
+ - "**.py"
8
12
 
9
13
  jobs:
10
14
  lint:
@@ -3,8 +3,12 @@ name: mypy-check
3
3
  on:
4
4
  push:
5
5
  branches: [main]
6
+ paths:
7
+ - "**.py"
6
8
  pull_request:
7
9
  branches: [main]
10
+ paths:
11
+ - "**.py"
8
12
 
9
13
  jobs:
10
14
  lint:
@@ -18,9 +18,6 @@ jobs:
18
18
  - name: Ruff Check
19
19
  run: uv run ruff check .
20
20
 
21
- - name: Ruff Format
22
- run: uv run ruff format --check .
23
-
24
21
  - name: Mypy
25
22
  run: uv run mypy src
26
23
 
@@ -54,6 +54,8 @@ cover/
54
54
  # Translations
55
55
  *.mo
56
56
  *.pot
57
+ CLAUDE.md
58
+ /.claude
57
59
 
58
60
  # Django stuff:
59
61
  *.log
@@ -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: 1.0.4
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: Smart Task Orchestration
29
+ # 🚀 Processes: Robust Routines Management
25
30
 
26
31
  [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
27
32
  ![Fast & Lightweight](https://img.shields.io/badge/Library-Pure%20Python-green.svg)
@@ -87,53 +92,120 @@ The library operates on two main primitives:
87
92
  ---
88
93
 
89
94
  ## 💻 Quick Start
90
- Define your tasks and their dependencies. **Processes** will handle the execution order and data injection between tasks.
95
+
96
+ Four short examples — read top to bottom.
97
+
98
+ ### 1. One task
91
99
 
92
100
  ```python
93
- from datetime import date
101
+ from processes import Process, Task
94
102
 
95
- from processes import Process, Task, TaskDependency, HTMLSMTPHandler
103
+ def hello():
104
+ return 42
96
105
 
97
- # 1. Setup Email Alerts (Optional)
98
- smtp_handler = HTMLSMTPHandler(
99
- ('smtp_server', 587), 'sender@example.com', ['admin@example.com', 'user@example.com'],
100
- use_tls=True, credentials=('user', 'pass')
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("t-1", "etl.log", get_previous_working_day),
116
- Task("intependent", "indep.log", indep_task, html_mail_handler=smtp_handler), # This task will send email on failure
117
- Task("sum_csv", "etl.log", search_and_sum_csv,
118
- dependencies= [
119
- TaskDependency("t-1",
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
- # 4. Run the Process
134
- with Process(tasks) as process: # Context Manager ensures correct disposal of loggers
135
- process_result = process.run() # To enable parallelization use .run(parallel=True)
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
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
8
+ ![Fast & Lightweight](https://img.shields.io/badge/Library-Pure%20Python-green.svg)
9
+
10
+
11
+ [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue.svg)](https://oliverm91.github.io/processes/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
+
14
+
15
+ [![Python Tests Status](https://github.com/oliverm91/processes/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/oliverm91/processes/actions/workflows/tests.yml)
16
+ [![Ruff Lint Status](https://github.com/oliverm91/processes/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/oliverm91/processes/actions/workflows/lint.yml)
17
+ [![mypy-check](https://github.com/oliverm91/processes/actions/workflows/mypy.yml/badge.svg)](https://github.com/oliverm91/processes/actions/workflows/mypy.yml)
18
+
19
+ [![PyPI version](https://img.shields.io/pypi/v/processes.svg)](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.8">
33
- <!-- Top branch flowing down -->
34
- <line x1="80" y1="120" x2="140" y2="120" stroke="#4ecca3" stroke-width="3" stroke-linecap="round"/>
35
- <line x1="140" y1="120" x2="200" y2="180" stroke="#4ecca3" stroke-width="3" stroke-linecap="round"/>
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
- <!-- Middle branch straight -->
38
- <line x1="80" y1="200" x2="200" y2="200" stroke="#4ecca3" stroke-width="3" stroke-linecap="round"/>
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
- <!-- Bottom branch flowing up -->
41
- <line x1="80" y1="280" x2="140" y2="280" stroke="#4ecca3" stroke-width="3" stroke-linecap="round"/>
42
- <line x1="140" y1="280" x2="200" y2="220" stroke="#4ecca3" stroke-width="3" stroke-linecap="round"/>
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
- Smart Task Orchestration
95
+ Robust Routines Management
65
96
  </text>
66
97
 
67
98
  <!-- Feature badges -->
Binary file