Skip to content

Commit c7413a2

Browse files
authored
Merge 96d7d98 into c561b7b
2 parents c561b7b + 96d7d98 commit c7413a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+876
-327
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ build
2020
dist
2121
src/_pytask/_version.py
2222
*.pkl
23+
24+
tests/test_jupyter/*.txt

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ repos:
6262
additional_dependencies: [
6363
attrs>=21.3.0,
6464
click,
65+
pluggy,
6566
types-setuptools
6667
]
6768
pass_filenames: false
@@ -100,6 +101,10 @@ repos:
100101
docs/source/tutorials/selecting_tasks.md|
101102
docs/source/tutorials/set_up_a_project.md
102103
)$
104+
- repo: https://github.com/kynan/nbstripout
105+
rev: 0.6.1
106+
hooks:
107+
- id: nbstripout
103108
- repo: https://github.com/codespell-project/codespell
104109
rev: v2.2.5
105110
hooks:

docs/source/changes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
2828
- {pull}`408` removes `.value` from `Node` protocol.
2929
- {pull}`409` make `.from_annot` an optional feature of nodes.
3030
- {pull}`410` allows to pass functions to `PythonNode(hash=...)`.
31+
- {pull}`411` implements a new functional interface and adds experimental support for
32+
defining and running tasks in REPLs or Jupyter notebooks.
3133
- {pull}`412` adds protocols for tasks.
3234
- {pull}`413` removes scripts to generate `.svg`s.
3335
- {pull}`414` allow more ruff rules.
36+
- {pull}`416` removes `.from_annot` again.
3437

3538
## 0.3.2 - 2023-06-07
3639

docs/source/how_to_guides/invoking_pytask_extended.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Invoke pytask programmatically with
1010
import pytask
1111

1212

13-
session = pytask.main({"paths": ...})
13+
session = pytask.build(paths=...)
1414
```
1515

1616
Pass command line arguments with their long name and hyphens replaced by underscores as

docs/source/reference_guides/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ outcome.
325325

326326
```{eval-rst}
327327
.. autofunction:: pytask.build_dag
328-
.. autofunction:: pytask.main
328+
.. autofunction:: pytask.build
329329
```
330330

331331
## Reports

docs/source/tutorials/visualizing_the_dag.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ layouts, which are listed [here](https://graphviz.org/docs/layouts/).
3737

3838
The programmatic and interactive interface allows customizing the figure.
3939

40-
Similar to {func}`pytask.main`, there exists {func}`pytask.build_dag` which returns the
40+
Similar to {func}`pytask.build`, there exists {func}`pytask.build_dag` which returns the
4141
DAG as a {class}`networkx.DiGraph`.
4242

4343
```python

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ dependencies:
2626
- black
2727
- jupyterlab
2828
- matplotlib
29+
- nbval
2930
- pre-commit
3031
- pygraphviz
3132
- pytest

src/_pytask/build.py

Lines changed: 146 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import sys
55
from pathlib import Path
66
from typing import Any
7+
from typing import Callable
8+
from typing import Iterable
9+
from typing import Literal
710
from typing import TYPE_CHECKING
811

912
import click
13+
from _pytask.capture import CaptureMethod
1014
from _pytask.click import ColoredCommand
1115
from _pytask.config import hookimpl
1216
from _pytask.config_utils import _find_project_root_and_config
@@ -26,16 +30,50 @@
2630

2731

2832
if TYPE_CHECKING:
33+
from _pytask.node_protocols import PTask
2934
from typing import NoReturn
3035

3136

3237
@hookimpl(tryfirst=True)
3338
def pytask_extend_command_line_interface(cli: click.Group) -> None:
3439
"""Extend the command line interface."""
35-
cli.add_command(build)
36-
37-
38-
def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
40+
cli.add_command(build_command)
41+
42+
43+
def build( # noqa: C901, PLR0912, PLR0913, PLR0915
44+
*,
45+
capture: Literal["fd", "no", "sys", "tee-sys"] | CaptureMethod = CaptureMethod.NO,
46+
check_casing_of_paths: bool = True,
47+
config: Path | None = None,
48+
database_url: str = "",
49+
debug_pytask: bool = False,
50+
disable_warnings: bool = False,
51+
dry_run: bool = False,
52+
editor_url_scheme: Literal["no_link", "file", "vscode", "pycharm"] # noqa: PYI051
53+
| str = "file",
54+
expression: str = "",
55+
force: bool = False,
56+
ignore: Iterable[str] = (),
57+
marker_expression: str = "",
58+
max_failures: float = float("inf"),
59+
n_entries_in_table: int = 15,
60+
paths: str | Path | Iterable[str | Path] = (),
61+
pdb: bool = False,
62+
pdb_cls: str = "",
63+
s: bool = False,
64+
show_capture: bool = True,
65+
show_errors_immediately: bool = False,
66+
show_locals: bool = False,
67+
show_traceback: bool = True,
68+
sort_table: bool = True,
69+
stop_after_first_failure: bool = False,
70+
strict_markers: bool = False,
71+
tasks: Callable[..., Any] | PTask | Iterable[Callable[..., Any] | PTask] = (),
72+
task_files: str | Iterable[str] = "task_*.py",
73+
trace: bool = False,
74+
verbose: int = 1,
75+
**kwargs: Any,
76+
) -> Session:
3977
"""Run pytask.
4078
4179
This is the main command to run pytask which usually receives kwargs from the
@@ -44,13 +82,73 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
4482
4583
Parameters
4684
----------
47-
raw_config : dict[str, Any]
48-
A dictionary with options passed to pytask. In general, this dictionary holds
49-
the information passed via the command line interface.
85+
capture
86+
The capture method for stdout and stderr.
87+
check_casing_of_paths
88+
Whether errors should be raised when file names have different casings.
89+
config
90+
A path to the configuration file.
91+
database_url
92+
An URL to the database that tracks the status of tasks.
93+
debug_pytask
94+
Whether debug information should be shown.
95+
disable_warnings
96+
Whether warnings should be disabled and not displayed.
97+
dry_run
98+
Whether a dry-run should be performed that shows which tasks need to be rerun.
99+
editor_url_scheme
100+
An url scheme that allows to click on task names, node names and filenames and
101+
jump right into you preferred edior to the right line.
102+
expression
103+
Same as ``-k`` on the command line. Select tasks via expressions on task ids.
104+
force
105+
Run tasks even though they would be skipped since nothing has changed.
106+
ignore
107+
A pattern to ignore files or directories. Refer to ``pathlib.Path.match``
108+
for more info.
109+
marker_expression
110+
Same as ``-m`` on the command line. Select tasks via marker expressions.
111+
max_failures
112+
Stop after some failures.
113+
n_entries_in_table
114+
How many entries to display in the table during the execution. Tasks which are
115+
running are always displayed.
116+
paths
117+
A path or collection of paths where pytask looks for the configuration and
118+
tasks.
119+
pdb
120+
Start the interactive debugger on errors.
121+
pdb_cls
122+
Start a custom debugger on errors. For example:
123+
``--pdbcls=IPython.terminal.debugger:TerminalPdb``
124+
s
125+
Shortcut for ``pytask.build(capture"no")``.
126+
show_capture
127+
Choose which captured output should be shown for failed tasks.
128+
show_errors_immediately
129+
Show errors with tracebacks as soon as the task fails.
130+
show_locals
131+
Show local variables in tracebacks.
132+
show_traceback
133+
Choose whether tracebacks should be displayed or not.
134+
sort_table
135+
Sort the table of tasks at the end of the execution.
136+
stop_after_first_failure
137+
Stop after the first failure.
138+
strict_markers
139+
Raise errors for unknown markers.
140+
tasks
141+
A task or a collection of tasks that is passed to ``pytask.build(tasks=...)``.
142+
task_files
143+
A pattern to describe modules that contain tasks.
144+
trace
145+
Enter debugger in the beginning of each task.
146+
verbose
147+
Make pytask verbose (>= 0) or quiet (= 0).
50148
51149
Returns
52150
-------
53-
session : _pytask.session.Session
151+
session : pytask.Session
54152
The session captures all the information of the current run.
55153
56154
"""
@@ -61,6 +159,39 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
61159
pm.register(cli)
62160
pm.hook.pytask_add_hooks(pm=pm)
63161

162+
raw_config = {
163+
"capture": capture,
164+
"check_casing_of_paths": check_casing_of_paths,
165+
"config": config,
166+
"database_url": database_url,
167+
"debug_pytask": debug_pytask,
168+
"disable_warnings": disable_warnings,
169+
"dry_run": dry_run,
170+
"editor_url_scheme": editor_url_scheme,
171+
"expression": expression,
172+
"force": force,
173+
"ignore": ignore,
174+
"marker_expression": marker_expression,
175+
"max_failures": max_failures,
176+
"n_entries_in_table": n_entries_in_table,
177+
"paths": paths,
178+
"pdb": pdb,
179+
"pdb_cls": pdb_cls,
180+
"s": s,
181+
"show_capture": show_capture,
182+
"show_errors_immediately": show_errors_immediately,
183+
"show_locals": show_locals,
184+
"show_traceback": show_traceback,
185+
"sort_table": sort_table,
186+
"stop_after_first_failure": stop_after_first_failure,
187+
"strict_markers": strict_markers,
188+
"tasks": tasks,
189+
"task_files": task_files,
190+
"trace": trace,
191+
"verbose": verbose,
192+
**kwargs,
193+
}
194+
64195
# If someone called the programmatic interface, we need to do some parsing.
65196
if "command" not in raw_config:
66197
raw_config["command"] = "build"
@@ -97,9 +228,9 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
97228

98229
raw_config = {**raw_config, **config_from_file}
99230

100-
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
231+
config_ = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
101232

102-
session = Session.from_config(config)
233+
session = Session.from_config(config_)
103234

104235
except (ConfigurationError, Exception):
105236
exc_info = sys.exc_info()
@@ -137,7 +268,7 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
137268
return session
138269

139270

140-
@click.command(cls=ColoredCommand)
271+
@click.command(cls=ColoredCommand, name="build")
141272
@click.option(
142273
"--debug-pytask",
143274
is_flag=True,
@@ -161,13 +292,13 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
161292
"--show-errors-immediately",
162293
is_flag=True,
163294
default=False,
164-
help="Print errors with tracebacks as soon as the task fails.",
295+
help="Show errors with tracebacks as soon as the task fails.",
165296
)
166297
@click.option(
167298
"--show-traceback/--show-no-traceback",
168299
type=bool,
169300
default=True,
170-
help=("Choose whether tracebacks should be displayed or not."),
301+
help="Choose whether tracebacks should be displayed or not.",
171302
)
172303
@click.option(
173304
"--dry-run", type=bool, is_flag=True, default=False, help="Perform a dry-run."
@@ -179,13 +310,13 @@ def main(raw_config: dict[str, Any]) -> Session: # noqa: C901, PLR0912, PLR0915
179310
default=False,
180311
help="Execute a task even if it succeeded successfully before.",
181312
)
182-
def build(**raw_config: Any) -> NoReturn:
313+
def build_command(**raw_config: Any) -> NoReturn:
183314
"""Collect tasks, execute them and report the results.
184315
185316
The default command. pytask collects tasks from the given paths or the
186317
current working directory, executes them and reports the results.
187318
188319
"""
189320
raw_config["command"] = "build"
190-
session = main(raw_config)
321+
session = build(**raw_config)
191322
sys.exit(session.exit_code)

src/_pytask/capture.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from _pytask.node_protocols import PTask
5151

5252

53-
class _CaptureMethod(enum.Enum):
53+
class CaptureMethod(enum.Enum):
5454
FD = "fd"
5555
NO = "no"
5656
SYS = "sys"
@@ -63,8 +63,8 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None:
6363
additional_parameters = [
6464
click.Option(
6565
["--capture"],
66-
type=EnumChoice(_CaptureMethod),
67-
default=_CaptureMethod.FD,
66+
type=EnumChoice(CaptureMethod),
67+
default=CaptureMethod.FD,
6868
help="Per task capturing method.",
6969
),
7070
click.Option(
@@ -77,7 +77,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None:
7777
["--show-capture"],
7878
type=EnumChoice(ShowCapture),
7979
default=ShowCapture.ALL,
80-
help=("Choose which captured output should be shown for failed tasks."),
80+
help="Choose which captured output should be shown for failed tasks.",
8181
),
8282
]
8383
cli.commands["build"].params.extend(additional_parameters)
@@ -90,8 +90,11 @@ def pytask_parse_config(config: dict[str, Any]) -> None:
9090
Note that, ``-s`` is a shortcut for ``--capture=no``.
9191
9292
"""
93+
if isinstance(config["capture"], str):
94+
config["capture"] = CaptureMethod(config["capture"])
95+
9396
if config["s"]:
94-
config["capture"] = _CaptureMethod.NO
97+
config["capture"] = CaptureMethod.NO
9598

9699

97100
@hookimpl
@@ -642,20 +645,20 @@ def readouterr(self) -> CaptureResult[AnyStr]:
642645
return CaptureResult(out, err) # type: ignore
643646

644647

645-
def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]:
648+
def _get_multicapture(method: CaptureMethod) -> MultiCapture[str]:
646649
"""Set up the MultiCapture class with the passed method.
647650
648651
For each valid method, the function instantiates the :class:`MultiCapture` class
649652
with the specified buffers for ``stdin``, ``stdout``, and ``stderr``.
650653
651654
"""
652-
if method == _CaptureMethod.FD:
655+
if method == CaptureMethod.FD:
653656
return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
654-
if method == _CaptureMethod.SYS:
657+
if method == CaptureMethod.SYS:
655658
return MultiCapture(in_=SysCapture(0), out=SysCapture(1), err=SysCapture(2))
656-
if method == _CaptureMethod.NO:
659+
if method == CaptureMethod.NO:
657660
return MultiCapture(in_=None, out=None, err=None)
658-
if method == _CaptureMethod.TEE_SYS:
661+
if method == CaptureMethod.TEE_SYS:
659662
return MultiCapture(
660663
in_=None, out=SysCapture(1, tee=True), err=SysCapture(2, tee=True)
661664
)
@@ -679,7 +682,7 @@ class CaptureManager:
679682
680683
"""
681684

682-
def __init__(self, method: _CaptureMethod) -> None:
685+
def __init__(self, method: CaptureMethod) -> None:
683686
self._method = method
684687
self._capturing: MultiCapture[str] | None = None
685688

src/_pytask/clean.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def clean(**raw_config: Any) -> NoReturn: # noqa: C901, PLR0912, PLR0915
101101
raw_config["command"] = "clean"
102102

103103
try:
104-
# Duplication of the same mechanism in :func:`pytask.main.main`.
104+
# Duplication of the same mechanism in :func:`pytask.build`.
105105
pm = get_plugin_manager()
106106
from _pytask import cli
107107

0 commit comments

Comments
 (0)