/
hookspecs.py
401 lines (249 loc) · 10.3 KB
/
hookspecs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
"""Implement hook specifications or entry-points for pytask.
At each of the entry-points, a plugin can register a hook implementation which receives
the message send by the host and may send a response.
"""
from __future__ import annotations
from typing import Any
from typing import TYPE_CHECKING
import pluggy
if TYPE_CHECKING:
from _pytask.models import NodeInfo
from _pytask.node_protocols import PNode
import click
from _pytask.node_protocols import PTask
import networkx as nx
from pathlib import Path
from _pytask.session import Session
from _pytask.outcomes import CollectionOutcome
from _pytask.outcomes import TaskOutcome
from _pytask.reports import CollectionReport
from _pytask.reports import ExecutionReport
from _pytask.reports import DagReport
from pluggy import PluginManager
hookspec = pluggy.HookspecMarker("pytask")
@hookspec(historic=True)
def pytask_add_hooks(pm: PluginManager) -> None:
"""Add hook specifications and implementations to the plugin manager.
This hook is the first to be called to let plugins register their hook
specifications and implementations.
If you want to register plugins dynamically depending on the configuration, use
:func:`pytask_post_parse` instead. See :mod:`_pytask.debugging` for an example.
"""
# Hooks for the command-line interface.
@hookspec(historic=True)
def pytask_extend_command_line_interface(cli: click.Group) -> None:
"""Extend the command line interface.
The hook can be used to extend the command line interface either by providing new
commands or adding options and arguments to existing commands.
- Add commands via ``cli.add_command``.
- Add options and arguments by extending ``cli.params`` or
``cli.commands["<name>"].params`` if the parameters only apply to a certain
command.
"""
# Hooks for the configuration.
@hookspec(firstresult=True)
def pytask_configure(pm: PluginManager, raw_config: dict[str, Any]) -> dict[str, Any]:
"""Configure pytask.
The main hook implementation which controls the configuration and calls subordinated
hooks.
"""
@hookspec
def pytask_parse_config(config: dict[str, Any]) -> None:
"""Parse configuration that is from CLI or file."""
@hookspec
def pytask_post_parse(config: dict[str, Any]) -> None:
"""Post parsing.
This hook allows to consolidate the configuration in case some plugins might be
mutually exclusive. For example, the parallel execution provided by pytask-parallel
does not work with any form of debugging. If debugging is turned on, parallelization
can be turned of in this step.
"""
@hookspec
def pytask_unconfigure(session: Session) -> None:
"""Unconfigure a pytask session before the process is exited.
The hook allows to return resources previously borrowed like :func:`pdb.set_trace`
by :class:`_pytask.debugging.PytaskPDB` and do other stuff at the end of a session.
"""
# Hooks for the collection.
@hookspec(firstresult=True)
def pytask_collect(session: Session) -> Any:
"""Collect tasks from paths.
The main hook implementation which controls the collection and calls subordinated
hooks.
"""
@hookspec(firstresult=True)
def pytask_ignore_collect(path: Path, config: dict[str, Any]) -> bool:
"""Ignore collected path.
This hook is indicates for each directory and file whether it should be ignored.
This speeds up the collection.
"""
@hookspec
def pytask_collect_modify_tasks(session: Session, tasks: list[PTask]) -> None:
"""Modify tasks after they have been collected.
This hook can be used to deselect tasks when they match a certain keyword or mark.
"""
@hookspec(firstresult=True)
def pytask_collect_file_protocol(
session: Session, path: Path, reports: list[CollectionReport]
) -> list[CollectionReport]:
"""Start protocol to collect files.
The protocol calls the subordinate hook :func:`pytask_collect_file` which might
error if the file has a :class:`SyntaxError`.
"""
@hookspec
def pytask_collect_file(
session: Session, path: Path, reports: list[CollectionReport]
) -> list[CollectionReport] | None:
"""Collect tasks from a file.
If you want to collect tasks from other files, modify this hook.
"""
@hookspec
def pytask_collect_file_log(session: Session, reports: list[CollectionReport]) -> None:
"""Perform logging at the end of collecting a file."""
@hookspec(firstresult=True)
def pytask_collect_task_protocol(
session: Session, path: Path | None, name: str, obj: Any
) -> CollectionReport | None:
"""Start protocol to collect tasks."""
@hookspec
def pytask_collect_task_setup(
session: Session, path: Path | None, name: str, obj: Any
) -> None:
"""Set up collecting a task."""
@hookspec(firstresult=True)
def pytask_collect_task(
session: Session, path: Path | None, name: str, obj: Any
) -> PTask:
"""Collect a single task."""
@hookspec
def pytask_collect_task_teardown(session: Session, task: PTask) -> None:
"""Perform tear-down operations when a task was collected.
Use this hook specification to, for example, perform checks on the collected task.
"""
@hookspec(firstresult=True)
def pytask_collect_node(
session: Session, path: Path, node_info: NodeInfo
) -> PNode | None:
"""Collect a node which is a dependency or a product of a task."""
@hookspec(firstresult=True)
def pytask_collect_log(
session: Session, reports: list[CollectionReport], tasks: list[PTask]
) -> None:
"""Log errors occurring during the collection.
This hook reports errors during the collection.
"""
# Hooks for resolving dependencies.
@hookspec(firstresult=True)
def pytask_dag(session: Session) -> None:
"""Create a DAG.
The main hook implementation which controls the resolution of dependencies and calls
subordinated hooks.
"""
@hookspec(firstresult=True)
def pytask_dag_create_dag(session: Session, tasks: list[PTask]) -> nx.DiGraph:
"""Create the DAG.
This hook creates the DAG from tasks, dependencies and products. The DAG can be used
by a scheduler to find an execution order.
"""
@hookspec
def pytask_dag_modify_dag(session: Session, dag: nx.DiGraph) -> None:
"""Modify the DAG.
This hook allows to make some changes to the DAG before it is validated and tasks
are selected.
"""
@hookspec
def pytask_dag_log(session: Session, report: DagReport) -> None:
"""Log errors during resolving dependencies."""
# Hooks for running tasks.
@hookspec(firstresult=True)
def pytask_execute(session: Session) -> Any | None:
"""Loop over all tasks for the execution.
The main hook implementation which controls the execution and calls subordinated
hooks.
"""
@hookspec
def pytask_execute_log_start(session: Session) -> None:
"""Start logging of execution.
This hook allows to provide a header with information before the execution starts.
"""
@hookspec(firstresult=True)
def pytask_execute_create_scheduler(session: Session) -> Any:
"""Create a scheduler for the execution.
The scheduler provides information on which tasks are able to be executed. Its
foundation is likely a topological ordering of the tasks based on the DAG.
"""
@hookspec(firstresult=True)
def pytask_execute_build(session: Session) -> Any:
"""Execute the build.
This hook implements the main loop to execute tasks.
"""
@hookspec(firstresult=True)
def pytask_execute_task_protocol(session: Session, task: PTask) -> ExecutionReport:
"""Run the protocol for executing a test.
This hook runs all stages of the execution process, setup, execution, and teardown
and catches any exception.
Then, the exception or success is stored in a report and logged.
"""
@hookspec(firstresult=True)
def pytask_execute_task_log_start(session: Session, task: PTask) -> None:
"""Start logging of task execution.
This hook can be used to provide more verbose output during the execution.
"""
@hookspec
def pytask_execute_task_setup(session: Session, task: PTask) -> None:
"""Set up the task execution.
This hook is called before the task is executed and can provide an entry-point to
fast-fail a task. For example, raise and exception if a dependency is missing
instead of letting the error occur in the execution.
"""
@hookspec(firstresult=True)
def pytask_execute_task(session: Session, task: PTask) -> Any:
"""Execute a task."""
@hookspec
def pytask_execute_task_teardown(session: Session, task: PTask) -> None:
"""Tear down task execution.
This hook is executed after the task has been executed. It allows to perform clean-
up operations or checks for missing products.
"""
@hookspec(firstresult=True)
def pytask_execute_task_process_report(
session: Session, report: ExecutionReport
) -> Any | None:
"""Process the report of a task.
This hook allows to process each report generated by a task which is either based on
an exception or a success. Set the color and the symbol for logging.
Some exceptions are intentionally raised like skips, but they should not be reported
as failures.
"""
@hookspec(firstresult=True)
def pytask_execute_task_log_end(session: Session, report: ExecutionReport) -> None:
"""Log the end of a task execution."""
@hookspec
def pytask_execute_log_end(session: Session, reports: list[ExecutionReport]) -> None:
"""Log the footer of the execution report."""
# Hooks for general logging.
@hookspec
def pytask_log_session_header(session: Session) -> None:
"""Log session information at the begin of a run."""
@hookspec
def pytask_log_session_footer(
session: Session,
duration: float,
outcome: CollectionOutcome | TaskOutcome,
) -> None:
"""Log session information at the end of a run."""
# Hooks for profile.
@hookspec
def pytask_profile_add_info_on_task(
session: Session, tasks: list[PTask], profile: dict[str, dict[Any, Any]]
) -> None:
"""Add information on task for profile.
Hook implementations can add information to the ``profile`` dictionary. The
dictionary's keys are the task names. The value for each task is a dictionary itself
where keys correspond to columns of the profile table.
"""
@hookspec
def pytask_profile_export_profile(
session: Session, profile: dict[str, dict[Any, Any]]
) -> None:
"""Export the profile."""