Skip to content

Commit 760eda6

Browse files
authored
Fix links to files when pdb or trace is activated. (#183)
1 parent c1b7d51 commit 760eda6

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

docs/source/changes.rst

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ all releases are available on `PyPI <https://pypi.org/project/pytask>`_ and
2828
- :gh:`181` adds correct formatting of running tasks.
2929
- :gh:`182` introduces that only the starting year is displayed in the license following
3030
https://hynek.me/til/copyright-years.
31+
- :gh:`183` enables tracing down the source of a function through decorators.
3132

3233

3334
0.1.3 - 2021-11-30

src/_pytask/console.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Callable
1010
from typing import Dict
1111
from typing import Iterable
12+
from typing import List
1213
from typing import Optional
1314
from typing import Type
1415
from typing import TYPE_CHECKING
@@ -50,6 +51,12 @@
5051
_HORIZONTAL_PADDING = (0, 1, 0, 1)
5152

5253

54+
_SKIPPED_PATHS = [Path(__file__).parent.joinpath("debugging.py")]
55+
"""List[Path]: List of paths to skip when tracing down the path to the source of a task
56+
function.
57+
58+
"""
59+
5360
ARROW_DOWN_ICON = "|" if _IS_LEGACY_WINDOWS else "⬇"
5461
FILE_ICON = "" if _IS_LEGACY_WINDOWS else "📄 "
5562
PYTHON_ICON = "" if _IS_LEGACY_WINDOWS else "🐍 "
@@ -165,12 +172,26 @@ def create_url_style_for_path(path: Path, edtior_url_scheme: str) -> Style:
165172
)
166173

167174

168-
def _get_file(function: Callable[..., Any]) -> Path:
169-
"""Get path to module where the function is defined."""
175+
def _get_file(function: Callable[..., Any], skipped_paths: List[Path] = None) -> Path:
176+
"""Get path to module where the function is defined.
177+
178+
When the ``pdb`` or ``trace`` mode is activated, every task function is wrapped with
179+
a decorator which we need to skip to get to the underlying task function. Thus, the
180+
special case.
181+
182+
"""
183+
if skipped_paths is None:
184+
skipped_paths = _SKIPPED_PATHS
185+
170186
if isinstance(function, functools.partial):
171187
return _get_file(function.func)
188+
elif (
189+
hasattr(function, "__wrapped__")
190+
and Path(inspect.getsourcefile(function)) in skipped_paths
191+
):
192+
return _get_file(function.__wrapped__) # type: ignore[attr-defined]
172193
else:
173-
return Path(inspect.getfile(function))
194+
return Path(inspect.getsourcefile(function))
174195

175196

176197
def _get_source_lines(function: Callable[..., Any]) -> int:

tests/_test_console_helpers.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import functools
2+
3+
4+
def empty_decorator(func):
5+
@functools.wraps(func)
6+
def wrapped():
7+
return func()
8+
9+
return wrapped

tests/test_console.py

+37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import attr
55
import pytest
6+
from _pytask.console import _get_file
7+
from _pytask.console import _get_source_lines
68
from _pytask.console import console
79
from _pytask.console import create_summary_panel
810
from _pytask.console import create_url_style_for_path
@@ -19,6 +21,8 @@
1921
from rich.text import Span
2022
from rich.text import Text
2123

24+
from tests._test_console_helpers import empty_decorator
25+
2226

2327
def task_func():
2428
...
@@ -185,3 +189,36 @@ def test_format_task_id(
185189

186190
result = format_task_id(task, editor_url_scheme, use_short_name, relative_to)
187191
assert result == expected
192+
193+
194+
@pytest.mark.parametrize(
195+
"task_func, skipped_paths, expected",
196+
[
197+
(task_func, [], _THIS_FILE),
198+
(
199+
empty_decorator(task_func),
200+
[],
201+
_THIS_FILE.parent.joinpath("_test_console_helpers.py"),
202+
),
203+
(
204+
empty_decorator(task_func),
205+
[_THIS_FILE.parent.joinpath("_test_console_helpers.py")],
206+
_THIS_FILE,
207+
),
208+
],
209+
)
210+
def test_get_file(task_func, skipped_paths, expected):
211+
result = _get_file(task_func, skipped_paths)
212+
assert result == expected
213+
214+
215+
@pytest.mark.parametrize(
216+
"task_func, expected",
217+
[
218+
(task_func, _SOURCE_LINE_TASK_FUNC),
219+
(empty_decorator(task_func), _SOURCE_LINE_TASK_FUNC),
220+
],
221+
)
222+
def test_get_source_lines(task_func, expected):
223+
result = _get_source_lines(task_func)
224+
assert result == expected

0 commit comments

Comments
 (0)