Skip to content

Remove @pytask.mark.task and switch to @task. #552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions docs/source/_static/md/markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ $ pytask markers
│ pytask.mark.skipif │ Skip a task and all its dependent tasks │
│ │ if a condition is met. │
│ │ │
│ pytask.mark.task │ Mark a function as a task regardless of │
│ │ its name. Or mark tasks which are │
│ │ repeated in a loop. See this tutorial │
│ │ for more information: │
│ │ <a href="https://bit.ly/3DWrXS3">https://bit.ly/3DWrXS3</a>. │
│ │ │
│ pytask.mark.try_first │ Try to execute a task a early as │
│ │ possible. │
│ │ │
Expand Down
1 change: 1 addition & 0 deletions docs/source/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
{meth}`~pytask.TaskWithoutPath.execute`. Thanks to {user}`Ostheer`.
- {pull}`551` removes the deprecated `@pytask.mark.depends_on` and
`@pytask.mark.produces`.
- {pull}`552` removes the deprecated `@pytask.mark.task`.

## 0.4.5 - 2024-01-09

Expand Down
21 changes: 0 additions & 21 deletions docs/source/reference_guides/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,27 +96,6 @@ plugins process. The following marks are available by default.

Skip a task.

.. function:: pytask.mark.task(name, *, id, kwargs)

The task decorator allows to mark any task function regardless of its name as a task
or assigns a new task name.

It also allows to repeat tasks in for-loops by adding a specific ``id`` or keyword
arguments via ``kwargs``.

.. deprecated:: 0.4.0

Will be removed in v0.5.0. Use :func:`~pytask.task` instead.

:type name: str | None
:param name: The name of the task.
:type id: str | None
:param id: An id for the task if it is part of a parametrization.
:type kwargs: dict[Any, Any] | None
:param kwargs:
A dictionary containing keyword arguments which are passed to the task when it
is executed.

.. function:: pytask.mark.try_first

Indicate that the task should be executed as soon as possible.
Expand Down
5 changes: 0 additions & 5 deletions docs/source/tutorials/write_a_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,6 @@ def create_random_data():
...
```

```{warning}
Since v0.4 users should use {func}`@task <pytask.task>` over
{func}`@pytask.mark.task <pytask.mark.task>` which will be removed in v0.5.
```

## Customize task module names

Use the configuration value {confval}`task_files` if you prefer a different naming
Expand Down
11 changes: 5 additions & 6 deletions src/_pytask/collect_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ def parse_dependencies_from_task_function(
kwargs[name] = parameters_with_node_annot.pop(name)
else:
msg = (
f"The value for the parameter {name!r} is defined twice in "
"'@pytask.mark.task(kwargs=...)' and in the type annotation. Choose "
"only one option."
f"The value for the parameter {name!r} is defined twice, in "
"'@task(kwargs=...)' and in the type annotation. Choose only one way."
)
raise ValueError(msg)

Expand Down Expand Up @@ -209,9 +208,9 @@ def parse_products_from_task_function(
and parameter_name in parameters_with_node_annot
):
msg = (
f"The value for the parameter {parameter_name!r} is defined twice "
"in '@pytask.mark.task(kwargs=...)' and in the type annotation. "
"Choose only one option."
f"The value for the parameter {parameter_name!r} is defined twice, "
"in '@task(kwargs=...)' and in the type annotation. Choose only "
"one way."
)
raise ValueError(msg)

Expand Down
8 changes: 3 additions & 5 deletions src/_pytask/mark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ def from_task(cls, task: PTask) -> KeywordMatcher:
mapped_names = {task.name}

# Add the names attached to the current function through direct assignment.
function_obj = task.function
if function_obj:
mapped_names.update(function_obj.__dict__)
mapped_names.update(task.function.__dict__)

# Add the markers to the keywords as we no longer handle them correctly.
mapped_names.update(mark.name for mark in task.markers)
Expand All @@ -149,7 +147,7 @@ def __call__(self, subname: str) -> bool:
return any(subname in name for name in names)


def select_by_keyword(session: Session, dag: nx.DiGraph) -> set[str]:
def select_by_keyword(session: Session, dag: nx.DiGraph) -> set[str] | None:
"""Deselect tests by keywords."""
keywordexpr = session.config["expression"]
if not keywordexpr:
Expand Down Expand Up @@ -204,7 +202,7 @@ def __call__(self, name: str) -> bool:
return name in self.own_mark_names


def select_by_mark(session: Session, dag: nx.DiGraph) -> set[str]:
def select_by_mark(session: Session, dag: nx.DiGraph) -> set[str] | None:
"""Deselect tests by marks."""
matchexpr = session.config["marker_expression"]
if not matchexpr:
Expand Down
44 changes: 0 additions & 44 deletions src/_pytask/mark/__init__.pyi

This file was deleted.

20 changes: 7 additions & 13 deletions src/_pytask/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
if name in ("depends_on", "produces"):
raise RuntimeError(_DEPRECATION_DECORATOR.format(name))

if name == "task":
msg = (
"'@pytask.mark.task' is removed. Use '@task' with 'from pytask import "
"task' instead."
)
raise RuntimeError(msg)

# If the name is not in the set of known marks after updating,
# then it really is time to issue a warning or an error.
if self.config is not None and name not in self.config["markers"]:
Expand All @@ -217,19 +224,6 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
stacklevel=2,
)

if name == "task":
from _pytask.task_utils import task

warnings.warn(
"'@pytask.mark.task' is deprecated starting pytask v0.4.0 and will be "
"removed in v0.5.0. Use '@task' with 'from pytask import task' "
"instead.",
category=FutureWarning,
stacklevel=1,
)

return task

return MarkDecorator(Mark(name, (), {}))


Expand Down
2 changes: 1 addition & 1 deletion src/_pytask/task.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Contain hooks related to the ``@pytask.mark.task`` decorator."""
"""Contain hooks related to the :func:`@task <pytask.task>`."""
from __future__ import annotations

from typing import Any
Expand Down
15 changes: 7 additions & 8 deletions src/_pytask/task_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Contains utilities related to the ``@pytask.mark.task`` decorator."""
"""Contains utilities related to the :func:`@task <pytask.task>`."""
from __future__ import annotations

import functools
Expand Down Expand Up @@ -31,9 +31,9 @@
COLLECTED_TASKS: dict[Path | None, list[Callable[..., Any]]] = defaultdict(list)
"""A container for collecting tasks.

Tasks marked by the ``@pytask.mark.task`` decorator can be generated in a loop where one
iteration overwrites the previous task. To retrieve the tasks later, use this dictionary
mapping from paths of modules to a list of tasks per module.
Tasks marked by the :func:`@task <pytask.task>` decorator can be generated in a loop
where one iteration overwrites the previous task. To retrieve the tasks later, use this
dictionary mapping from paths of modules to a list of tasks per module.

"""

Expand Down Expand Up @@ -96,8 +96,7 @@ def wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
for arg, arg_name in ((name, "name"), (id, "id")):
if not (isinstance(arg, str) or arg is None):
msg = (
f"Argument {arg_name!r} of @pytask.mark.task must be a str, but it "
f"is {arg!r}."
f"Argument {arg_name!r} of @task must be a str, but it is {arg!r}."
)
raise ValueError(msg)

Expand Down Expand Up @@ -231,7 +230,7 @@ def _parse_task(task: Callable[..., Any]) -> tuple[str, Callable[..., Any]]:

if meta.name is None and task.__name__ == "_":
msg = (
"A task function either needs 'name' passed by the ``@pytask.mark.task`` "
"A task function either needs 'name' passed by the ``@task`` "
"decorator or the function name of the task function must not be '_'."
)
raise ValueError(msg)
Expand All @@ -255,7 +254,7 @@ def _parse_task_kwargs(kwargs: Any) -> dict[str, Any]:
if attrs.has(type(kwargs)):
return attrs.asdict(kwargs)
msg = (
"'@pytask.mark.task(kwargs=...) needs to be a dictionary, namedtuple or an "
"'@task(kwargs=...) needs to be a dictionary, namedtuple or an "
"instance of an attrs class."
)
raise ValueError(msg)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ def task_no_2():
)
def test_keyword_option_parametrize(tmp_path, expr: str, expected_passed: str) -> None:
source = """
import pytask
from pytask import task

for arg in [None, 1.3, "2-3"]:

@pytask.mark.task
@task
def task_func(arg=arg):
pass
"""
Expand Down Expand Up @@ -373,7 +373,7 @@ def task_write_text(): ...


@pytest.mark.end_to_end()
@pytest.mark.parametrize("name", ["parametrize", "depends_on", "produces"])
@pytest.mark.parametrize("name", ["parametrize", "depends_on", "produces", "task"])
def test_error_with_depreacated_markers(runner, tmp_path, name):
source = f"""
from pytask import mark
Expand Down
2 changes: 1 addition & 1 deletion tests/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def task_example():
result = runner.invoke(cli, [tmp_path.as_posix()])

assert result.exit_code == ExitCode.COLLECTION_FAILED
assert "Argument 'id' of @pytask.mark.task" in result.output
assert "Argument 'id' of @task" in result.output


@pytest.mark.end_to_end()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_task_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ class ExampleAttrs:
(ExampleNT(), does_not_raise(), {"a": 1}),
(ExampleNT, pytest.raises(TypeError, match=r"(_asdict\(\) missing 1)"), None),
(ExampleAttrs(), does_not_raise(), {"b": "wonderful"}),
(ExampleAttrs, pytest.raises(ValueError, match="@pytask.mark.task"), None),
(1, pytest.raises(ValueError, match="@pytask.mark.task"), None),
(ExampleAttrs, pytest.raises(ValueError, match="@task"), None),
(1, pytest.raises(ValueError, match="@task"), None),
],
)
def test_parse_task_kwargs(kwargs, expectation, expected):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,11 @@ def warn_now():
def test_multiple_occurrences_of_warning_are_reduced(tmp_path, runner):
source = """
import warnings
import pytask
from pytask import task

for i in range(10):

@pytask.mark.task
@task
def task_example():
warnings.warn("warning!!!")
"""
Expand Down