Skip to content

Commit 45fdf59

Browse files
authored
Do not allow builtin functions as tasks. (#519)
1 parent 62077ed commit 45fdf59

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

docs/source/changes.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
99

1010
- {pull}`515` enables tests with graphviz in CI. Thanks to {user}`NickCrews`.
1111
- {pull}`517` raises an error when the configuration file contains a non-existing path
12-
(fixes #514). Also adds a warning if the path is configured as a string and not a list
13-
of strings.
12+
(fixes #514). It also warns if the path is configured as a string and not a list of
13+
strings.
14+
- {pull}`519` raises an error when builtin functions are wrapped with
15+
{func}`~pytask.task`. Closes {issue}`512`.
1416

1517
## 0.4.4 - 2023-12-04
1618

src/_pytask/task_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import inspect
55
from collections import defaultdict
66
from pathlib import Path
7+
from types import BuiltinFunctionType
78
from typing import Any
89
from typing import Callable
910

@@ -84,6 +85,9 @@ def task(
8485
"""
8586

8687
def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
88+
# Omits frame when a builtin function is wrapped.
89+
_rich_traceback_omit = True
90+
8791
for arg, arg_name in ((name, "name"), (id, "id")):
8892
if not (isinstance(arg, str) or arg is None):
8993
msg = (
@@ -94,6 +98,16 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
9498

9599
unwrapped = inspect.unwrap(func)
96100

101+
# We do not allow builtins as functions because we would need to use
102+
# ``inspect.stack`` to infer their caller location and they are unable to carry
103+
# the pytask metadata.
104+
if isinstance(unwrapped, BuiltinFunctionType):
105+
msg = (
106+
"Builtin functions cannot be wrapped with '@task'. If necessary, wrap "
107+
"the builtin function in a function or lambda expression."
108+
)
109+
raise NotImplementedError(msg)
110+
97111
raw_path = inspect.getfile(unwrapped)
98112
if "<string>" in raw_path:
99113
path = Path(unwrapped.__globals__["__file__"]).absolute().resolve()

tests/test_task.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,3 +662,20 @@ def task_second():
662662

663663
session = build(paths=tmp_path)
664664
assert session.exit_code == ExitCode.OK
665+
666+
667+
def test_raise_error_with_builtin_function_as_task(runner, tmp_path):
668+
source = """
669+
from pytask import task
670+
from pathlib import Path
671+
from datetime import datetime
672+
673+
task(
674+
kwargs={"format": "%y/%m/%d"}, produces=Path("time.txt")
675+
)(datetime.utcnow().strftime)
676+
"""
677+
tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(source))
678+
679+
result = runner.invoke(cli, [tmp_path.as_posix()])
680+
assert result.exit_code == ExitCode.COLLECTION_FAILED
681+
assert "Builtin functions cannot be wrapped" in result.output

0 commit comments

Comments
 (0)