Skip to content

Commit 290f455

Browse files
authored
Add tests for Jupyter and fix parallelization with PythonNodes. (#79)
1 parent e9bd415 commit 290f455

10 files changed

+214
-33
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242

4343
- name: Run unit tests and doctests.
4444
shell: bash -l {0}
45-
run: tox -e pytest -- tests -m "unit or (not integration and not end_to_end)" --cov=./ --cov-report=xml -n auto
45+
run: tox -e test -- tests -m "unit or (not integration and not end_to_end)" --cov=./ --cov-report=xml
4646

4747
- name: Upload coverage report for unit tests and doctests.
4848
if: runner.os == 'Linux' && matrix.python-version == '3.10'
@@ -51,7 +51,7 @@ jobs:
5151

5252
- name: Run end-to-end tests.
5353
shell: bash -l {0}
54-
run: tox -e pytest -- tests -m end_to_end --cov=./ --cov-report=xml -n auto
54+
run: tox -e test -- tests -m end_to_end --cov=./ --cov-report=xml
5555

5656
- name: Upload coverage reports of end-to-end tests.
5757
if: runner.os == 'Linux' && matrix.python-version == '3.10'

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ __pycache__
1212

1313
*.egg-info
1414

15-
.pytask.sqlite3
16-
15+
.pytask
1716
build
1817
dist
1918
src/pytask_parallel/_version.py

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ repos:
3636
rev: v1.25.0
3737
hooks:
3838
- id: refurb
39+
- repo: https://github.com/kynan/nbstripout
40+
rev: 0.6.1
41+
hooks:
42+
- id: nbstripout
3943
- repo: https://github.com/executablebooks/mdformat
4044
rev: 0.7.17
4145
hooks:

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask-parallel) and
99

1010
- {pull}`72` moves the project to `pyproject.toml`.
1111
- {pull}`75` updates the release strategy.
12+
- {pull}`79` add tests for Jupyter and fix parallelization with `PythonNode`s.
1213

1314
## 0.4.0 - 2023-10-07
1415

environment.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ dependencies:
1818
- optree
1919

2020
# Misc
21-
- black
21+
- tox
22+
- ipywidgets
23+
- nbmake
2224
- pre-commit
2325
- pytest-cov
2426

pyproject.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[build-system]
2-
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.0"]
32
build-backend = "setuptools.build_meta"
3+
requires = ["setuptools>=64", "setuptools_scm[toml]>=8"]
44

55
[project]
66
name = "pytask_parallel"
@@ -28,6 +28,13 @@ dynamic = ["version"]
2828
name = "Tobias Raabe"
2929
3030

31+
[project.optional-dependencies]
32+
test = [
33+
"nbmake",
34+
"pytest",
35+
"pytest-cov",
36+
]
37+
3138
[project.readme]
3239
file = "README.md"
3340
content-type = "text/markdown"
@@ -62,7 +69,7 @@ where = ["src"]
6269
namespaces = false
6370

6471
[tool.setuptools_scm]
65-
write_to = "src/pytask_parallel/_version.py"
72+
version_file = "src/pytask_parallel/_version.py"
6673

6774
[tool.mypy]
6875
files = ["src", "tests"]

src/pytask_parallel/execute.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
from pytask import hookimpl
2222
from pytask import Mark
2323
from pytask import parse_warning_filter
24+
from pytask import PNode
2425
from pytask import PTask
26+
from pytask import PythonNode
2527
from pytask import remove_internal_traceback_frames_from_exc_info
2628
from pytask import Session
2729
from pytask import Task
@@ -114,7 +116,11 @@ def pytask_execute_build(session: Session) -> bool | None: # noqa: C901, PLR091
114116
warning_reports = []
115117
# A task raised an exception.
116118
else:
117-
warning_reports, task_exception = future.result()
119+
(
120+
python_nodes,
121+
warning_reports,
122+
task_exception,
123+
) = future.result()
118124
session.warnings.extend(warning_reports)
119125
exc_info = (
120126
_parse_future_exception(future.exception())
@@ -132,6 +138,19 @@ def pytask_execute_build(session: Session) -> bool | None: # noqa: C901, PLR091
132138
session.scheduler.done(task_name)
133139
else:
134140
task = session.dag.nodes[task_name]["task"]
141+
142+
# Update PythonNodes with the values from the future if
143+
# not threads.
144+
if (
145+
session.config["parallel_backend"]
146+
!= ParallelBackend.THREADS
147+
):
148+
task.produces = tree_map(
149+
_update_python_node,
150+
task.produces,
151+
python_nodes,
152+
)
153+
135154
try:
136155
session.hook.pytask_execute_task_teardown(
137156
session=session, task=task
@@ -169,6 +188,12 @@ def pytask_execute_build(session: Session) -> bool | None: # noqa: C901, PLR091
169188
return None
170189

171190

191+
def _update_python_node(x: PNode, y: PythonNode | None) -> PNode:
192+
if y:
193+
x.save(y.load())
194+
return x
195+
196+
172197
def _parse_future_exception(
173198
exc: BaseException | None,
174199
) -> tuple[type[BaseException], BaseException, TracebackType] | None:
@@ -240,7 +265,11 @@ def _execute_task( # noqa: PLR0913
240265
console_options: ConsoleOptions,
241266
session_filterwarnings: tuple[str, ...],
242267
task_filterwarnings: tuple[Mark, ...],
243-
) -> tuple[list[WarningReport], tuple[type[BaseException], BaseException, str] | None]:
268+
) -> tuple[
269+
PyTree[PythonNode | None],
270+
list[WarningReport],
271+
tuple[type[BaseException], BaseException, str] | None,
272+
]:
244273
"""Unserialize and execute task.
245274
246275
This function receives bytes and unpickles them to a task which is them execute in a
@@ -251,9 +280,6 @@ def _execute_task( # noqa: PLR0913
251280
_patch_set_trace_and_breakpoint()
252281

253282
with warnings.catch_warnings(record=True) as log:
254-
# mypy can't infer that record=True means log is not None; help it.
255-
assert log is not None # noqa: S101
256-
257283
for arg in session_filterwarnings:
258284
warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
259285

@@ -301,7 +327,11 @@ def _execute_task( # noqa: PLR0913
301327
)
302328
)
303329

304-
return warning_reports, processed_exc_info
330+
python_nodes = tree_map(
331+
lambda x: x if isinstance(x, PythonNode) else None, task.produces
332+
)
333+
334+
return python_nodes, warning_reports, processed_exc_info
305335

306336

307337
def _process_exception(
@@ -339,7 +369,9 @@ def pytask_execute_task(session: Session, task: Task) -> Future[Any] | None:
339369

340370
def _mock_processes_for_threads(
341371
func: Callable[..., Any], **kwargs: Any
342-
) -> tuple[list[Any], tuple[type[BaseException], BaseException, TracebackType] | None]:
372+
) -> tuple[
373+
None, list[Any], tuple[type[BaseException], BaseException, TracebackType] | None
374+
]:
343375
"""Mock execution function such that it returns the same as for processes.
344376
345377
The function for processes returns ``warning_reports`` and an ``exception``. With
@@ -354,7 +386,7 @@ def _mock_processes_for_threads(
354386
exc_info = sys.exc_info()
355387
else:
356388
exc_info = None
357-
return [], exc_info
389+
return None, [], exc_info
358390

359391

360392
def _create_kwargs_for_task(task: PTask) -> dict[str, PyTree[Any]]:
@@ -395,7 +427,7 @@ def sleep(self) -> None:
395427
time.sleep(self.timings[self.timing_idx])
396428

397429

398-
def _get_module(func: Callable[..., Any], path: Path) -> ModuleType:
430+
def _get_module(func: Callable[..., Any], path: Path | None) -> ModuleType:
399431
"""Get the module of a python function.
400432
401433
For Python <3.10, functools.partial does not set a `__module__` attribute which is
@@ -410,4 +442,6 @@ def _get_module(func: Callable[..., Any], path: Path) -> ModuleType:
410442
does not really support ``functools.partial``. Instead, use ``@task(kwargs=...)``.
411443
412444
"""
413-
return inspect.getmodule(func, path.as_posix())
445+
if path:
446+
return inspect.getmodule(func, path.as_posix())
447+
return inspect.getmodule(func)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "12bc75b1",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from pathlib import Path\n",
11+
"\n",
12+
"from typing_extensions import Annotated\n",
13+
"\n",
14+
"import pytask\n",
15+
"from pytask import ExitCode, PathNode, PythonNode"
16+
]
17+
},
18+
{
19+
"cell_type": "code",
20+
"execution_count": null,
21+
"id": "29ac7311",
22+
"metadata": {},
23+
"outputs": [],
24+
"source": [
25+
"node_text = PythonNode(name=\"text\", hash=True)\n",
26+
"\n",
27+
"\n",
28+
"def create_text() -> Annotated[int, node_text]:\n",
29+
" return \"This is the text.\"\n",
30+
"\n",
31+
"\n",
32+
"node_file = PathNode.from_path(Path(\"file.txt\").resolve())\n",
33+
"\n",
34+
"\n",
35+
"def create_file(text: Annotated[int, node_text]) -> Annotated[str, node_file]:\n",
36+
" return text"
37+
]
38+
},
39+
{
40+
"cell_type": "code",
41+
"execution_count": null,
42+
"id": "738c9418",
43+
"metadata": {},
44+
"outputs": [],
45+
"source": [
46+
"session = pytask.build(tasks=[create_file, create_text], n_workers=2)\n",
47+
"assert session.exit_code == ExitCode.OK"
48+
]
49+
}
50+
],
51+
"metadata": {
52+
"kernelspec": {
53+
"display_name": "Python 3 (ipykernel)",
54+
"language": "python",
55+
"name": "python3"
56+
},
57+
"language_info": {
58+
"codemirror_mode": {
59+
"name": "ipython",
60+
"version": 3
61+
},
62+
"file_extension": ".py",
63+
"mimetype": "text/x-python",
64+
"name": "python",
65+
"nbconvert_exporter": "python",
66+
"pygments_lexer": "ipython3",
67+
"version": "3.11.6"
68+
}
69+
},
70+
"nbformat": 4,
71+
"nbformat_minor": 5
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "12bc75b1",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from pathlib import Path\n",
11+
"\n",
12+
"from typing_extensions import Annotated\n",
13+
"\n",
14+
"import pytask\n",
15+
"from pytask import ExitCode, PathNode, PythonNode"
16+
]
17+
},
18+
{
19+
"cell_type": "code",
20+
"execution_count": null,
21+
"id": "29ac7311",
22+
"metadata": {},
23+
"outputs": [],
24+
"source": [
25+
"node_text = PythonNode(name=\"text\", hash=True)\n",
26+
"\n",
27+
"\n",
28+
"def create_text() -> Annotated[int, node_text]:\n",
29+
" return \"This is the text.\"\n",
30+
"\n",
31+
"\n",
32+
"node_file = PathNode(name=\"product\", path=Path(\"file.txt\"))\n",
33+
"\n",
34+
"\n",
35+
"def create_file(text: Annotated[int, node_text]) -> Annotated[str, node_file]:\n",
36+
" return text"
37+
]
38+
},
39+
{
40+
"cell_type": "code",
41+
"execution_count": null,
42+
"id": "738c9418",
43+
"metadata": {},
44+
"outputs": [],
45+
"source": [
46+
"session = pytask.build(tasks=[create_file, create_text], n_workers=2)\n",
47+
"assert session.exit_code == ExitCode.OK"
48+
]
49+
}
50+
],
51+
"metadata": {
52+
"kernelspec": {
53+
"display_name": "Python 3 (ipykernel)",
54+
"language": "python",
55+
"name": "python3"
56+
},
57+
"language_info": {
58+
"codemirror_mode": {
59+
"name": "ipython",
60+
"version": 3
61+
},
62+
"file_extension": ".py",
63+
"mimetype": "text/x-python",
64+
"name": "python",
65+
"nbconvert_exporter": "python",
66+
"pygments_lexer": "ipython3",
67+
"version": "3.11.6"
68+
}
69+
},
70+
"nbformat": 4,
71+
"nbformat_minor": 5
72+
}

tox.ini

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
[tox]
2-
envlist = pytest
2+
requires = tox>=4
3+
envlist = test
34

45
[testenv]
5-
usedevelop = true
6+
package = wheel
67

7-
[testenv:pytest]
8-
deps =
9-
# pytest
10-
pytest
11-
pytest-cov
12-
pytest-xdist
13-
setuptools_scm
14-
toml
15-
16-
# Package
17-
pytask >=0.4.0
18-
cloudpickle
19-
loky
8+
[testenv:test]
9+
extras = test
2010
commands =
21-
pytest {posargs}
11+
pytest --nbmake {posargs}

0 commit comments

Comments
 (0)