Skip to content

Commit d7a03aa

Browse files
authored
Implemented matching by expressions and marker expressions. (#13)
1 parent 3dbcb55 commit d7a03aa

25 files changed

+1048
-208
lines changed

docs/changes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ all releases are available on `Anaconda.org <https://anaconda.org/pytask/pytask>
1212
- :gh:`10` turns parametrization into a plugin.
1313
- :gh:`11` extends the documentation.
1414
- :gh:`12` replaces ``pytest.mark`` with ``pytask.mark``.
15+
- :gh:`13` implements selecting tasks via expressions or marker expressions.
1516

1617

1718
0.0.4 - 2020-07-22
+71-7
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,81 @@
11
How to select tasks
22
===================
33

4-
If you want to run only a subset of tasks, there exists currently one option.
4+
If you want to run only a subset of tasks, there exist multiple options.
55

66

7-
Selecting tasks via paths
8-
-------------------------
7+
Paths
8+
-----
99

10-
You can run all tasks in one file by passing the path to the file to pytask. The same
11-
can be done for multiple paths
10+
You can run all tasks in one file or one directory by passing the corresponding path to
11+
pytask. The same can be done for multiple paths.
1212

1313
.. code-block:: bash
1414
15-
$ pytask path/to/task_1.py
15+
$ pytask src/task_1.py
1616
17-
$ pytask path/to/task_1.py path/to/task_2.py
17+
$ pytask src
18+
19+
$ pytask src/task_1.py src/task_2.py
20+
21+
22+
Markers
23+
-------
24+
25+
If you assign markers to task functions, you can use marker expressions to select tasks.
26+
For example, here is a task with the ``wip`` marker which indicates work-in-progress.
27+
28+
.. code-block:: python
29+
30+
@pytask.mark.wip
31+
def task_1():
32+
pass
33+
34+
To execute only tasks with the ``wip`` marker, use
35+
36+
.. code-block:: bash
37+
38+
$ pytask -m wip
39+
40+
You can pass more complex expressions to ``-m`` by using multiple markers and ``and``,
41+
``or``, ``not``, and brackets (``()``). The following pattern selects all tasks which
42+
belong to the data management, but not the ones which produce plots and plots produced
43+
for the analysis.
44+
45+
.. code-block:: bash
46+
47+
$ pytask -m "(data_management and not plots) or (analysis and plots)"
48+
49+
50+
Expressions
51+
-----------
52+
53+
Expressions are similar to markers and offer the same syntax but target the task ids.
54+
Assume you have the following tasks.
55+
56+
.. code-block:: python
57+
58+
def task_1():
59+
pass
60+
61+
62+
def task_2():
63+
pass
64+
65+
66+
def task_12():
67+
pass
68+
69+
Then,
70+
71+
.. code-block:: bash
72+
73+
$ pytask -k 1
74+
75+
will execute the first and third task and
76+
77+
.. code-block:: bash
78+
79+
$ pytask -k "1 and not 2"
80+
81+
executes only the first task.

src/pytask/__init__.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import pluggy
2-
from pytask.mark.structures import MARK_GEN as mark # noqa: F401, N811
1+
from pytask.config import hookimpl
2+
from pytask.mark_ import MARK_GEN as mark # noqa: N811
33

4-
hookimpl = pluggy.HookimplMarker("pytask")
5-
6-
7-
__all__ = ["hookimpl", "mark"]
4+
__all__ = ["hookimpl", "main", "mark"]
85
__version__ = "0.0.4"

src/pytask/cli.py

+34-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from pathlib import Path
33

44
import click
5-
import pytask.mark.cli
5+
from pytask.config import hookimpl
66
from pytask.pluginmanager import get_plugin_manager
77

88

@@ -11,42 +11,54 @@
1111

1212
def add_parameters(func):
1313
"""Add parameters from plugins to the commandline interface."""
14-
pm = get_plugin_manager()
15-
pm.register(sys.modules[__name__])
16-
pm.hook.pytask_add_hooks(pm=pm)
14+
pm = _prepare_plugin_manager()
1715
pm.hook.pytask_add_parameters_to_cli(command=func)
18-
1916
# Hack to pass the plugin manager via a hidden option to the ``config_from_cli``.
2017
func.params.append(click.Option(["--pm"], default=pm, hidden=True))
2118

2219
return func
2320

2421

25-
@pytask.hookimpl
26-
def pytask_add_hooks(pm):
27-
"""Add some hooks and plugins.
22+
def _prepare_plugin_manager():
23+
pm = get_plugin_manager()
24+
pm.register(sys.modules[__name__])
25+
pm.hook.pytask_add_hooks(pm=pm)
26+
return pm
2827

29-
This hook implementation registers only plugins which extend the command line
30-
interface or patch the main entry-point :func:`pytask.hookspecs.pytask_main`.
3128

32-
"""
29+
@hookimpl
30+
def pytask_add_hooks(pm):
31+
from pytask import collect
32+
from pytask import config
3333
from pytask import database
3434
from pytask import debugging
35+
from pytask import execute
36+
from pytask import logging
3537
from pytask import main
36-
from pytask.mark import cli as mark_cli
38+
from pytask import parametrize
39+
from pytask import resolve_dependencies
40+
from pytask import skipping
41+
from pytask import mark_
3742

43+
pm.register(collect)
44+
pm.register(config)
3845
pm.register(database)
3946
pm.register(debugging)
47+
pm.register(execute)
48+
pm.register(logging)
4049
pm.register(main)
41-
pm.register(mark_cli)
50+
pm.register(parametrize)
51+
pm.register(resolve_dependencies)
52+
pm.register(skipping)
53+
pm.register(mark_)
4254

4355

4456
def _to_path(ctx, param, value): # noqa: U100
4557
"""Callback for :class:`click.Argument` or :class:`click.Option`."""
4658
return [Path(i).resolve() for i in value]
4759

4860

49-
@pytask.hookimpl
61+
@hookimpl
5062
def pytask_add_parameters_to_cli(command):
5163
additional_parameters = [
5264
click.Argument(
@@ -72,6 +84,12 @@ def pytask_add_parameters_to_cli(command):
7284
@click.version_option()
7385
def pytask(**config_from_cli):
7486
"""Command-line interface for pytask."""
75-
pm = config_from_cli["pm"]
76-
session = pm.hook.pytask_main(config_from_cli=config_from_cli)
87+
session = main(config_from_cli)
7788
sys.exit(session.exit_code)
89+
90+
91+
def main(config_from_cli):
92+
pm = config_from_cli.get("pm", _prepare_plugin_manager())
93+
session = pm.hook.pytask_main(config_from_cli=config_from_cli)
94+
95+
return session

src/pytask/collect.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
import pytask
1212
from pytask.exceptions import CollectionError
1313
from pytask.exceptions import TaskDuplicatedError
14-
from pytask.mark.structures import has_marker
14+
from pytask.mark_ import has_marker
1515
from pytask.nodes import FilePathNode
1616
from pytask.nodes import PythonFunctionTask
17+
from pytask.report import CollectionReport
1718
from pytask.report import CollectionReportFile
1819
from pytask.report import CollectionReportTask
1920

@@ -22,8 +23,16 @@
2223
def pytask_collect(session):
2324
reports = _collect_from_paths(session)
2425
tasks = _extract_tasks_from_reports(reports)
25-
session.hook.pytask_collect_modify_tasks(tasks=tasks, config=session.config)
26-
session.hook.pytask_collect_log(reports=reports, tasks=tasks, config=session.config)
26+
27+
try:
28+
session.hook.pytask_collect_modify_tasks(session=session, tasks=tasks)
29+
except Exception:
30+
report = CollectionReport(
31+
" Modification of collected tasks failed ", sys.exc_info()
32+
)
33+
reports.append(report)
34+
35+
session.hook.pytask_collect_log(session=session, reports=reports, tasks=tasks)
2736

2837
session.collection_reports = reports
2938
session.tasks = tasks
@@ -211,10 +220,13 @@ def _extract_tasks_from_reports(reports):
211220

212221

213222
@pytask.hookimpl
214-
def pytask_collect_log(reports, tasks, config):
215-
tm_width = config["terminal_width"]
223+
def pytask_collect_log(session, reports, tasks):
224+
tm_width = session.config["terminal_width"]
216225

217-
click.echo(f"Collected {len(tasks)} task(s).")
226+
message = f"Collected {len(tasks)} task(s)."
227+
if session.deselected:
228+
message += f" Deselected {len(session.deselected)} task(s)."
229+
click.echo(message)
218230

219231
failed_reports = [i for i in reports if not i.successful]
220232
if failed_reports:

src/pytask/config.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
from pathlib import Path
88

99
import click
10-
import pytask
11-
from pytask.mark.structures import MARK_GEN
10+
import pluggy
1211
from pytask.shared import get_first_not_none_value
1312
from pytask.shared import to_list
1413

14+
15+
hookimpl = pluggy.HookimplMarker("pytask")
16+
17+
1518
IGNORED_FILES_AND_FOLDERS = [
1619
"*/.git/*",
1720
"*/__pycache__/*",
@@ -20,7 +23,7 @@
2023
]
2124

2225

23-
@pytask.hookimpl
26+
@hookimpl
2427
def pytask_configure(pm, config_from_cli):
2528
config = {"pm": pm, "terminal_width": _get_terminal_width()}
2629

@@ -38,20 +41,18 @@ def pytask_configure(pm, config_from_cli):
3841
"produces": "Attach a product/products to a task.",
3942
}
4043

41-
config["pm"].hook.pytask_parse_config(
44+
pm.hook.pytask_parse_config(
4245
config=config,
4346
config_from_cli=config_from_cli,
4447
config_from_file=config_from_file,
4548
)
4649

47-
config["pm"].hook.pytask_post_parse(config=config)
48-
49-
MARK_GEN.config = config
50+
pm.hook.pytask_post_parse(config=config)
5051

5152
return config
5253

5354

54-
@pytask.hookimpl
55+
@hookimpl
5556
def pytask_parse_config(config, config_from_cli, config_from_file):
5657
config["ignore"] = (
5758
get_first_not_none_value(

src/pytask/execute.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from pytask.database import create_or_update_state
1313
from pytask.exceptions import ExecutionError
1414
from pytask.exceptions import NodeNotFoundError
15-
from pytask.mark.structures import Mark
15+
from pytask.mark_ import Mark
1616
from pytask.nodes import FilePathNode
1717
from pytask.report import ExecutionReport
1818
from pytask.report import format_execute_footer

src/pytask/hookspecs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def pytask_ignore_collect(path, config):
104104

105105

106106
@hookspec
107-
def pytask_collect_modify_tasks(tasks, config):
107+
def pytask_collect_modify_tasks(session, tasks):
108108
"""Modify tasks after they have been collected.
109109
110110
This hook can be used to deselect tasks when they match a certain keyword or mark.
@@ -161,7 +161,7 @@ def pytask_collect_node(path, node):
161161

162162

163163
@hookspec(firstresult=True)
164-
def pytask_collect_log(reports, tasks, config):
164+
def pytask_collect_log(session, reports, tasks):
165165
"""Log errors occurring during the collection.
166166
167167
This hook reports errors during the collection.

0 commit comments

Comments
 (0)