Skip to content

Commit 58d8969

Browse files
authored
Capture warnings. (#263)
1 parent 9d23167 commit 58d8969

File tree

13 files changed

+729
-13
lines changed

13 files changed

+729
-13
lines changed

.pre-commit-config.yaml

-4
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ repos:
117117
- id: check-manifest
118118
args: [--no-build-isolation]
119119
additional_dependencies: [setuptools-scm, toml]
120-
- repo: https://github.com/guilatrova/tryceratops
121-
rev: v1.0.1
122-
hooks:
123-
- id: tryceratops
124120
- repo: https://github.com/pre-commit/mirrors-mypy
125121
rev: 'v0.942'
126122
hooks:
+146
Loading

docs/source/_static/images/write-a-task.svg

+1-1
Loading

docs/source/changes.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
77

88
## 0.2.1 - 2022-xx-xx
99

10-
- {pull}`261` adds a config file option to sort entries in live table
1110
- {pull}`259` adds an `.svg` for profiling tasks.
11+
- {pull}`261` adds a config file option to sort entries in live table
12+
- {pull}`262` allows pytask to capture warnings. Here is the
13+
[guide](https://pytask-dev.readthedocs.io/en/stable/how_to_guides/capture_warnings.html).
1214

1315
## 0.2.0 - 2022-04-14
1416

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Capture warnings
2+
3+
pytask captures warnings during the execution.
4+
5+
Here is an example with the most infamous warning in the world of scientific Python.
6+
7+
```python
8+
import pandas as pd
9+
import pytask
10+
11+
12+
def _create_df():
13+
df = pd.DataFrame({"a": range(10), "b": range(10, 20)})
14+
df[df["a"] < 5]["b"] = 1
15+
return df
16+
17+
18+
@pytask.mark.products("df.pkl")
19+
def task_warning(produces):
20+
df = _create_df()
21+
df.to_pickle(produces)
22+
```
23+
24+
Running pytask produces
25+
26+
```{image} /_static/images/warning.svg
27+
```
28+
29+
## Controlling warnings
30+
31+
You can use the `filterwarnings` option in `pyproject.toml` to configure pytasks
32+
behavior to warnings. For example, the configuration below will ignore all user warnings
33+
and specific deprecation warnings matching a regex, but will transform all other
34+
warnings into errors.
35+
36+
```toml
37+
[tool.pytask.ini_options]
38+
filterwarnings = [
39+
"error",
40+
"ignore::UserWarning",
41+
# note the use of single quote below to denote "raw" strings in TOML
42+
'ignore:function ham\(\) is deprecated:DeprecationWarning',
43+
]
44+
```
45+
46+
When a warning matches more than one option in the list, the action for the last
47+
matching option is performed.
48+
49+
## `@pytask.mark.filterwarnings`
50+
51+
You can use the `@pytask.mark.filterwarnings` to add warning filters to specific test
52+
items, allowing you to have finer control of which warnings should be captured at test,
53+
class or even module level:
54+
55+
```python
56+
import pandas as pd
57+
import pytask
58+
59+
60+
def _create_df():
61+
df = pd.DataFrame({"a": range(10), "b": range(10, 20)})
62+
df[df["a"] < 5]["b"] = 1
63+
return df
64+
65+
66+
@pytask.mark.filterwarnings("ignore:.*:SettingWithCopyWarning")
67+
@pytask.mark.products("df.pkl")
68+
def task_warning(produces):
69+
df = _create_df()
70+
df.to_pickle(produces)
71+
```
72+
73+
Filters applied using a mark take precedence over filters passed on the command line or
74+
configured by the `filterwarnings` configuration option.
75+
76+
## Disabling warnings summary
77+
78+
Although not recommended, you can use the `--disable-warnings` command-line option to
79+
suppress the warning summary entirely from the test run output.
80+
81+
## `DeprecationWarning` and `PendingDeprecationWarning`
82+
83+
By default pytask will display `DeprecationWarning` and `PendingDeprecationWarning`
84+
warnings from user code and third-party libraries. This helps users keep their code
85+
modern and avoid breakages when deprecated warnings are effectively removed.
86+
87+
Sometimes it is useful to hide some specific deprecation warnings that happen in code
88+
that you have no control over (such as third-party libraries), in which case you might
89+
use the warning filters options (ini or marks) to ignore those warnings.
90+
91+
For example:
92+
93+
```toml
94+
[tool.pytask.ini_options]
95+
filterwarnings = [
96+
"ignore:.*U.*mode is deprecated:DeprecationWarning"
97+
]
98+
```
99+
100+
This will ignore all warnings of type `DeprecationWarning` where the start of the
101+
message matches the regular expression `".*U.*mode is deprecated"`.
102+
103+
## Debugging warnings
104+
105+
Sometimes it is not clear which line of code triggered a warning. To find the location,
106+
you can turn warnings into exceptions and then use the {option}`pytask build --pdb` flag
107+
to enter the debugger.
108+
109+
You can use the configuration to convert warnings to errors by setting
110+
111+
```toml
112+
[tool.pytask.ini_options]
113+
filterwarnings = ["error:.*"]
114+
```
115+
116+
and then run `pytask`.
117+
118+
Or, you use a temporary environment variable. Here is an example for bash
119+
120+
```console
121+
$ PYTHONWARNINGS=error pytask --pdb
122+
```
123+
124+
and here for Powershell
125+
126+
```console
127+
$ $env:PYTHONWARNINGS = 'error'
128+
$ pytask
129+
$ Remove-Item env:\PYTHONWARNINGS
130+
```

docs/source/how_to_guides/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ specific tasks with pytask.
1212
maxdepth: 1
1313
---
1414
invoking_pytask_extended
15+
capture_warnings
1516
repeating_tasks_with_different_inputs_the_pytest_way
1617
how_to_influence_build_order
1718
how_to_write_a_plugin

scripts/svgs/task_warning.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
import pandas as pd
4+
import pytask
5+
from click.testing import CliRunner
6+
7+
8+
def _create_df():
9+
df = pd.DataFrame({"a": range(10), "b": range(10, 20)})
10+
df[df["a"] < 5]["b"] = 1
11+
return df
12+
13+
14+
@pytask.mark.produces("df.pkl")
15+
def task_warning(produces):
16+
df = _create_df()
17+
df.to_pickle(produces)
18+
19+
20+
if __name__ == "__main__":
21+
runner = CliRunner()
22+
23+
pytask.console.record = True
24+
runner.invoke(pytask.cli, [__file__])
25+
pytask.console.save_svg("warning.svg", title="pytask")

src/_pytask/cli.py

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def pytask_add_hooks(pm: pluggy.PluginManager) -> None:
6868
from _pytask import resolve_dependencies
6969
from _pytask import skipping
7070
from _pytask import task
71+
from _pytask import warnings
7172

7273
pm.register(build)
7374
pm.register(capture)
@@ -89,6 +90,7 @@ def pytask_add_hooks(pm: pluggy.PluginManager) -> None:
8990
pm.register(resolve_dependencies)
9091
pm.register(skipping)
9192
pm.register(task)
93+
pm.register(warnings)
9294

9395

9496
@click.group(

src/_pytask/session.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import attr
1111
import networkx as nx
1212
from _pytask.outcomes import ExitCode
13-
13+
from _pytask.warnings_utils import WarningReport
1414

1515
# Location was moved from pluggy v0.13.1 to v1.0.0.
1616
try:
@@ -62,6 +62,7 @@ class Session:
6262
scheduler = attr.ib(default=None, type=Any)
6363
should_stop = attr.ib(default=False, type=Optional[bool])
6464
"""Optional[bool]: Indicates whether the session should be stopped."""
65+
warnings = attr.ib(factory=list, type=List[WarningReport])
6566

6667
@classmethod
6768
def from_config(cls, config: dict[str, Any]) -> Session:

0 commit comments

Comments
 (0)