processes 3.1.0__tar.gz → 3.1.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-3.1.0 → processes-3.1.1}/CHANGELOG.md +6 -0
- {processes-3.1.0 → processes-3.1.1}/PKG-INFO +15 -1
- {processes-3.1.0 → processes-3.1.1}/README.md +14 -0
- {processes-3.1.0 → processes-3.1.1}/docs/index.md +7 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/_email_internals.py +55 -6
- {processes-3.1.0 → processes-3.1.1}/src/processes/_error_data.py +37 -1
- processes-3.1.0/src/processes/_log_formatting.py → processes-3.1.1/src/processes/_logfile_formatting.py +14 -1
- {processes-3.1.0 → processes-3.1.1}/src/processes/_tb_utils.py +82 -4
- {processes-3.1.0 → processes-3.1.1}/src/processes/email_config.py +13 -3
- {processes-3.1.0 → processes-3.1.1}/src/processes/process.py +10 -1
- {processes-3.1.0 → processes-3.1.1}/src/processes/task.py +9 -2
- processes-3.1.0/RELEASE_NOTES.md +0 -36
- {processes-3.1.0 → processes-3.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/ISSUE_TEMPLATE/custom.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/docs.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/lint-pr.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/lint.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/mypy.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/publish.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/tags.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.github/workflows/tests.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/.gitignore +0 -0
- {processes-3.1.0 → processes-3.1.1}/CONTRIBUTING.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/LICENSE +0 -0
- {processes-3.1.0 → processes-3.1.1}/assets/banner.svg +0 -0
- {processes-3.1.0 → processes-3.1.1}/assets/logo.png +0 -0
- {processes-3.1.0 → processes-3.1.1}/assets/logo.svg +0 -0
- {processes-3.1.0 → processes-3.1.1}/assets/social_banner.png +0 -0
- {processes-3.1.0 → processes-3.1.1}/assets/social_banner.svg +0 -0
- {processes-3.1.0 → processes-3.1.1}/docs/assets/favicon.svg +0 -0
- {processes-3.1.0 → processes-3.1.1}/docs/examples/advanced.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/docs/examples/basic.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/docs/examples/intro.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/docs/reference.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/examples/01_basic_tasks_and_dependencies/README.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/examples/01_basic_tasks_and_dependencies/example1.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/examples/02_task_dependencies_result_passing/README.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/examples/02_task_dependencies_result_passing/example2.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/examples/README.md +0 -0
- {processes-3.1.0 → processes-3.1.1}/mkdocs.yml +0 -0
- {processes-3.1.0 → processes-3.1.1}/pyproject.toml +0 -0
- {processes-3.1.0 → processes-3.1.1}/pytest.ini +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/__init__.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/exceptions.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/py.typed +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/de.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/en.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/es.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/fr.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/it.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/languages/pt.json +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/palettes/catppuccin.css +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/palettes/neobones.css +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/palettes/neutral.css +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/palettes/slate.css +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/styles/classic.html +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/styles/compact.html +0 -0
- {processes-3.1.0 → processes-3.1.1}/src/processes/themes/styles/modern.html +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/__init__.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/base_test.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/manual_tests/__init__.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/manual_tests/manual_pipeline_inspect.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/manual_tests/manual_themed_tracebacks.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_args_kwargs.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_complex_dag_failures.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_dependencies.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_email_themes.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_logfiles.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_normal_run_no_errors.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_parallel_race_conditions.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_timeout_retry.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/tests/test_unique_name.py +0 -0
- {processes-3.1.0 → processes-3.1.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: processes
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: Orchestrate graphs of callables in Python with automatic dependency resolution, parallel execution, retries, timeouts, and HTML email alerts on failure — zero dependencies
|
|
5
5
|
Project-URL: Homepage, https://github.com/oliverm91/processes
|
|
6
6
|
Project-URL: Documentation, https://oliverm91.github.io/processes/
|
|
@@ -336,6 +336,20 @@ HTMLEmailStyle(
|
|
|
336
336
|
|
|
337
337
|
All fields are optional — omit `HTMLEmailStyle` entirely to use the defaults.
|
|
338
338
|
|
|
339
|
+
#### Traced Variables
|
|
340
|
+
|
|
341
|
+
On failure, the email body includes the local variables of the **outermost
|
|
342
|
+
user frame in the traceback** — i.e. the last frame that is not inside
|
|
343
|
+
`site-packages` or your virtualenv. A `file:line` reference next to the
|
|
344
|
+
section shows exactly where those values were captured.
|
|
345
|
+
|
|
346
|
+
`traced_vars_frame_filter` lets you point this at a different frame: set it
|
|
347
|
+
to a path substring (e.g. one of your own package or module names) to
|
|
348
|
+
capture locals from the outermost frame whose filename contains that
|
|
349
|
+
substring instead. This is useful for deep-debugging code that runs through
|
|
350
|
+
several layers of internal libraries or wrappers, where the default
|
|
351
|
+
outermost-user-frame would land too high up the call stack.
|
|
352
|
+
|
|
339
353
|
</details>
|
|
340
354
|
|
|
341
355
|
<details>
|
|
@@ -311,6 +311,20 @@ HTMLEmailStyle(
|
|
|
311
311
|
|
|
312
312
|
All fields are optional — omit `HTMLEmailStyle` entirely to use the defaults.
|
|
313
313
|
|
|
314
|
+
#### Traced Variables
|
|
315
|
+
|
|
316
|
+
On failure, the email body includes the local variables of the **outermost
|
|
317
|
+
user frame in the traceback** — i.e. the last frame that is not inside
|
|
318
|
+
`site-packages` or your virtualenv. A `file:line` reference next to the
|
|
319
|
+
section shows exactly where those values were captured.
|
|
320
|
+
|
|
321
|
+
`traced_vars_frame_filter` lets you point this at a different frame: set it
|
|
322
|
+
to a path substring (e.g. one of your own package or module names) to
|
|
323
|
+
capture locals from the outermost frame whose filename contains that
|
|
324
|
+
substring instead. This is useful for deep-debugging code that runs through
|
|
325
|
+
several layers of internal libraries or wrappers, where the default
|
|
326
|
+
outermost-user-frame would land too high up the call stack.
|
|
327
|
+
|
|
314
328
|
</details>
|
|
315
329
|
|
|
316
330
|
<details>
|
|
@@ -184,6 +184,13 @@ in the source the listed values were captured, which is usually the
|
|
|
184
184
|
fastest way to figure out *why* a complex task broke deep inside a
|
|
185
185
|
wrapper.
|
|
186
186
|
|
|
187
|
+
This default can be overridden with `HTMLEmailStyle.traced_vars_frame_filter`.
|
|
188
|
+
Set it to a path substring (e.g. the name of one of your own packages or
|
|
189
|
+
modules) to capture locals from the outermost frame whose filename contains
|
|
190
|
+
that substring instead — useful for deep-debugging code that runs through
|
|
191
|
+
several layers of internal libraries or wrappers, where the default
|
|
192
|
+
outermost-user-frame would land too high up the call stack.
|
|
193
|
+
|
|
187
194
|
---
|
|
188
195
|
|
|
189
196
|
## 🛡️ Fault Tolerance & Logs
|
|
@@ -22,7 +22,18 @@ _PALETTE_MARKER = "{{__palette_css__}}"
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def _load_language_strings(language: str) -> dict[str, str]:
|
|
25
|
-
"""Load translatable strings for the given ISO 639-1 language code.
|
|
25
|
+
"""Load translatable strings for the given ISO 639-1 language code.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
language : str
|
|
30
|
+
ISO 639-1 language code, e.g. ``"en"``.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
dict[str, str]
|
|
35
|
+
Mapping of translation keys to localized strings.
|
|
36
|
+
"""
|
|
26
37
|
path = os.path.join(_LANGUAGES_DIR, f"{language}.json")
|
|
27
38
|
with open(path, encoding="utf-8") as fh:
|
|
28
39
|
return cast(dict[str, str], json.load(fh))
|
|
@@ -57,9 +68,20 @@ class _HTMLEmailFormatter(_ErrorContextFormatter):
|
|
|
57
68
|
def _split_traceback_at_target(self, tb_str: str, location: str) -> tuple[str, str, str]:
|
|
58
69
|
"""Split *tb_str* around the frame line matching *location*.
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
tb_str : str
|
|
74
|
+
Full formatted traceback to split.
|
|
75
|
+
location : str
|
|
76
|
+
``"filename:lineno"`` of the frame to highlight, as produced by
|
|
77
|
+
``_build_traced_vars_location``.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
tuple[str, str, str]
|
|
82
|
+
``(before, highlight, after)`` where ``highlight`` is the
|
|
83
|
+
``File "<filename>", line <lineno>, in <func>`` line for that
|
|
84
|
+
frame. Returns ``("", "", tb_str)`` if no match is found.
|
|
63
85
|
"""
|
|
64
86
|
if not tb_str or not location:
|
|
65
87
|
return ("", "", tb_str)
|
|
@@ -83,7 +105,18 @@ class _HTMLEmailFormatter(_ErrorContextFormatter):
|
|
|
83
105
|
return rendered
|
|
84
106
|
|
|
85
107
|
def format(self, record: logging.LogRecord) -> str:
|
|
86
|
-
"""Render a log record as a complete HTML email body.
|
|
108
|
+
"""Render a log record as a complete HTML email body.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
record : logging.LogRecord
|
|
113
|
+
The record being formatted.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
str
|
|
118
|
+
The fully rendered HTML email body.
|
|
119
|
+
"""
|
|
87
120
|
error = self._error_data(record)
|
|
88
121
|
|
|
89
122
|
tb_before, tb_highlight, tb_after = self._split_traceback_at_target(
|
|
@@ -159,7 +192,23 @@ def _build_task_email_handler(
|
|
|
159
192
|
style: HTMLEmailStyle,
|
|
160
193
|
task_name: str,
|
|
161
194
|
) -> _HTMLEmailHandler:
|
|
162
|
-
"""Create a fully configured email handler bound to one task.
|
|
195
|
+
"""Create a fully configured email handler bound to one task.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
smtp_config : SMTPConfig
|
|
200
|
+
SMTP transport configuration for the handler.
|
|
201
|
+
style : HTMLEmailStyle
|
|
202
|
+
HTML presentation settings used by the handler's formatter.
|
|
203
|
+
task_name : str
|
|
204
|
+
Name of the task the handler is bound to, used in the email subject.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
_HTMLEmailHandler
|
|
209
|
+
A handler at ``logging.ERROR`` level, with its formatter and
|
|
210
|
+
localized subject configured.
|
|
211
|
+
"""
|
|
163
212
|
handler = _HTMLEmailHandler(smtp_config)
|
|
164
213
|
handler.setFormatter(_HTMLEmailFormatter(style))
|
|
165
214
|
handler.setLevel(logging.ERROR)
|
|
@@ -7,7 +7,29 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
@dataclass(frozen=True)
|
|
9
9
|
class _ErrorData:
|
|
10
|
-
"""Typed view of a task failure, extracted from ``record.task_context``.
|
|
10
|
+
"""Typed view of a task failure, extracted from ``record.task_context``.
|
|
11
|
+
|
|
12
|
+
Attributes
|
|
13
|
+
----------
|
|
14
|
+
task_name : str
|
|
15
|
+
Name of the task that failed. Defaults to ``"?"``.
|
|
16
|
+
function : str
|
|
17
|
+
Name of the function that was executing. Defaults to ``"?"``.
|
|
18
|
+
args : tuple[Any, ...]
|
|
19
|
+
Positional arguments the function was called with. Defaults to ``()``.
|
|
20
|
+
kwargs : dict[str, Any]
|
|
21
|
+
Keyword arguments the function was called with. Defaults to ``{}``.
|
|
22
|
+
downstream_impact : list[str]
|
|
23
|
+
Names of tasks skipped as a result of this failure. Defaults to ``[]``.
|
|
24
|
+
exception : str
|
|
25
|
+
String representation of the raised exception. Defaults to ``""``.
|
|
26
|
+
traceback_str : str
|
|
27
|
+
Full formatted traceback. Defaults to ``""``.
|
|
28
|
+
traced_vars : str
|
|
29
|
+
Rendered local variables of the traced frame. Defaults to ``""``.
|
|
30
|
+
traced_vars_location : str
|
|
31
|
+
``"filename:lineno"`` of the traced frame. Defaults to ``""``.
|
|
32
|
+
"""
|
|
11
33
|
|
|
12
34
|
task_name: str = "?"
|
|
13
35
|
function: str = "?"
|
|
@@ -24,6 +46,20 @@ class _ErrorContextFormatter(logging.Formatter):
|
|
|
24
46
|
"""Base formatter providing typed access to a record's failure context."""
|
|
25
47
|
|
|
26
48
|
def _error_data(self, record: logging.LogRecord) -> _ErrorData:
|
|
49
|
+
"""Extract the failure context from a log record.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
record : logging.LogRecord
|
|
54
|
+
The record being formatted. May or may not carry a
|
|
55
|
+
``task_context`` attribute.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
_ErrorData
|
|
60
|
+
Typed view of ``record.task_context``, with defaults filled in
|
|
61
|
+
for any missing fields.
|
|
62
|
+
"""
|
|
27
63
|
ctx = getattr(record, "task_context", None) or {}
|
|
28
64
|
return _ErrorData(
|
|
29
65
|
task_name=str(ctx.get("task_name", "?")),
|
|
@@ -7,7 +7,7 @@ from ._error_data import _ErrorContextFormatter
|
|
|
7
7
|
_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class _TaskLogfileFormatter(_ErrorContextFormatter):
|
|
11
11
|
"""Plain-text formatter for task logfiles.
|
|
12
12
|
|
|
13
13
|
On failure, appends the same failure context shown in the HTML email
|
|
@@ -19,6 +19,19 @@ class _TaskLogFormatter(_ErrorContextFormatter):
|
|
|
19
19
|
super().__init__(_LOG_FORMAT)
|
|
20
20
|
|
|
21
21
|
def format(self, record: logging.LogRecord) -> str:
|
|
22
|
+
"""Render a log record as plain text, appending failure context if present.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
record : logging.LogRecord
|
|
27
|
+
The record being formatted.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
str
|
|
32
|
+
The formatted log line, with the failure context appended if
|
|
33
|
+
``record.task_context`` is set.
|
|
34
|
+
"""
|
|
22
35
|
if not getattr(record, "task_context", None):
|
|
23
36
|
return super().format(record)
|
|
24
37
|
|
|
@@ -8,7 +8,19 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def _is_library_path(filename: str) -> bool:
|
|
11
|
-
"""
|
|
11
|
+
"""Check whether a source file belongs to a library or the stdlib.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
filename : str
|
|
16
|
+
Path of a traceback frame's source file.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
bool
|
|
21
|
+
True if ``filename`` lives inside ``site-packages``, a ``.venv``,
|
|
22
|
+
or the Python install prefix, False otherwise.
|
|
23
|
+
"""
|
|
12
24
|
if "site-packages" in filename:
|
|
13
25
|
return True
|
|
14
26
|
if ".venv" in filename:
|
|
@@ -26,6 +38,18 @@ def _is_library_path(filename: str) -> bool:
|
|
|
26
38
|
|
|
27
39
|
|
|
28
40
|
def _iter_tb(exc_tb: Any) -> Any:
|
|
41
|
+
"""Iterate over a traceback's frames from outermost to innermost.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
exc_tb : types.TracebackType | None
|
|
46
|
+
The traceback to walk, typically ``exc.__traceback__``.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
Iterator[types.TracebackType]
|
|
51
|
+
Each traceback node from ``exc_tb`` to the innermost frame.
|
|
52
|
+
"""
|
|
29
53
|
tb = exc_tb
|
|
30
54
|
while tb is not None:
|
|
31
55
|
yield tb
|
|
@@ -38,6 +62,19 @@ def _resolve_target_tb(exc_tb: Any, frame_filter: str | None) -> Any:
|
|
|
38
62
|
With ``frame_filter=None``, picks the last non-library frame.
|
|
39
63
|
With a filter string, picks the last frame whose filename contains it.
|
|
40
64
|
Falls back to the innermost frame when nothing matches.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
exc_tb : types.TracebackType | None
|
|
69
|
+
The traceback to search, typically ``exc.__traceback__``.
|
|
70
|
+
frame_filter : str | None
|
|
71
|
+
Substring used to select a frame by filename, or ``None`` to pick
|
|
72
|
+
the outermost non-library frame.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
types.TracebackType | None
|
|
77
|
+
The selected traceback frame, or ``None`` if ``exc_tb`` is ``None``.
|
|
41
78
|
"""
|
|
42
79
|
frames = list(_iter_tb(exc_tb))
|
|
43
80
|
if not frames:
|
|
@@ -50,7 +87,22 @@ def _resolve_target_tb(exc_tb: Any, frame_filter: str | None) -> Any:
|
|
|
50
87
|
|
|
51
88
|
|
|
52
89
|
def _build_traced_vars_html(exc_tb: Any, frame_filter: str | None) -> str:
|
|
53
|
-
"""Return HTML-escaped ``name = repr(value)`` lines for the target frame's locals.
|
|
90
|
+
"""Return HTML-escaped ``name = repr(value)`` lines for the target frame's locals.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
exc_tb : types.TracebackType | None
|
|
95
|
+
The traceback to search, typically ``exc.__traceback__``.
|
|
96
|
+
frame_filter : str | None
|
|
97
|
+
Substring used to select a frame by filename, or ``None`` to pick
|
|
98
|
+
the outermost non-library frame.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
str
|
|
103
|
+
Newline-separated, HTML-escaped ``name = repr(value)`` lines for the
|
|
104
|
+
target frame's locals, or ``""`` if no frame is found.
|
|
105
|
+
"""
|
|
54
106
|
target = _resolve_target_tb(exc_tb, frame_filter)
|
|
55
107
|
if target is None:
|
|
56
108
|
return ""
|
|
@@ -66,7 +118,21 @@ def _build_traced_vars_html(exc_tb: Any, frame_filter: str | None) -> str:
|
|
|
66
118
|
|
|
67
119
|
|
|
68
120
|
def _build_traced_vars_location(exc_tb: Any, frame_filter: str | None) -> str:
|
|
69
|
-
"""Return ``"filename:lineno"`` for the target frame, or empty string.
|
|
121
|
+
"""Return ``"filename:lineno"`` for the target frame, or empty string.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
exc_tb : types.TracebackType | None
|
|
126
|
+
The traceback to search, typically ``exc.__traceback__``.
|
|
127
|
+
frame_filter : str | None
|
|
128
|
+
Substring used to select a frame by filename, or ``None`` to pick
|
|
129
|
+
the outermost non-library frame.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
str
|
|
134
|
+
``"filename:lineno"`` of the target frame, or ``""`` if no frame is found.
|
|
135
|
+
"""
|
|
70
136
|
target = _resolve_target_tb(exc_tb, frame_filter)
|
|
71
137
|
if target is None:
|
|
72
138
|
return ""
|
|
@@ -75,5 +141,17 @@ def _build_traced_vars_location(exc_tb: Any, frame_filter: str | None) -> str:
|
|
|
75
141
|
|
|
76
142
|
|
|
77
143
|
def _format_traceback(exc: BaseException) -> str:
|
|
78
|
-
"""Return the full traceback of *exc* as a single string.
|
|
144
|
+
"""Return the full traceback of *exc* as a single string.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
exc : BaseException
|
|
149
|
+
The exception whose traceback should be formatted.
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
str
|
|
154
|
+
The full traceback, as produced by ``traceback.format_exception``,
|
|
155
|
+
joined into a single string.
|
|
156
|
+
"""
|
|
79
157
|
return "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
|
@@ -26,9 +26,11 @@ class SMTPConfig:
|
|
|
26
26
|
Recipient email addresses.
|
|
27
27
|
credentials : tuple[str, str] | None
|
|
28
28
|
``(username, password)`` for SMTP authentication. Defaults to ``None``.
|
|
29
|
-
secure : tuple | None
|
|
30
|
-
Security configuration. Use ``()`` for STARTTLS, ``(
|
|
31
|
-
|
|
29
|
+
secure : tuple[()] | tuple[str] | tuple[str, str] | tuple[str, str, ssl.SSLContext] | None
|
|
30
|
+
Security configuration. Use ``()`` for STARTTLS, ``(keyfile,)`` or
|
|
31
|
+
``(keyfile, certfile)`` for explicit TLS context creation, or
|
|
32
|
+
``(keyfile, certfile, ssl_context)`` to supply a pre-built
|
|
33
|
+
``ssl.SSLContext``. ``None`` means no encryption. Defaults to ``None``.
|
|
32
34
|
timeout : int
|
|
33
35
|
Connection timeout in seconds. Defaults to ``5``.
|
|
34
36
|
"""
|
|
@@ -64,6 +66,14 @@ class HTMLEmailStyle:
|
|
|
64
66
|
appear in the *Traced Variables* section. When ``None`` (default),
|
|
65
67
|
the outermost user frame is used; when set, the outermost frame whose
|
|
66
68
|
filename contains this substring is used instead.
|
|
69
|
+
|
|
70
|
+
Raises
|
|
71
|
+
------
|
|
72
|
+
ValueError
|
|
73
|
+
If ``style``, ``palette``, or ``language`` is not one of the
|
|
74
|
+
supported values.
|
|
75
|
+
TypeError
|
|
76
|
+
If ``traced_vars_frame_filter`` is neither ``str`` nor ``None``.
|
|
67
77
|
"""
|
|
68
78
|
|
|
69
79
|
style: str = _DEFAULT_STYLE
|
|
@@ -298,6 +298,8 @@ class ProcessRunner:
|
|
|
298
298
|
Results from successfully executed tasks.
|
|
299
299
|
failed_tasks : set[str]
|
|
300
300
|
Names of tasks that failed during execution.
|
|
301
|
+
skipped_tasks : set[str]
|
|
302
|
+
Names of tasks that were never run because an upstream dependency failed.
|
|
301
303
|
submitted_tasks : set[str]
|
|
302
304
|
Names of tasks that have been submitted for execution.
|
|
303
305
|
"""
|
|
@@ -310,7 +312,14 @@ class ProcessRunner:
|
|
|
310
312
|
self.submitted_tasks: set[str] = set()
|
|
311
313
|
|
|
312
314
|
def _is_done(self) -> bool:
|
|
313
|
-
"""
|
|
315
|
+
"""Check whether every task has either passed or failed.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
bool
|
|
320
|
+
True if every task has a recorded result (passed or failed),
|
|
321
|
+
False otherwise.
|
|
322
|
+
"""
|
|
314
323
|
return len(self.passed_results) + len(self.failed_tasks) >= len(self.process.tasks)
|
|
315
324
|
|
|
316
325
|
def run(self, parallel: bool, max_workers: int) -> ProcessResult:
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
import logging
|
|
11
11
|
|
|
12
12
|
from ._email_internals import _build_task_email_handler
|
|
13
|
-
from .
|
|
13
|
+
from ._logfile_formatting import _TaskLogfileFormatter
|
|
14
14
|
from ._tb_utils import _build_traced_vars_html, _build_traced_vars_location, _format_traceback
|
|
15
15
|
from .email_config import HTMLEmailStyle, SMTPConfig
|
|
16
16
|
from .exceptions import CircularDependencyError
|
|
@@ -251,7 +251,7 @@ class Task:
|
|
|
251
251
|
|
|
252
252
|
file_handler = logging.FileHandler(self.log_path)
|
|
253
253
|
file_handler.setLevel(logging.INFO)
|
|
254
|
-
file_handler.setFormatter(
|
|
254
|
+
file_handler.setFormatter(_TaskLogfileFormatter())
|
|
255
255
|
logger.addHandler(file_handler)
|
|
256
256
|
|
|
257
257
|
if smtp_config is not None:
|
|
@@ -271,6 +271,13 @@ class Task:
|
|
|
271
271
|
"""
|
|
272
272
|
Validates all input parameter types.
|
|
273
273
|
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
smtp_config : SMTPConfig | None
|
|
277
|
+
SMTP transport configuration passed to the constructor, if any.
|
|
278
|
+
email_style : HTMLEmailStyle | None
|
|
279
|
+
HTML presentation settings passed to the constructor, if any.
|
|
280
|
+
|
|
274
281
|
Raises
|
|
275
282
|
------
|
|
276
283
|
TypeError
|
processes-3.1.0/RELEASE_NOTES.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# v3.0.1 — Email API Overhaul, Task Retries & Timeouts
|
|
2
|
-
|
|
3
|
-
## ⚠️ Breaking Changes
|
|
4
|
-
|
|
5
|
-
- **Removed `HTMLSMTPHandler` and `html_mail_handler`**. Email alerting is now configured with two plain dataclasses passed to `Task`:
|
|
6
|
-
- `SMTPConfig` — SMTP transport settings (host, credentials, sender/recipients, TLS).
|
|
7
|
-
- `HTMLEmailStyle` — presentation settings (style, color palette, language, traced-vars frame filter).
|
|
8
|
-
|
|
9
|
-
```python
|
|
10
|
-
Task(..., smtp_config=SMTPConfig(...), email_style=HTMLEmailStyle(style="modern", palette="catppuccin"))
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## ✨ New Features
|
|
14
|
-
|
|
15
|
-
- **Per-task timeouts**: `Task(..., timeout=30)` raises `TimeoutError` if an attempt exceeds the limit.
|
|
16
|
-
- **Automatic retries**: `Task(..., retries=3, retry_on=(ConnectionError, TimeoutError))` retries failed attempts on the specified exception types (defaults to `ConnectionError`/`TimeoutError`).
|
|
17
|
-
- **Structured exceptions**: `DependencyNotFoundError`, `TaskNotFoundError`, and `CircularDependencyError` now carry structured attributes (`task_name`, `missing_dep`) instead of plain messages.
|
|
18
|
-
- **Traced variables in error emails**: failure emails include a "Traced Variables" section showing local variables at the relevant stack frame, with a configurable `traced_vars_frame_filter` to target a specific module.
|
|
19
|
-
|
|
20
|
-
## 🔧 Refactoring
|
|
21
|
-
|
|
22
|
-
- Centralized failure-context extraction: `Task._resolve_args` and `Task._build_failure_context` cleanly separate dependency-result injection from failure reporting.
|
|
23
|
-
- Split traceback/frame-walking utilities (`_tb_utils.py`) from email-rendering internals (`_email_internals.py`) — each module now has a single responsibility.
|
|
24
|
-
- Each `Task` now gets a unique per-instance logger, preventing handler leakage between tasks with the same name.
|
|
25
|
-
- Minor cleanups in `Process`: bare `raise` for re-raised exceptions, extracted `_is_done()` helper for the parallel runner loop.
|
|
26
|
-
|
|
27
|
-
## 🐛 Bug Fixes
|
|
28
|
-
|
|
29
|
-
- Fixed a logging bug where `logger.exception(...)` was called outside an `except` block, resulting in empty exception details (`None`) in failure emails. Errors now log with full `exc_info` and a structured `task_context` payload.
|
|
30
|
-
- Fixed `Task.run()` not reporting the actual exception when retries were exhausted.
|
|
31
|
-
|
|
32
|
-
## 🧪 Testing & CI
|
|
33
|
-
|
|
34
|
-
- Migrated the test suite to a shared `BaseTest` base class with consistent per-test log directories and cleanup.
|
|
35
|
-
- Added dedicated tests for timeout and retry behavior.
|
|
36
|
-
- Bumped GitHub Actions (`actions/checkout` → v6, `astral-sh/setup-uv` → v6) to silence deprecation warnings.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processes-3.1.0 → processes-3.1.1}/examples/02_task_dependencies_result_passing/example2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|