From 494ec67795e0a7e1db8501ee2d7794a5c6574119 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Mon, 10 Jan 2022 22:17:18 +0100 Subject: [PATCH 1/8] Deprecate Python 3.6. --- README.rst | 5 ++--- docs/source/changes.rst | 6 ++++++ setup.cfg | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 383a72fc..bd4a33b3 100644 --- a/README.rst +++ b/README.rst @@ -85,9 +85,8 @@ Installation .. start-installation -pytask is available on `PyPI `_ for Python >= 3.6.1 and -on `Anaconda.org `_ for Python >= 3.7. Install -the package with +pytask is available on `PyPI `_ and on `Anaconda.org +`_. Install the package with .. code-block:: console diff --git a/docs/source/changes.rst b/docs/source/changes.rst index 4de9d4a3..c207c1fc 100644 --- a/docs/source/changes.rst +++ b/docs/source/changes.rst @@ -7,6 +7,12 @@ all releases are available on `PyPI `_ and `Anaconda.org `_. +0.2.0 - 2022-xx-xx +------------------ + +- :gh:`192` deprecates Python 3.6. + + 0.1.5 - 2022-01-10 ------------------ diff --git a/setup.cfg b/setup.cfg index 72e86c10..28a9fb40 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,6 @@ classifiers = Operating System :: POSIX Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -43,7 +42,7 @@ install_requires = pluggy pony>=0.7.13 rich -python_requires = >=3.6.1 +python_requires = >=3.7 include_package_data = True package_dir = =src From bcf8c33918100858999660b427c60a6d8272006a Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Mon, 10 Jan 2022 22:18:41 +0100 Subject: [PATCH 2/8] Upgrade pyupgrade. --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c07084d1..737be311 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: rev: v2.31.0 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/asottile/reorder_python_imports rev: v2.6.0 hooks: From 96a42891c1ce1c9040dff5f64d7c278226d62051 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Mon, 10 Jan 2022 22:20:21 +0100 Subject: [PATCH 3/8] Remove _py36_windowsconsoleio_workaround. --- src/_pytask/capture.py | 62 ------------------------------------------ 1 file changed, 62 deletions(-) diff --git a/src/_pytask/capture.py b/src/_pytask/capture.py index 152ee9d4..d5dc07c9 100644 --- a/src/_pytask/capture.py +++ b/src/_pytask/capture.py @@ -124,8 +124,6 @@ def pytask_parse_config( @hookimpl def pytask_post_parse(config: Dict[str, Any]) -> None: """Initialize the CaptureManager.""" - if config["capture"] == "fd": - _py36_windowsconsoleio_workaround(sys.stdout) _colorama_workaround() pluginmanager = config["pm"] @@ -181,66 +179,6 @@ def _colorama_workaround() -> None: pass -def _py36_windowsconsoleio_workaround(stream: TextIO) -> None: - """Workaround for Windows Unicode console handling on Python>=3.6. - - Python 3.6 implemented Unicode console handling for Windows. This works by - reading/writing to the raw console handle using ``{Read,Write}ConsoleW``. - - The problem is that we are going to ``dup2`` over the stdio file descriptors when - doing ``FDCapture`` and this will ``CloseHandle`` the handles used by Python to - write to the console. Though there is still some weirdness and the console handle - seems to only be closed randomly and not on the first call to ``CloseHandle``, or - maybe it gets reopened with the same handle value when we suspend capturing. - - The workaround in this case will reopen stdio with a different fd which also means a - different handle by replicating the logic in - "Py_lifecycle.c:initstdio/create_stdio". - - Parameters - --------- - stream - In practice ``sys.stdout`` or ``sys.stderr``, but given here as parameter for - unit testing purposes. - - See https://github.com/pytest-dev/py/issues/103. - - """ - if not sys.platform.startswith("win32") or hasattr(sys, "pypy_version_info"): - return - - # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). - if not hasattr(stream, "buffer"): - return - - buffered = hasattr(stream.buffer, "raw") - # ``getattr`` hack since ``buffer`` might not have an attribute ``raw``. - raw_stdout = getattr(stream.buffer, "raw", stream.buffer) - - # ``getattr`` hack since ``_WindowsConsoleIO`` is not defined in stubs. - windowsconsoleio = getattr(io, "_WindowsConsoleIO", None) - if windowsconsoleio is not None and not isinstance(raw_stdout, windowsconsoleio): - return - - def _reopen_stdio(f: TextIO, mode: str) -> TextIO: - if not buffered and mode[0] == "w": - buffering = 0 - else: - buffering = -1 - - return io.TextIOWrapper( - open(os.dup(f.fileno()), mode, buffering), - f.encoding, - f.errors, - f.newlines, - bool(f.line_buffering), - ) - - sys.stdin = _reopen_stdio(sys.stdin, "rb") - sys.stdout = _reopen_stdio(sys.stdout, "wb") - sys.stderr = _reopen_stdio(sys.stderr, "wb") - - # IO Helpers. From 3be6c1218f8ae4d0f8e0f4a402b5cd9b838f4253 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Mon, 17 Jan 2022 23:34:18 +0100 Subject: [PATCH 4/8] Upgrade many typing import statements. --- .pre-commit-config.yaml | 1 + docs/source/conf.py | 4 +- setup.py | 2 + src/_pytask/__init__.py | 2 + src/_pytask/build.py | 7 +- src/_pytask/capture.py | 48 ++++++------ src/_pytask/clean.py | 30 ++++--- src/_pytask/cli.py | 7 +- src/_pytask/collect.py | 32 ++++---- src/_pytask/collect_command.py | 19 +++-- src/_pytask/compat.py | 14 ++-- src/_pytask/config.py | 29 ++++--- src/_pytask/console.py | 25 +++--- src/_pytask/dag.py | 9 ++- src/_pytask/database.py | 11 +-- src/_pytask/debugging.py | 41 +++++----- src/_pytask/exceptions.py | 1 + src/_pytask/execute.py | 14 ++-- src/_pytask/graph.py | 23 +++--- src/_pytask/hookspecs.py | 117 +++++++++++++--------------- src/_pytask/live.py | 22 +++--- src/_pytask/logging.py | 33 ++++---- src/_pytask/mark/__init__.py | 27 +++---- src/_pytask/mark/expression.py | 11 +-- src/_pytask/mark/structures.py | 24 +++--- src/_pytask/mark_utils.py | 7 +- src/_pytask/nodes.py | 56 ++++++------- src/_pytask/outcomes.py | 14 ++-- src/_pytask/parameters.py | 2 + src/_pytask/parametrize.py | 69 +++++++--------- src/_pytask/path.py | 9 ++- src/_pytask/persist.py | 12 +-- src/_pytask/pluginmanager.py | 2 + src/_pytask/profile.py | 44 +++++------ src/_pytask/report.py | 20 ++--- src/_pytask/resolve_dependencies.py | 25 +++--- src/_pytask/session.py | 4 +- src/_pytask/shared.py | 30 ++++--- src/_pytask/skipping.py | 15 ++-- src/_pytask/traceback.py | 8 +- src/pytask/__init__.py | 2 + src/pytask/__main__.py | 2 + tests/_test_console_helpers.py | 2 + tests/conftest.py | 2 + tests/test_build.py | 2 + tests/test_capture.py | 2 + tests/test_clean.py | 2 + tests/test_cli.py | 2 + tests/test_collect.py | 2 + tests/test_collect_command.py | 2 + tests/test_compat.py | 2 + tests/test_config.py | 2 + tests/test_console.py | 2 + tests/test_dag.py | 2 + tests/test_database.py | 2 + tests/test_debugging.py | 2 + tests/test_execute.py | 2 + tests/test_graph.py | 2 + tests/test_ignore.py | 2 + tests/test_live.py | 2 + tests/test_logging.py | 2 + tests/test_mark.py | 2 + tests/test_mark_cli.py | 2 + tests/test_mark_expression.py | 2 + tests/test_nodes.py | 2 + tests/test_outcomes.py | 2 + tests/test_parametrize.py | 2 + tests/test_path.py | 2 + tests/test_persist.py | 2 + tests/test_profile.py | 2 + tests/test_resolve_dependencies.py | 2 + tests/test_shared.py | 2 + tests/test_skipping.py | 2 + tests/test_traceback.py | 2 + 74 files changed, 486 insertions(+), 452 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 737be311..1f5d550d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,6 +38,7 @@ repos: rev: v2.6.0 hooks: - id: reorder-python-imports + args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/setup-cfg-fmt rev: v1.20.0 hooks: diff --git a/docs/source/conf.py b/docs/source/conf.py index 488a4e8f..6c915867 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,6 +5,8 @@ # If extensions (or modules to document with autodoc) are in another directory, add # these directories to sys.path here. If the directory is relative to the documentation # root, use os.path.abspath to make it absolute, like shown here. +from __future__ import annotations + from importlib.metadata import version import sphinx @@ -137,7 +139,7 @@ } -def setup(app: "sphinx.application.Sphinx") -> None: +def setup(app: sphinx.application.Sphinx) -> None: app.add_object_type( "confval", "confval", diff --git a/setup.py b/setup.py index 26e08e48..c21a9ee0 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from setuptools import setup diff --git a/src/_pytask/__init__.py b/src/_pytask/__init__.py index 165802c5..f493641c 100644 --- a/src/_pytask/__init__.py +++ b/src/_pytask/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + try: from ._version import version as __version__ except ImportError: diff --git a/src/_pytask/build.py b/src/_pytask/build.py index 72a6ead3..69e06e63 100644 --- a/src/_pytask/build.py +++ b/src/_pytask/build.py @@ -1,7 +1,8 @@ """Implement the build command.""" +from __future__ import annotations + import sys from typing import Any -from typing import Dict from typing import TYPE_CHECKING import click @@ -26,7 +27,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: cli.add_command(build) -def main(config_from_cli: Dict[str, Any]) -> Session: +def main(config_from_cli: dict[str, Any]) -> Session: """Run pytask. This is the main command to run pytask which usually receives kwargs from the @@ -112,7 +113,7 @@ def main(config_from_cli: Dict[str, Any]) -> Session: type=click.Choice(["yes", "no"]), help="Choose whether tracebacks should be displayed or not. [default: yes]", ) -def build(**config_from_cli: Any) -> "NoReturn": +def build(**config_from_cli: Any) -> NoReturn: """Collect and execute tasks and report the results. This is the default command of pytask which searches given paths or the current diff --git a/src/_pytask/capture.py b/src/_pytask/capture.py index d5dc07c9..146817e8 100644 --- a/src/_pytask/capture.py +++ b/src/_pytask/capture.py @@ -23,6 +23,8 @@ `_. """ +from __future__ import annotations + import contextlib import functools import io @@ -31,15 +33,11 @@ from tempfile import TemporaryFile from typing import Any from typing import AnyStr -from typing import Dict from typing import Generator from typing import Generic from typing import Iterator -from typing import Optional from typing import TextIO -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import click from _pytask.config import hookimpl @@ -92,9 +90,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse configuration. @@ -122,7 +120,7 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Initialize the CaptureManager.""" _colorama_workaround() @@ -134,7 +132,7 @@ def pytask_post_parse(config: Dict[str, Any]) -> None: capman.suspend() -def _capture_callback(x: "Optional[_CaptureMethod]") -> "Optional[_CaptureMethod]": +def _capture_callback(x: _CaptureMethod | None) -> _CaptureMethod | None: """Validate the passed options for capturing output.""" if x in [None, "None", "none"]: x = None @@ -146,8 +144,8 @@ def _capture_callback(x: "Optional[_CaptureMethod]") -> "Optional[_CaptureMethod def _show_capture_callback( - x: "Optional[_CaptureCallback]", -) -> "Optional[_CaptureCallback]": + x: _CaptureCallback | None, +) -> _CaptureCallback | None: """Validate the passed options for showing captured output.""" if x in [None, "None", "none"]: x = None @@ -230,7 +228,7 @@ def read(self, *_args: Any) -> None: # noqa: U101 readlines = read __next__ = read - def __iter__(self) -> "DontReadFromInput": + def __iter__(self) -> DontReadFromInput: return self def fileno(self) -> int: @@ -243,7 +241,7 @@ def close(self) -> None: pass @property - def buffer(self) -> "DontReadFromInput": + def buffer(self) -> DontReadFromInput: return self @@ -306,7 +304,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -401,7 +399,7 @@ def __init__(self, targetfd: int) -> None: # Further complications are the need to support suspend() and the # possibility of FD reuse (e.g. the tmpfile getting the very same target # FD). The following approach is robust, I believe. - self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -434,7 +432,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -552,8 +550,8 @@ def __getitem__(self, item: int) -> AnyStr: return tuple(self)[item] def _replace( - self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None - ) -> "CaptureResult[AnyStr]": + self, *, out: AnyStr | None = None, err: AnyStr | None = None + ) -> CaptureResult[AnyStr]: return CaptureResult( out=self.out if out is None else out, err=self.err if err is None else err ) @@ -595,9 +593,9 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: Optional[Union[FDCapture, SysCapture]], - out: Optional[Union[FDCapture, SysCapture]], - err: Optional[Union[FDCapture, SysCapture]], + in_: FDCapture | SysCapture | None, + out: FDCapture | SysCapture | None, + err: FDCapture | SysCapture | None, ) -> None: self.in_ = in_ self.out = out @@ -624,7 +622,7 @@ def start_capturing(self) -> None: if self.err: self.err.start() - def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: @@ -681,7 +679,7 @@ def readouterr(self) -> CaptureResult[AnyStr]: return CaptureResult(out, err) # type: ignore -def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: +def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: """Set up the MultiCapture class with the passed method. For each valid method, the function instantiates the :class:`MultiCapture` class @@ -717,9 +715,9 @@ class CaptureManager: """ - def __init__(self, method: "_CaptureMethod") -> None: + def __init__(self, method: _CaptureMethod) -> None: self._method = method - self._capturing: Optional[MultiCapture[str]] = None + self._capturing: MultiCapture[str] | None = None def __repr__(self) -> str: return ("").format( diff --git a/src/_pytask/clean.py b/src/_pytask/clean.py index 6abd7307..33c015dc 100644 --- a/src/_pytask/clean.py +++ b/src/_pytask/clean.py @@ -1,18 +1,14 @@ """Add a command to clean the project from files unknown to pytask.""" +from __future__ import annotations + import itertools import shutil import sys from pathlib import Path from types import TracebackType from typing import Any -from typing import Dict from typing import Generator from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING import attr @@ -50,7 +46,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], config_from_cli: Dict[str, Any] + config: dict[str, Any], config_from_cli: dict[str, Any] ) -> None: """Parse the configuration.""" config["mode"] = get_first_non_none_value( @@ -65,7 +61,7 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Correct ignore patterns such that caches, etc. will not be ignored.""" if config["command"] == "clean": config["ignore"] = [ @@ -83,7 +79,7 @@ def pytask_post_parse(config: Dict[str, Any]) -> None: @click.option( "-q", "--quiet", is_flag=True, help="Do not print the names of the removed paths." ) -def clean(**config_from_cli: Any) -> "NoReturn": +def clean(**config_from_cli: Any) -> NoReturn: """Clean provided paths by removing files unknown to pytask.""" config_from_cli["command"] = "clean" @@ -101,8 +97,8 @@ def clean(**config_from_cli: Any) -> "NoReturn": except Exception: session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED - exc_info: Tuple[ - Type[BaseException], BaseException, Optional[TracebackType] + exc_info: tuple[ + type[BaseException], BaseException, TracebackType | None ] = sys.exc_info() console.print(render_exc_info(*exc_info, config["show_locals"])) @@ -164,7 +160,7 @@ def clean(**config_from_cli: Any) -> "NoReturn": sys.exit(session.exit_code) -def _collect_all_paths_known_to_pytask(session: Session) -> Set[Path]: +def _collect_all_paths_known_to_pytask(session: Session) -> set[Path]: """Collect all paths from the session which are known to pytask. Paths belong to tasks and nodes and configuration values. @@ -175,7 +171,7 @@ def _collect_all_paths_known_to_pytask(session: Session) -> Set[Path]: for path in _yield_paths_from_task(task): known_files.add(path) - known_directories: Set[Path] = set() + known_directories: set[Path] = set() for path in known_files: known_directories.update(path.parents) @@ -199,8 +195,8 @@ def _yield_paths_from_task(task: MetaTask) -> Generator[Path, None, None]: def _find_all_unknown_paths( - session: Session, known_paths: Set[Path], include_directories: bool -) -> List[Path]: + session: Session, known_paths: set[Path], include_directories: bool +) -> list[Path]: """Find all unknown paths. First, create a tree of :class:`_RecursivePathNode`. Then, create a list of unknown @@ -237,7 +233,7 @@ class _RecursivePathNode: """ path = attr.ib(type=Path) - sub_nodes = attr.ib(type="List[_RecursivePathNode]") + sub_nodes = attr.ib(type=list["_RecursivePathNode"]) is_dir = attr.ib(type=bool) is_file = attr.ib(type=bool) is_unknown = attr.ib(type=bool) @@ -245,7 +241,7 @@ class _RecursivePathNode: @classmethod def from_path( cls, path: Path, known_paths: Iterable[Path], session: Session - ) -> "_RecursivePathNode": + ) -> _RecursivePathNode: """Create a node from a path. While instantiating the class, subordinate nodes are spawned for all paths diff --git a/src/_pytask/cli.py b/src/_pytask/cli.py index d898b6d1..18b86f9b 100644 --- a/src/_pytask/cli.py +++ b/src/_pytask/cli.py @@ -1,7 +1,8 @@ """Implements the command line interface.""" +from __future__ import annotations + import sys from typing import Any -from typing import Dict import click import pluggy @@ -11,11 +12,11 @@ from packaging.version import parse as parse_version -_CONTEXT_SETTINGS: Dict[str, Any] = {"help_option_names": ["-h", "--help"]} +_CONTEXT_SETTINGS: dict[str, Any] = {"help_option_names": ["-h", "--help"]} if parse_version(click.__version__) < parse_version("8"): - _VERSION_OPTION_KWARGS: Dict[str, Any] = {} + _VERSION_OPTION_KWARGS: dict[str, Any] = {} else: _VERSION_OPTION_KWARGS = {"package_name": "pytask"} diff --git a/src/_pytask/collect.py b/src/_pytask/collect.py index 29ab0e3d..9d139225 100644 --- a/src/_pytask/collect.py +++ b/src/_pytask/collect.py @@ -1,4 +1,6 @@ """Implement functionality to collect tasks.""" +from __future__ import annotations + import inspect import os import sys @@ -6,12 +8,8 @@ from importlib import util as importlib_util from pathlib import Path from typing import Any -from typing import Dict from typing import Generator from typing import Iterable -from typing import List -from typing import Optional -from typing import Union from _pytask.config import hookimpl from _pytask.config import IS_FILE_SYSTEM_CASE_SENSITIVE @@ -75,7 +73,7 @@ def _collect_from_paths(session: Session) -> None: @hookimpl -def pytask_ignore_collect(path: Path, config: Dict[str, Any]) -> bool: +def pytask_ignore_collect(path: Path, config: dict[str, Any]) -> bool: """Ignore a path during the collection.""" is_ignored = any(path.match(pattern) for pattern in config["ignore"]) return is_ignored @@ -83,8 +81,8 @@ def pytask_ignore_collect(path: Path, config: Dict[str, Any]) -> bool: @hookimpl def pytask_collect_file_protocol( - session: Session, path: Path, reports: List[CollectionReport] -) -> List[CollectionReport]: + session: Session, path: Path, reports: list[CollectionReport] +) -> list[CollectionReport]: """Wrap the collection of tasks from a file to collect reports.""" try: reports = session.hook.pytask_collect_file( @@ -105,8 +103,8 @@ def pytask_collect_file_protocol( @hookimpl def pytask_collect_file( - session: Session, path: Path, reports: List[CollectionReport] -) -> Optional[List[CollectionReport]]: + session: Session, path: Path, reports: list[CollectionReport] +) -> list[CollectionReport] | None: """Collect a file.""" if any(path.match(pattern) for pattern in session.config["task_files"]): spec = importlib_util.spec_from_file_location(path.stem, str(path)) @@ -141,7 +139,7 @@ def pytask_collect_file( @hookimpl def pytask_collect_task_protocol( session: Session, path: Path, name: str, obj: Any -) -> Optional[CollectionReport]: +) -> CollectionReport | None: """Start protocol for collecting a task.""" try: session.hook.pytask_collect_task_setup( @@ -169,7 +167,7 @@ def pytask_collect_task_protocol( @hookimpl(trylast=True) def pytask_collect_task( session: Session, path: Path, name: str, obj: Any -) -> Optional[PythonFunctionTask]: +) -> PythonFunctionTask | None: """Collect a task which is a function. There is some discussion on how to detect functions in this `thread @@ -195,8 +193,8 @@ def pytask_collect_task( @hookimpl(trylast=True) def pytask_collect_node( - session: Session, path: Path, node: Union[str, Path] -) -> Optional[FilePathNode]: + session: Session, path: Path, node: str | Path +) -> FilePathNode | None: """Collect a node of a task as a :class:`pytask.nodes.FilePathNode`. Strings are assumed to be paths. This might be a strict assumption, but since this @@ -273,7 +271,7 @@ def _not_ignored_paths( @hookimpl(trylast=True) -def pytask_collect_modify_tasks(tasks: List[MetaTask]) -> None: +def pytask_collect_modify_tasks(tasks: list[MetaTask]) -> None: """Given all tasks, assign a short uniquely identifiable name to each task. The shorter ids are necessary to display @@ -286,8 +284,8 @@ def pytask_collect_modify_tasks(tasks: List[MetaTask]) -> None: def _find_shortest_uniquely_identifiable_name_for_tasks( - tasks: List[MetaTask], -) -> Dict[str, str]: + tasks: list[MetaTask], +) -> dict[str, str]: """Find the shortest uniquely identifiable name for tasks. The shortest unique id consists of the module name plus the base name (e.g. function @@ -320,7 +318,7 @@ def _find_shortest_uniquely_identifiable_name_for_tasks( @hookimpl def pytask_collect_log( - session: Session, reports: List[CollectionReport], tasks: List[PythonFunctionTask] + session: Session, reports: list[CollectionReport], tasks: list[PythonFunctionTask] ) -> None: """Log collection.""" session.collection_end = time.time() diff --git a/src/_pytask/collect_command.py b/src/_pytask/collect_command.py index 5e813338..9c4aa22e 100644 --- a/src/_pytask/collect_command.py +++ b/src/_pytask/collect_command.py @@ -1,10 +1,9 @@ """This module contains the implementation of ``pytask collect``.""" +from __future__ import annotations + import sys from pathlib import Path from typing import Any -from typing import Dict -from typing import List -from typing import Optional from typing import TYPE_CHECKING import click @@ -42,7 +41,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], config_from_cli: Dict[str, Any] + config: dict[str, Any], config_from_cli: dict[str, Any] ) -> None: """Parse configuration.""" config["nodes"] = config_from_cli.get("nodes", False) @@ -50,7 +49,7 @@ def pytask_parse_config( @click.command() @click.option("--nodes", is_flag=True, help="Show a task's dependencies and products.") -def collect(**config_from_cli: Optional[Any]) -> "NoReturn": +def collect(**config_from_cli: Any | None) -> NoReturn: """Collect tasks from paths.""" config_from_cli["command"] = "collect" @@ -107,7 +106,7 @@ def collect(**config_from_cli: Optional[Any]) -> "NoReturn": sys.exit(session.exit_code) -def _select_tasks_by_expressions_and_marker(session: Session) -> "List[MetaTask]": +def _select_tasks_by_expressions_and_marker(session: Session) -> list[MetaTask]: """Select tasks by expressions and marker.""" all_tasks = {task.name for task in session.tasks} remaining_by_mark = select_by_mark(session, session.dag) or all_tasks @@ -118,7 +117,7 @@ def _select_tasks_by_expressions_and_marker(session: Session) -> "List[MetaTask] def _find_common_ancestor_of_all_nodes( - tasks: "List[MetaTask]", paths: List[Path], show_nodes: bool + tasks: list[MetaTask], paths: list[Path], show_nodes: bool ) -> Path: """Find common ancestor from all nodes and passed paths.""" all_paths = [] @@ -138,14 +137,14 @@ def _find_common_ancestor_of_all_nodes( return common_ancestor -def _organize_tasks(tasks: List["MetaTask"]) -> Dict[Path, List["MetaTask"]]: +def _organize_tasks(tasks: list[MetaTask]) -> dict[Path, list[MetaTask]]: """Organize tasks in a dictionary. The dictionary has file names as keys and then a dictionary with task names and below a dictionary with dependencies and targets. """ - dictionary: Dict[Path, List["MetaTask"]] = {} + dictionary: dict[Path, list[MetaTask]] = {} for task in tasks: dictionary[task.path] = dictionary.get(task.path, []) dictionary[task.path].append(task) @@ -158,7 +157,7 @@ def _organize_tasks(tasks: List["MetaTask"]) -> Dict[Path, List["MetaTask"]]: def _print_collected_tasks( - dictionary: Dict[Path, List["MetaTask"]], + dictionary: dict[Path, list[MetaTask]], show_nodes: bool, editor_url_scheme: str, common_ancestor: Path, diff --git a/src/_pytask/compat.py b/src/_pytask/compat.py index 0c453a28..733f1294 100644 --- a/src/_pytask/compat.py +++ b/src/_pytask/compat.py @@ -1,20 +1,20 @@ """This module contains functions to assess compatibility and optional dependencies.""" +from __future__ import annotations + import importlib import shutil import sys import types import warnings -from typing import Dict -from typing import Optional from packaging.version import parse as parse_version -_MINIMUM_VERSIONS: Dict[str, str] = {} +_MINIMUM_VERSIONS: dict[str, str] = {} """Dict[str, str]: A mapping from packages to their minimum versions.""" -_IMPORT_TO_PACKAGE_NAME: Dict[str, str] = {} +_IMPORT_TO_PACKAGE_NAME: dict[str, str] = {} """Dict[str, str]: A mapping from import name to package name (on PyPI) for packages where these two names are different.""" @@ -31,9 +31,9 @@ def import_optional_dependency( name: str, extra: str = "", errors: str = "raise", - min_version: Optional[str] = None, + min_version: str | None = None, caller: str = "pytask", -) -> Optional[types.ModuleType]: +) -> types.ModuleType | None: """Import an optional dependency. By default, if a dependency is missing an ImportError with a nice message will be @@ -119,7 +119,7 @@ def check_for_optional_program( extra: str = "", errors: str = "raise", caller: str = "pytask", -) -> Optional[bool]: +) -> bool | None: """Check whether an optional program exists.""" if errors not in ("warn", "raise", "ignore"): raise ValueError( diff --git a/src/_pytask/config.py b/src/_pytask/config.py index 5d1645c7..46afbb22 100644 --- a/src/_pytask/config.py +++ b/src/_pytask/config.py @@ -1,4 +1,6 @@ """Configure pytask.""" +from __future__ import annotations + import configparser import itertools import os @@ -6,9 +8,6 @@ import warnings from pathlib import Path from typing import Any -from typing import Dict -from typing import List -from typing import Tuple import pluggy from _pytask.shared import convert_truthy_or_falsy_to_bool @@ -21,7 +20,7 @@ hookimpl = pluggy.HookimplMarker("pytask") -_IGNORED_FOLDERS: List[str] = [ +_IGNORED_FOLDERS: list[str] = [ ".git/*", ".hg/*", ".svn/*", @@ -29,7 +28,7 @@ ] -_IGNORED_FILES: List[str] = [ +_IGNORED_FILES: list[str] = [ ".codecov.yml", ".gitignore", ".pre-commit-config.yaml", @@ -44,10 +43,10 @@ ] -_IGNORED_FILES_AND_FOLDERS: List[str] = _IGNORED_FILES + _IGNORED_FOLDERS +_IGNORED_FILES_AND_FOLDERS: list[str] = _IGNORED_FILES + _IGNORED_FOLDERS -IGNORED_TEMPORARY_FILES_AND_FOLDERS: List[str] = [ +IGNORED_TEMPORARY_FILES_AND_FOLDERS: list[str] = [ "*.egg-info/*", ".ipynb_checkpoints/*", ".mypy_cache/*", @@ -72,8 +71,8 @@ def is_file_system_case_sensitive() -> bool: @hookimpl def pytask_configure( - pm: pluggy.PluginManager, config_from_cli: Dict[str, Any] -) -> Dict[str, Any]: + pm: pluggy.PluginManager, config_from_cli: dict[str, Any] +) -> dict[str, Any]: """Configure pytask.""" config = {"pm": pm} @@ -131,9 +130,9 @@ def pytask_configure( @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse the configuration.""" config["command"] = config_from_cli.get("command", "build") @@ -210,12 +209,12 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Sort markers alphabetically.""" config["markers"] = {k: config["markers"][k] for k in sorted(config["markers"])} -def _find_project_root_and_ini(paths: List[Path]) -> Tuple[Path, Path]: +def _find_project_root_and_ini(paths: list[Path]) -> tuple[Path, Path]: """Find the project root and configuration file from a list of paths.""" try: common_ancestor = Path(os.path.commonpath(paths)) @@ -254,7 +253,7 @@ def _find_project_root_and_ini(paths: List[Path]) -> Tuple[Path, Path]: return root, config_path -def _read_config(path: Path) -> Dict[str, Any]: +def _read_config(path: Path) -> dict[str, Any]: """Read the configuration from a file with a [pytask] section.""" config = configparser.ConfigParser() config.read(path) diff --git a/src/_pytask/console.py b/src/_pytask/console.py index 85d37355..95e73c75 100644 --- a/src/_pytask/console.py +++ b/src/_pytask/console.py @@ -1,4 +1,6 @@ """This module contains the code to format output on the command line.""" +from __future__ import annotations + import functools import inspect import os @@ -7,13 +9,8 @@ from pathlib import Path from typing import Any from typing import Callable -from typing import Dict from typing import Iterable -from typing import List -from typing import Optional -from typing import Type from typing import TYPE_CHECKING -from typing import Union import rich from _pytask.path import relative_to as relative_to_ @@ -63,7 +60,7 @@ TASK_ICON = "" if _IS_LEGACY_WINDOWS else "📝 " -_EDITOR_URL_SCHEMES: Dict[str, str] = { +_EDITOR_URL_SCHEMES: dict[str, str] = { "no_link": "", "file": "file:///{path}", "vscode": "vscode://file/{path}:{line_number}", @@ -89,9 +86,9 @@ def render_to_string( - text: Union[str, Text], + text: str | Text, *, - console: Optional[Console] = None, + console: Console | None = None, strip_styles: bool = False, ) -> str: """Render text with rich to string including ANSI codes, etc.. @@ -129,10 +126,10 @@ def render_to_string( def format_task_id( - task: "MetaTask", + task: MetaTask, editor_url_scheme: str, short_name: bool = False, - relative_to: Optional[Path] = None, + relative_to: Path | None = None, ) -> Text: """Format a task id.""" if short_name: @@ -187,7 +184,7 @@ def create_url_style_for_path(path: Path, edtior_url_scheme: str) -> Style: ) -def _get_file(function: Callable[..., Any], skipped_paths: List[Path] = None) -> Path: +def _get_file(function: Callable[..., Any], skipped_paths: list[Path] = None) -> Path: """Get path to module where the function is defined. When the ``pdb`` or ``trace`` mode is activated, every task function is wrapped with @@ -217,7 +214,7 @@ def _get_source_lines(function: Callable[..., Any]) -> int: return inspect.getsourcelines(function)[1] -def unify_styles(*styles: Union[str, Style]) -> Style: +def unify_styles(*styles: str | Style) -> Style: """Unify styles.""" parsed_styles = [] for style in styles: @@ -231,8 +228,8 @@ def unify_styles(*styles: Union[str, Style]) -> Style: def create_summary_panel( - counts: Dict[Enum, int], - outcome_enum: Union[Type["CollectionOutcome"], Type["TaskOutcome"]], + counts: dict[Enum, int], + outcome_enum: type[CollectionOutcome] | type[TaskOutcome], description_total: str, ) -> Panel: """Create a summary panel.""" diff --git a/src/_pytask/dag.py b/src/_pytask/dag.py index 875e71a4..fb2d9ff5 100644 --- a/src/_pytask/dag.py +++ b/src/_pytask/dag.py @@ -1,9 +1,10 @@ """Implement some capabilities to deal with the DAG.""" +from __future__ import annotations + import itertools from typing import Dict from typing import Generator from typing import Iterable -from typing import List from typing import Set import attr @@ -69,7 +70,7 @@ class TopologicalSorter: _nodes_out = attr.ib(factory=set, type=Set[str]) @classmethod - def from_dag(cls, dag: nx.DiGraph) -> "TopologicalSorter": + def from_dag(cls, dag: nx.DiGraph) -> TopologicalSorter: """Instantiate from a DAG.""" if not dag.is_directed(): raise ValueError("Only directed graphs have a topological order.") @@ -96,7 +97,7 @@ def prepare(self) -> None: self._is_prepared = True - def get_ready(self, n: int = 1) -> List[str]: + def get_ready(self, n: int = 1) -> list[str]: """Get up to ``n`` tasks which are ready.""" if not self._is_prepared: raise ValueError("The TopologicalSorter needs to be prepared.") @@ -136,7 +137,7 @@ def static_order(self) -> Generator[str, None, None]: self.done(new_task) -def _extract_priorities_from_tasks(tasks: List[MetaTask]) -> Dict[str, int]: +def _extract_priorities_from_tasks(tasks: list[MetaTask]) -> dict[str, int]: """Extract priorities from tasks. Priorities are set via the ``pytask.mark.try_first`` and ``pytask.mark.try_last`` diff --git a/src/_pytask/database.py b/src/_pytask/database.py index f1363e26..7eac39b5 100644 --- a/src/_pytask/database.py +++ b/src/_pytask/database.py @@ -1,7 +1,8 @@ """Implement the database managed with pony.""" +from __future__ import annotations + from pathlib import Path from typing import Any -from typing import Dict import click import networkx as nx @@ -91,9 +92,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse the configuration.""" config["database_provider"] = get_first_non_none_value( @@ -133,7 +134,7 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Post-parse the configuration.""" create_database(**config["database"]) diff --git a/src/_pytask/debugging.py b/src/_pytask/debugging.py index 564b647c..efdafdcb 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -1,16 +1,13 @@ """This module contains everything related to debugging.""" +from __future__ import annotations + import functools import pdb import sys from types import FrameType from types import TracebackType from typing import Any -from typing import Dict from typing import Generator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING import click @@ -62,9 +59,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse the configuration.""" config["pdb"] = get_first_non_none_value( @@ -97,7 +94,7 @@ def pytask_parse_config( ) -def _pdbcls_callback(x: Optional[str]) -> Optional[Tuple[str, str]]: +def _pdbcls_callback(x: str | None) -> tuple[str, str] | None: """Validate the debugger class string passed to pdbcls.""" message = "'pdbcls' must be like IPython.terminal.debugger:TerminalPdb" @@ -114,7 +111,7 @@ def _pdbcls_callback(x: Optional[str]) -> Optional[Tuple[str, str]]: @hookimpl(trylast=True) -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Post parse the configuration. Register the plugins in this step to let other plugins influence the pdb or trace @@ -149,22 +146,22 @@ def pytask_unconfigure() -> None: class PytaskPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: Optional[pluggy.PluginManager] = None - _config: Optional[Dict[str, Any]] = None - _saved: List[Tuple[Any, ...]] = [] + _pluginmanager: pluggy.PluginManager | None = None + _config: dict[str, Any] | None = None + _saved: list[tuple[Any, ...]] = [] _recursive_debug: int = 0 - _wrapped_pdb_cls: Optional[Tuple[Type[pdb.Pdb], Type[pdb.Pdb]]] = None + _wrapped_pdb_cls: tuple[type[pdb.Pdb], type[pdb.Pdb]] | None = None @classmethod - def _is_capturing(cls, capman: "CaptureManager") -> bool: + def _is_capturing(cls, capman: CaptureManager) -> bool: if capman: return capman.is_capturing() return False @classmethod def _import_pdb_cls( - cls, capman: "CaptureManager", live_manager: "LiveManager" - ) -> Type[pdb.Pdb]: + cls, capman: CaptureManager, live_manager: LiveManager + ) -> type[pdb.Pdb]: if not cls._config: import pdb @@ -205,10 +202,10 @@ def _import_pdb_cls( @classmethod def _get_pdb_wrapper_class( cls, - pdb_cls: Type[pdb.Pdb], - capman: "CaptureManager", - live_manager: "LiveManager", - ) -> Type[pdb.Pdb]: + pdb_cls: type[pdb.Pdb], + capman: CaptureManager, + live_manager: LiveManager, + ) -> type[pdb.Pdb]: # Type ignored because mypy doesn't support "dynamic" # inheritance like this. class PytaskPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] @@ -284,7 +281,7 @@ def setup(self, f, tb): # type: ignore self._pytask_live_manager.pause() return ret - def get_stack(self, f: FrameType, t: TracebackType) -> Tuple[str, int]: + def get_stack(self, f: FrameType, t: TracebackType) -> tuple[str, int]: stack, i = super().get_stack(f, t) if f is None: # Find last non-hidden frame. diff --git a/src/_pytask/exceptions.py b/src/_pytask/exceptions.py index ed07020d..a3df8c22 100644 --- a/src/_pytask/exceptions.py +++ b/src/_pytask/exceptions.py @@ -1,4 +1,5 @@ """This module contains custom exceptions.""" +from __future__ import annotations class PytaskError(Exception): diff --git a/src/_pytask/execute.py b/src/_pytask/execute.py index 75db350a..c459a9df 100644 --- a/src/_pytask/execute.py +++ b/src/_pytask/execute.py @@ -1,9 +1,9 @@ """This module contains hook implementations concerning the execution.""" +from __future__ import annotations + import sys import time from typing import Any -from typing import Dict -from typing import List from _pytask.config import hookimpl from _pytask.console import console @@ -33,9 +33,9 @@ @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse the configuration.""" config["show_errors_immediately"] = get_first_non_none_value( @@ -48,7 +48,7 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Adjust the configuration after intermediate values have been parsed.""" if config["show_errors_immediately"]: config["pm"].register(ShowErrorsImmediatelyPlugin) @@ -222,7 +222,7 @@ def pytask_execute_task_log_end(session: Session, report: ExecutionReport) -> No @hookimpl -def pytask_execute_log_end(session: Session, reports: List[ExecutionReport]) -> bool: +def pytask_execute_log_end(session: Session, reports: list[ExecutionReport]) -> bool: """Log information on the execution.""" session.execution_end = time.time() diff --git a/src/_pytask/graph.py b/src/_pytask/graph.py index 65cacb79..0ae6e993 100644 --- a/src/_pytask/graph.py +++ b/src/_pytask/graph.py @@ -1,10 +1,9 @@ """This file contains the command and code for drawing the DAG.""" +from __future__ import annotations + import sys from pathlib import Path from typing import Any -from typing import Dict -from typing import List -from typing import Optional from typing import TYPE_CHECKING import click @@ -45,9 +44,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse configuration.""" config["output_path"] = get_first_non_none_value( @@ -73,8 +72,8 @@ def pytask_parse_config( def _rank_direction_callback( - x: Optional["_RankDirection"], -) -> Optional["_RankDirection"]: + x: _RankDirection | None, +) -> _RankDirection | None: """Validate the passed options for rank direction.""" if x in [None, "None", "none"]: x = None @@ -113,7 +112,7 @@ def _rank_direction_callback( type=click.Choice(["TB", "LR", "BT", "RL"]), help=_HELP_TEXT_RANK_DIRECTION, ) -def dag(**config_from_cli: Any) -> "NoReturn": +def dag(**config_from_cli: Any) -> NoReturn: """Create a visualization of the project's DAG.""" try: pm = get_plugin_manager() @@ -161,7 +160,7 @@ def dag(**config_from_cli: Any) -> "NoReturn": sys.exit(session.exit_code) -def build_dag(config_from_cli: Dict[str, Any]) -> nx.DiGraph: +def build_dag(config_from_cli: dict[str, Any]) -> nx.DiGraph: """Build the DAG. This function is the programmatic interface to ``pytask dag`` and returns a @@ -229,7 +228,7 @@ def _refine_dag(session: Session) -> nx.DiGraph: return dag -def _create_session(config_from_cli: Dict[str, Any]) -> nx.DiGraph: +def _create_session(config_from_cli: dict[str, Any]) -> nx.DiGraph: """Create a session object.""" try: pm = get_plugin_manager() @@ -269,7 +268,7 @@ def _create_session(config_from_cli: Dict[str, Any]) -> nx.DiGraph: return session -def _shorten_node_labels(dag: nx.DiGraph, paths: List[Path]) -> nx.DiGraph: +def _shorten_node_labels(dag: nx.DiGraph, paths: list[Path]) -> nx.DiGraph: """Shorten the node labels in the graph for a better experience.""" node_names = dag.nodes short_names = reduce_names_of_multiple_nodes(node_names, dag, paths) diff --git a/src/_pytask/hookspecs.py b/src/_pytask/hookspecs.py index 6982ef47..1df1fe12 100644 --- a/src/_pytask/hookspecs.py +++ b/src/_pytask/hookspecs.py @@ -4,15 +4,12 @@ the message send by the host and may send a response. """ +from __future__ import annotations + from pathlib import Path from typing import Any from typing import Callable -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import click import networkx as nx @@ -69,8 +66,8 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookspec(firstresult=True) def pytask_configure( - pm: pluggy.PluginManager, config_from_cli: Dict[str, Any] -) -> Dict[str, Any]: + pm: pluggy.PluginManager, config_from_cli: dict[str, Any] +) -> dict[str, Any]: """Configure pytask. The main hook implementation which controls the configuration and calls subordinated @@ -81,9 +78,9 @@ def pytask_configure( @hookspec def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse configuration from the CLI or from file. @@ -97,7 +94,7 @@ def pytask_parse_config( @hookspec -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Post parsing. This hook allows to consolidate the configuration in case some plugins might be @@ -109,7 +106,7 @@ def pytask_post_parse(config: Dict[str, Any]) -> None: @hookspec -def pytask_unconfigure(session: "Session") -> None: +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` @@ -122,7 +119,7 @@ def pytask_unconfigure(session: "Session") -> None: @hookspec(firstresult=True) -def pytask_collect(session: "Session") -> Any: +def pytask_collect(session: Session) -> Any: """Collect tasks from paths. The main hook implementation which controls the collection and calls subordinated @@ -132,7 +129,7 @@ def pytask_collect(session: "Session") -> Any: @hookspec(firstresult=True) -def pytask_ignore_collect(path: Path, config: Dict[str, Any]) -> bool: +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. @@ -142,7 +139,7 @@ def pytask_ignore_collect(path: Path, config: Dict[str, Any]) -> bool: @hookspec -def pytask_collect_modify_tasks(session: "Session", tasks: "List[MetaTask]") -> None: +def pytask_collect_modify_tasks(session: Session, tasks: list[MetaTask]) -> None: """Modify tasks after they have been collected. This hook can be used to deselect tasks when they match a certain keyword or mark. @@ -152,8 +149,8 @@ def pytask_collect_modify_tasks(session: "Session", tasks: "List[MetaTask]") -> @hookspec(firstresult=True) def pytask_collect_file_protocol( - session: "Session", path: Path, reports: "List[CollectionReport]" -) -> "List[CollectionReport]": + 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 @@ -164,8 +161,8 @@ def pytask_collect_file_protocol( @hookspec(firstresult=True) def pytask_collect_file( - session: "Session", path: Path, reports: "List[CollectionReport]" -) -> "Optional[List[CollectionReport]]": + 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. @@ -174,35 +171,31 @@ def pytask_collect_file( @hookspec -def pytask_collect_file_log( - session: "Session", reports: "List[CollectionReport]" -) -> None: +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, name: str, obj: Any -) -> "Optional[CollectionReport]": + session: Session, path: Path, name: str, obj: Any +) -> CollectionReport | None: """Start protocol to collect tasks.""" @hookspec def pytask_collect_task_setup( - session: "Session", path: Path, name: str, obj: Any + session: Session, path: Path, name: str, obj: Any ) -> None: """Steps before collecting a task.""" @hookspec(firstresult=True) -def pytask_collect_task( - session: "Session", path: Path, name: str, obj: Any -) -> "MetaTask": +def pytask_collect_task(session: Session, path: Path, name: str, obj: Any) -> MetaTask: """Collect a single task.""" @hookspec -def pytask_collect_task_teardown(session: "Session", task: "MetaTask") -> None: +def pytask_collect_task_teardown(session: Session, task: MetaTask) -> None: """Perform tear-down operations when a task was collected. Use this hook specification to, for example, perform checks on the collected task. @@ -212,14 +205,14 @@ def pytask_collect_task_teardown(session: "Session", task: "MetaTask") -> None: @hookspec(firstresult=True) def pytask_collect_node( - session: "Session", path: Path, node: "MetaNode" -) -> "Optional[MetaNode]": + session: Session, path: Path, node: MetaNode +) -> MetaNode | 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[MetaTask]" + session: Session, reports: list[CollectionReport], tasks: list[MetaTask] ) -> None: """Log errors occurring during the collection. @@ -233,13 +226,13 @@ def pytask_collect_log( @hookspec(firstresult=True) def pytask_parametrize_task( - session: "Session", name: str, obj: Any -) -> List[Tuple[str, Callable[..., Any]]]: + session: Session, name: str, obj: Any +) -> list[tuple[str, Callable[..., Any]]]: """Generate multiple tasks from name and object with parametrization.""" @hookspec -def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: Dict[Any, Any]) -> None: +def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: dict[Any, Any]) -> None: """Add some keyword arguments as markers to object. This hook moves arguments defined in the parametrization to marks of the same @@ -253,7 +246,7 @@ def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: Dict[Any, Any]) -> None @hookspec(firstresult=True) -def pytask_resolve_dependencies(session: "Session") -> None: +def pytask_resolve_dependencies(session: Session) -> None: """Resolve dependencies. The main hook implementation which controls the resolution of dependencies and calls @@ -264,7 +257,7 @@ def pytask_resolve_dependencies(session: "Session") -> None: @hookspec(firstresult=True) def pytask_resolve_dependencies_create_dag( - session: "Session", tasks: "List[MetaTask]" + session: Session, tasks: list[MetaTask] ) -> nx.DiGraph: """Create the DAG. @@ -275,7 +268,7 @@ def pytask_resolve_dependencies_create_dag( @hookspec -def pytask_resolve_dependencies_modify_dag(session: "Session", dag: nx.DiGraph) -> None: +def pytask_resolve_dependencies_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 @@ -285,9 +278,7 @@ def pytask_resolve_dependencies_modify_dag(session: "Session", dag: nx.DiGraph) @hookspec(firstresult=True) -def pytask_resolve_dependencies_validate_dag( - session: "Session", dag: nx.DiGraph -) -> None: +def pytask_resolve_dependencies_validate_dag(session: Session, dag: nx.DiGraph) -> None: """Validate the DAG. This hook validates the DAG. For example, there can be cycles in the DAG if tasks, @@ -298,7 +289,7 @@ def pytask_resolve_dependencies_validate_dag( @hookspec def pytask_resolve_dependencies_select_execution_dag( - session: "Session", dag: nx.DiGraph + session: Session, dag: nx.DiGraph ) -> None: """Select the subgraph which needs to be executed. @@ -310,7 +301,7 @@ def pytask_resolve_dependencies_select_execution_dag( @hookspec def pytask_resolve_dependencies_log( - session: "Session", report: "ResolveDependencyReport" + session: Session, report: ResolveDependencyReport ) -> None: """Log errors during resolving dependencies.""" @@ -319,7 +310,7 @@ def pytask_resolve_dependencies_log( @hookspec(firstresult=True) -def pytask_execute(session: "Session") -> Optional[Any]: +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 @@ -329,7 +320,7 @@ def pytask_execute(session: "Session") -> Optional[Any]: @hookspec -def pytask_execute_log_start(session: "Session") -> None: +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. @@ -338,7 +329,7 @@ def pytask_execute_log_start(session: "Session") -> None: @hookspec(firstresult=True) -def pytask_execute_create_scheduler(session: "Session") -> Any: +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 @@ -348,7 +339,7 @@ def pytask_execute_create_scheduler(session: "Session") -> Any: @hookspec(firstresult=True) -def pytask_execute_build(session: "Session") -> Any: +def pytask_execute_build(session: Session) -> Any: """Execute the build. This hook implements the main loop to execute tasks. @@ -357,9 +348,7 @@ def pytask_execute_build(session: "Session") -> Any: @hookspec(firstresult=True) -def pytask_execute_task_protocol( - session: "Session", task: "MetaTask" -) -> "ExecutionReport": +def pytask_execute_task_protocol(session: Session, task: MetaTask) -> ExecutionReport: """Run the protocol for executing a test. This hook runs all stages of the execution process, setup, execution, and teardown @@ -371,7 +360,7 @@ def pytask_execute_task_protocol( @hookspec(firstresult=True) -def pytask_execute_task_log_start(session: "Session", task: "MetaTask") -> None: +def pytask_execute_task_log_start(session: Session, task: MetaTask) -> None: """Start logging of task execution. This hook can be used to provide more verbose output during the execution. @@ -380,7 +369,7 @@ def pytask_execute_task_log_start(session: "Session", task: "MetaTask") -> None: @hookspec -def pytask_execute_task_setup(session: "Session", task: "MetaTask") -> None: +def pytask_execute_task_setup(session: Session, task: MetaTask) -> None: """Set up the task execution. This hook is called before the task is executed and can provide an entry-point to @@ -391,12 +380,12 @@ def pytask_execute_task_setup(session: "Session", task: "MetaTask") -> None: @hookspec(firstresult=True) -def pytask_execute_task(session: "Session", task: "MetaTask") -> Optional[Any]: +def pytask_execute_task(session: Session, task: MetaTask) -> Any | None: """Execute a task.""" @hookspec -def pytask_execute_task_teardown(session: "Session", task: "MetaTask") -> None: +def pytask_execute_task_teardown(session: Session, task: MetaTask) -> None: """Tear down task execution. This hook is executed after the task has been executed. It allows to perform @@ -407,8 +396,8 @@ def pytask_execute_task_teardown(session: "Session", task: "MetaTask") -> None: @hookspec(firstresult=True) def pytask_execute_task_process_report( - session: "Session", report: "ExecutionReport" -) -> Optional[Any]: + 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 @@ -421,14 +410,12 @@ def pytask_execute_task_process_report( @hookspec(firstresult=True) -def pytask_execute_task_log_end(session: "Session", report: "ExecutionReport") -> None: +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: +def pytask_execute_log_end(session: Session, reports: list[ExecutionReport]) -> None: """Log the footer of the execution report.""" @@ -436,15 +423,15 @@ def pytask_execute_log_end( @hookspec -def pytask_log_session_header(session: "Session") -> None: +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", + session: Session, duration: float, - outcome: Union["CollectionOutcome", "TaskOutcome"], + outcome: CollectionOutcome | TaskOutcome, ) -> None: """Log session information at the end of a run.""" @@ -454,7 +441,7 @@ def pytask_log_session_footer( @hookspec def pytask_profile_add_info_on_task( - session: "Session", tasks: "List[MetaTask]", profile: Dict[str, Dict[Any, Any]] + session: Session, tasks: list[MetaTask], profile: dict[str, dict[Any, Any]] ) -> None: """Add information on task for profile. @@ -467,6 +454,6 @@ def pytask_profile_add_info_on_task( @hookspec def pytask_profile_export_profile( - session: "Session", profile: Dict[str, Dict[Any, Any]] + session: Session, profile: dict[str, dict[Any, Any]] ) -> None: """Export the profile.""" diff --git a/src/_pytask/live.py b/src/_pytask/live.py index 14160843..33923ad9 100644 --- a/src/_pytask/live.py +++ b/src/_pytask/live.py @@ -1,10 +1,10 @@ """This module contains code related to live objects.""" +from __future__ import annotations + from typing import Any from typing import Dict from typing import Generator from typing import List -from typing import Optional -from typing import Union import attr import click @@ -39,9 +39,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse the configuration.""" config["n_entries_in_table"] = get_first_non_none_value( @@ -53,7 +53,7 @@ def pytask_parse_config( ) -def _parse_n_entries_in_table(value: Union[int, str, None]) -> int: +def _parse_n_entries_in_table(value: int | str | None) -> int: """Parse how many entries should be displayed in the table during the execution.""" if value in ["none", "None", None, ""]: out = None @@ -71,7 +71,7 @@ def _parse_n_entries_in_table(value: Union[int, str, None]) -> int: @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Post-parse the configuration.""" live_manager = LiveManager() config["pm"].register(live_manager, "live_manager") @@ -114,7 +114,7 @@ class LiveManager: def start(self) -> None: self._live.start() - def stop(self, transient: Optional[bool] = None) -> None: + def stop(self, transient: bool | None = None) -> None: if transient is not None: self._live.transient = transient self._live.stop() @@ -172,7 +172,7 @@ def pytask_execute_task_log_end(self, report: ExecutionReport) -> bool: self.update_reports(report) return True - def _generate_table(self, reduce_table: bool, sort_table: bool) -> Optional[Table]: + def _generate_table(self, reduce_table: bool, sort_table: bool) -> Table | None: """Generate the table. First, display all completed tasks and, then, all running tasks. @@ -272,7 +272,7 @@ def pytask_collect(self) -> Generator[None, None, None]: yield @hookimpl - def pytask_collect_file_log(self, reports: List[CollectionReport]) -> None: + def pytask_collect_file_log(self, reports: list[CollectionReport]) -> None: """Update the status after a file is collected.""" self._update_statistics(reports) self._update_status() @@ -283,7 +283,7 @@ def pytask_collect_log(self) -> Generator[None, None, None]: self._live_manager.stop(transient=True) yield - def _update_statistics(self, reports: List[CollectionReport]) -> None: + def _update_statistics(self, reports: list[CollectionReport]) -> None: """Update the statistics on collected tasks and errors.""" if reports is None: reports = [] diff --git a/src/_pytask/logging.py b/src/_pytask/logging.py index fdec9aa4..09e4d519 100644 --- a/src/_pytask/logging.py +++ b/src/_pytask/logging.py @@ -1,14 +1,11 @@ """Add general logging capabilities.""" +from __future__ import annotations + import platform import sys import warnings from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import _pytask import click @@ -60,9 +57,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_file: Dict[str, Any], - config_from_cli: Dict[str, Any], + config: dict[str, Any], + config_from_file: dict[str, Any], + config_from_cli: dict[str, Any], ) -> None: """Parse configuration.""" config["show_locals"] = get_first_non_none_value( @@ -96,8 +93,8 @@ def pytask_parse_config( def _show_traceback_callback( - x: Optional["_ShowTraceback"], -) -> Optional["_ShowTraceback"]: + x: _ShowTraceback | None, +) -> _ShowTraceback | None: """Validate the passed options for showing tracebacks.""" if x in [None, "None", "none"]: x = None @@ -129,10 +126,10 @@ def pytask_log_session_header(session: Session) -> None: def _format_plugin_names_and_versions( - plugininfo: List[Tuple[str, DistFacade]] -) -> List[str]: + plugininfo: list[tuple[str, DistFacade]] +) -> list[str]: """Format name and version of loaded plugins.""" - values: List[str] = [] + values: list[str] = [] for _, dist in plugininfo: # Gets us name and version! name = f"{dist.project_name}-{dist.version}" @@ -148,7 +145,7 @@ def _format_plugin_names_and_versions( @hookimpl def pytask_log_session_footer( duration: float, - outcome: Union[CollectionOutcome, TaskOutcome], + outcome: CollectionOutcome | TaskOutcome, ) -> None: """Format the footer of the log message.""" formatted_duration = _format_duration(duration) @@ -158,7 +155,7 @@ def pytask_log_session_footer( console.rule(message, style=outcome.style) -_TIME_UNITS: List["_TimeUnit"] = [ +_TIME_UNITS: list[_TimeUnit] = [ {"singular": "day", "plural": "days", "short": "d", "in_seconds": 86400}, {"singular": "hour", "plural": "hours", "short": "h", "in_seconds": 3600}, {"singular": "minute", "plural": "minutes", "short": "m", "in_seconds": 60}, @@ -181,8 +178,8 @@ def _format_duration(duration: float) -> str: def _humanize_time( - amount: Union[int, float], unit: str, short_label: bool = False -) -> List[Tuple[float, str]]: + amount: int | float, unit: str, short_label: bool = False +) -> list[tuple[float, str]]: """Humanize the time. Examples @@ -211,7 +208,7 @@ def _humanize_time( seconds = amount * _TIME_UNITS[index]["in_seconds"] - result: List[Tuple[float, str]] = [] + result: list[tuple[float, str]] = [] remaining_seconds = seconds for entry in _TIME_UNITS: whole_units = int(remaining_seconds / entry["in_seconds"]) diff --git a/src/_pytask/mark/__init__.py b/src/_pytask/mark/__init__.py index 8b3b5123..272656c1 100644 --- a/src/_pytask/mark/__init__.py +++ b/src/_pytask/mark/__init__.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import sys from typing import AbstractSet from typing import Any -from typing import Dict from typing import Set from typing import TYPE_CHECKING @@ -42,7 +43,7 @@ @click.command() -def markers(**config_from_cli: Any) -> "NoReturn": +def markers(**config_from_cli: Any) -> NoReturn: """Show all registered markers.""" config_from_cli["command"] = "markers" @@ -105,9 +106,9 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], - config_from_cli: Dict[str, Any], - config_from_file: Dict[str, Any], + config: dict[str, Any], + config_from_cli: dict[str, Any], + config_from_file: dict[str, Any], ) -> None: """Parse marker related options.""" markers = _read_marker_mapping_from_ini(config_from_file.get("markers", "")) @@ -127,7 +128,7 @@ def pytask_parse_config( MARK_GEN.config = config -def _read_marker_mapping_from_ini(string: str) -> Dict[str, str]: +def _read_marker_mapping_from_ini(string: str) -> dict[str, str]: """Read marker descriptions from configuration file.""" # Split by newlines and remove empty strings. lines = filter(lambda x: bool(x), string.split("\n")) @@ -166,7 +167,7 @@ class KeywordMatcher: _names = attr.ib(type=AbstractSet[str]) @classmethod - def from_task(cls, task: MetaTask) -> "KeywordMatcher": + def from_task(cls, task: MetaTask) -> KeywordMatcher: mapped_names = {task.name} # Add the names attached to the current function through direct assignment. @@ -189,7 +190,7 @@ def __call__(self, subname: str) -> bool: return False -def select_by_keyword(session: Session, dag: nx.DiGraph) -> Set[str]: +def select_by_keyword(session: Session, dag: nx.DiGraph) -> set[str]: """Deselect tests by keywords.""" keywordexpr = session.config["expression"] if not keywordexpr: @@ -202,7 +203,7 @@ def select_by_keyword(session: Session, dag: nx.DiGraph) -> Set[str]: f"Wrong expression passed to '-k': {keywordexpr}: {e}" ) from None - remaining: Set[str] = set() + remaining: set[str] = set() for task in session.tasks: if keywordexpr and expression.evaluate(KeywordMatcher.from_task(task)): remaining.update(task_and_preceding_tasks(task.name, dag)) @@ -221,7 +222,7 @@ class MarkMatcher: own_mark_names = attr.ib(type=Set[str]) @classmethod - def from_task(cls, task: MetaTask) -> "MarkMatcher": + def from_task(cls, task: MetaTask) -> MarkMatcher: mark_names = {mark.name for mark in task.markers} return cls(mark_names) @@ -229,7 +230,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]: """Deselect tests by marks.""" matchexpr = session.config["marker_expression"] if not matchexpr: @@ -240,7 +241,7 @@ def select_by_mark(session: Session, dag: nx.DiGraph) -> Set[str]: except ParseError as e: raise ValueError(f"Wrong expression passed to '-m': {matchexpr}: {e}") from None - remaining: Set[str] = set() + remaining: set[str] = set() for task in session.tasks: if expression.evaluate(MarkMatcher.from_task(task)): remaining.update(task_and_preceding_tasks(task.name, dag)) @@ -249,7 +250,7 @@ def select_by_mark(session: Session, dag: nx.DiGraph) -> Set[str]: def _deselect_others_with_mark( - session: Session, remaining: Set[str], mark: Mark + session: Session, remaining: set[str], mark: Mark ) -> None: for task in session.tasks: if task.name not in remaining: diff --git a/src/_pytask/mark/expression.py b/src/_pytask/mark/expression.py index 72b60f43..192666c1 100644 --- a/src/_pytask/mark/expression.py +++ b/src/_pytask/mark/expression.py @@ -21,6 +21,8 @@ - or/and/not evaluate according to the usual boolean semantics. """ +from __future__ import annotations + import ast import enum import re @@ -28,7 +30,6 @@ from typing import Callable from typing import Iterator from typing import Mapping -from typing import Optional from typing import Sequence from typing import TYPE_CHECKING @@ -117,7 +118,7 @@ def lex(self, input_: str) -> Iterator[Token]: ) yield Token(TokenType.EOF, "", pos) - def accept(self, type_: TokenType, *, reject: bool = False) -> Optional[Token]: + def accept(self, type_: TokenType, *, reject: bool = False) -> Token | None: if self.current.type_ is type_: token = self.current if token.type_ is not TokenType.EOF: @@ -127,7 +128,7 @@ def accept(self, type_: TokenType, *, reject: bool = False) -> Optional[Token]: self.reject((type_,)) return None - def reject(self, expected: Sequence[TokenType]) -> "NoReturn": + def reject(self, expected: Sequence[TokenType]) -> NoReturn: raise ParseError( self.current.pos + 1, "expected {}; got {}".format( @@ -168,7 +169,7 @@ def and_expr(s: Scanner) -> ast.expr: return ret -def not_expr(s: Scanner) -> Optional[ast.expr]: +def not_expr(s: Scanner) -> ast.expr | None: if s.accept(TokenType.NOT): return ast.UnaryOp(ast.Not(), not_expr(s)) if s.accept(TokenType.LPAREN): @@ -210,7 +211,7 @@ def __init__(self, code: types.CodeType) -> None: self.code = code @classmethod - def compile_(cls, input_: str) -> "Expression": + def compile_(cls, input_: str) -> Expression: """Compile a match expression. Parameters diff --git a/src/_pytask/mark/structures.py b/src/_pytask/mark/structures.py index 7ca30e67..0e1daa51 100644 --- a/src/_pytask/mark/structures.py +++ b/src/_pytask/mark/structures.py @@ -1,15 +1,13 @@ +from __future__ import annotations + import warnings from typing import Any from typing import Callable -from typing import Dict from typing import Iterable -from typing import List from typing import Mapping from typing import Optional from typing import Sequence -from typing import Set from typing import Tuple -from typing import Union import attr @@ -37,7 +35,7 @@ class Mark: def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: "Mark") -> "Mark": + def combined_with(self, other: Mark) -> Mark: """Return a new Mark which is a combination of this Mark and another Mark. Combines by appending args and merging kwargs. @@ -56,7 +54,7 @@ def combined_with(self, other: "Mark") -> "Mark": assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None + param_ids_from: Mark | None = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -114,7 +112,7 @@ def name(self) -> str: return self.mark.name @property - def args(self) -> Tuple[Any, ...]: + def args(self) -> tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -126,7 +124,7 @@ def kwargs(self) -> Mapping[str, Any]: def __repr__(self) -> str: return f"" - def with_args(self, *args: Any, **kwargs: Any) -> "MarkDecorator": + def with_args(self, *args: Any, **kwargs: Any) -> MarkDecorator: """Return a MarkDecorator with extra arguments added. Unlike calling the MarkDecorator, ``with_args()`` can be used even if the sole @@ -136,7 +134,7 @@ def with_args(self, *args: Any, **kwargs: Any) -> "MarkDecorator": mark = Mark(self.name, args, kwargs) return self.__class__(self.mark.combined_with(mark)) - def __call__(self, *args: Any, **kwargs: Any) -> "MarkDecorator": + def __call__(self, *args: Any, **kwargs: Any) -> MarkDecorator: """Call the MarkDecorator.""" if args and not kwargs: func = args[0] @@ -146,7 +144,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> "MarkDecorator": return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj: Callable[..., Any]) -> List[Mark]: +def get_unpacked_marks(obj: Callable[..., Any]) -> list[Mark]: """Obtain the unpacked marks that are stored on an object.""" mark_list = getattr(obj, "pytaskmark", []) if not isinstance(mark_list, list): @@ -154,7 +152,7 @@ def get_unpacked_marks(obj: Callable[..., Any]) -> List[Mark]: return normalize_mark_list(mark_list) -def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]: +def normalize_mark_list(mark_list: Iterable[Mark | MarkDecorator]) -> list[Mark]: """Normalize marker decorating helpers to mark objects. Parameters @@ -201,9 +199,9 @@ class MarkGenerator: """ - config: Optional[Dict[str, Any]] = None + config: dict[str, Any] | None = None """Optional[Dict[str, Any]]: The configuration.""" - markers: Set[str] = set() + markers: set[str] = set() """Set[str]: The set of markers.""" def __getattr__(self, name: str) -> MarkDecorator: diff --git a/src/_pytask/mark_utils.py b/src/_pytask/mark_utils.py index 28f330a1..d0c684b6 100644 --- a/src/_pytask/mark_utils.py +++ b/src/_pytask/mark_utils.py @@ -3,8 +3,9 @@ The utility functions are stored here to be separate from the plugin. """ +from __future__ import annotations + from typing import Any -from typing import List from typing import TYPE_CHECKING @@ -13,12 +14,12 @@ from _pytask.mark import Mark -def get_specific_markers_from_task(task: "MetaTask", marker_name: str) -> "List[Mark]": +def get_specific_markers_from_task(task: MetaTask, marker_name: str) -> list[Mark]: """Get a specific group of markers from a task.""" return [marker for marker in task.markers if marker.name == marker_name] -def get_marks_from_obj(obj: Any, marker_name: str) -> "List[Mark]": +def get_marks_from_obj(obj: Any, marker_name: str) -> list[Mark]: """Get a specific group of markers from a task function.""" return [ marker diff --git a/src/_pytask/nodes.py b/src/_pytask/nodes.py index e3ed5a69..edbddfec 100644 --- a/src/_pytask/nodes.py +++ b/src/_pytask/nodes.py @@ -1,4 +1,6 @@ """Deals with nodes which are dependencies or products of a task.""" +from __future__ import annotations + import functools import inspect import itertools @@ -12,10 +14,8 @@ from typing import Iterable from typing import List from typing import Optional -from typing import Set from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import attr from _pytask.exceptions import NodeNotCollectedError @@ -29,8 +29,8 @@ def depends_on( - objects: Union[Any, Iterable[Any], Dict[Any, Any]] -) -> Union[Any, Iterable[Any], Dict[Any, Any]]: + objects: Any | Iterable[Any] | dict[Any, Any] +) -> Any | Iterable[Any] | dict[Any, Any]: """Specify dependencies for a task. Parameters @@ -45,8 +45,8 @@ def depends_on( def produces( - objects: Union[Any, Iterable[Any], Dict[Any, Any]] -) -> Union[Any, Iterable[Any], Dict[Any, Any]]: + objects: Any | Iterable[Any] | dict[Any, Any] +) -> Any | Iterable[Any] | dict[Any, Any]: """Specify products of a task. Parameters @@ -67,7 +67,7 @@ class MetaNode(metaclass=ABCMeta): path: Path @abstractmethod - def state(self) -> Optional[str]: + def state(self) -> str | None: ... @@ -76,14 +76,14 @@ class MetaTask(MetaNode): base_name: str name: str - short_name: Optional[str] - markers: List["Mark"] - depends_on: Dict[str, MetaNode] - produces: Dict[str, MetaNode] + short_name: str | None + markers: list[Mark] + depends_on: dict[str, MetaNode] + produces: dict[str, MetaNode] path: Path - function: Optional[Callable[..., Any]] - attributes: Dict[Any, Any] - _report_sections: List[Tuple[str, str, str]] + function: Callable[..., Any] | None + attributes: dict[Any, Any] + _report_sections: list[tuple[str, str, str]] @abstractmethod def execute(self) -> None: @@ -123,14 +123,14 @@ class PythonFunctionTask(MetaTask): attributes = attr.ib(factory=dict, type=Dict[Any, Any]) """Dict[Any, Any]: A dictionary to store additional information of the task.""" - def __attrs_post_init__(self: "PythonFunctionTask") -> None: + def __attrs_post_init__(self: PythonFunctionTask) -> None: if self.short_name is None: self.short_name = self.name @classmethod def from_path_name_function_session( cls, path: Path, name: str, function: Callable[..., Any], session: Session - ) -> "PythonFunctionTask": + ) -> PythonFunctionTask: """Create a task from a path, name, function, and session.""" keep_dictionary = {} @@ -170,7 +170,7 @@ def state(self) -> str: """Return the last modified date of the file where the task is defined.""" return str(self.path.stat().st_mtime) - def _get_kwargs_from_task_for_function(self) -> Dict[str, Any]: + def _get_kwargs_from_task_for_function(self) -> dict[str, Any]: """Process dependencies and products to pass them as kwargs to the function.""" func_arg_names = set(inspect.signature(self.function).parameters) kwargs = {} @@ -207,7 +207,7 @@ class FilePathNode(MetaNode): @classmethod @functools.lru_cache() - def from_path(cls, path: Path) -> "FilePathNode": + def from_path(cls, path: Path) -> FilePathNode: """Instantiate class from path to file. The `lru_cache` decorator ensures that the same object is not collected twice. @@ -217,7 +217,7 @@ def from_path(cls, path: Path) -> "FilePathNode": raise ValueError("FilePathNode must be instantiated from absolute path.") return cls(path.as_posix(), path, path) - def state(self) -> Optional[str]: + def state(self) -> str | None: """Return the last modified date for file path.""" if not self.path.exists(): raise NodeNotFoundError @@ -226,8 +226,8 @@ def state(self) -> Optional[str]: def _collect_nodes( - session: Session, path: Path, name: str, nodes: Dict[str, Union[str, Path]] -) -> Dict[str, MetaNode]: + session: Session, path: Path, name: str, nodes: dict[str, str | Path] +) -> dict[str, MetaNode]: """Collect nodes for a task. Parameters @@ -287,7 +287,7 @@ def _extract_nodes_from_function_markers( def _convert_objects_to_node_dictionary( objects: Any, when: str -) -> Tuple[Dict[Any, Any], bool]: +) -> tuple[dict[Any, Any], bool]: """Convert objects to node dictionary.""" list_of_tuples, keep_dict = _convert_objects_to_list_of_tuples(objects, when) _check_that_names_are_not_used_multiple_times(list_of_tuples, when) @@ -296,8 +296,8 @@ def _convert_objects_to_node_dictionary( def _convert_objects_to_list_of_tuples( - objects: Union[Any, Tuple[Any, Any], List[Any], List[Tuple[Any, Any]]], when: str -) -> Tuple[List[Tuple[Any, ...]], bool]: + objects: Any | tuple[Any, Any] | list[Any] | list[tuple[Any, Any]], when: str +) -> tuple[list[tuple[Any, ...]], bool]: """Convert objects to list of tuples. Examples @@ -339,7 +339,7 @@ def _convert_objects_to_list_of_tuples( def _check_that_names_are_not_used_multiple_times( - list_of_tuples: List[Tuple[Any, ...]], when: str + list_of_tuples: list[tuple[Any, ...]], when: str ) -> None: """Check that names of nodes are not assigned multiple times. @@ -368,8 +368,8 @@ def _check_that_names_are_not_used_multiple_times( def _convert_nodes_to_dictionary( - list_of_tuples: List[Tuple[Any, ...]] -) -> Dict[Any, Any]: + list_of_tuples: list[tuple[Any, ...]] +) -> dict[Any, Any]: """Convert nodes to dictionaries. Examples @@ -413,7 +413,7 @@ def create_task_name(path: Path, base_name: str) -> str: return path.as_posix() + "::" + base_name -def find_duplicates(x: Iterable[Any]) -> Set[Any]: +def find_duplicates(x: Iterable[Any]) -> set[Any]: """Find duplicated entries in iterable. Examples diff --git a/src/_pytask/outcomes.py b/src/_pytask/outcomes.py index 394fa05d..839cea11 100644 --- a/src/_pytask/outcomes.py +++ b/src/_pytask/outcomes.py @@ -1,13 +1,11 @@ """This module contains code related to outcomes.""" +from __future__ import annotations + from enum import auto from enum import Enum from enum import IntEnum -from typing import Dict -from typing import Optional from typing import Sequence -from typing import Type from typing import TYPE_CHECKING -from typing import Union if TYPE_CHECKING: @@ -156,9 +154,9 @@ def style_textonly(self) -> str: def count_outcomes( - reports: Sequence[Union["CollectionReport", "ExecutionReport"]], - outcome_enum: Union[Type[CollectionOutcome], Type[TaskOutcome]], -) -> Dict[Enum, int]: + reports: Sequence[CollectionReport | ExecutionReport], + outcome_enum: type[CollectionOutcome] | type[TaskOutcome], +) -> dict[Enum, int]: """Count how often an outcome occurred. Examples @@ -216,7 +214,7 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: Optional[int] = None + self, msg: str = "unknown reason", returncode: int | None = None ) -> None: self.msg = msg self.returncode = returncode diff --git a/src/_pytask/parameters.py b/src/_pytask/parameters.py index 938b042e..65b01088 100644 --- a/src/_pytask/parameters.py +++ b/src/_pytask/parameters.py @@ -1,4 +1,6 @@ """Contains common parameters for the commands of the command line interface.""" +from __future__ import annotations + import click from _pytask.config import hookimpl from _pytask.shared import falsy_to_none_callback diff --git a/src/_pytask/parametrize.py b/src/_pytask/parametrize.py index 31b8021e..3d5c7e26 100644 --- a/src/_pytask/parametrize.py +++ b/src/_pytask/parametrize.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import copy import functools import itertools @@ -5,13 +7,8 @@ import types from typing import Any from typing import Callable -from typing import Dict from typing import Iterable -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union from _pytask.config import hookimpl from _pytask.console import format_strings_as_flat_tree @@ -23,16 +20,14 @@ def parametrize( - arg_names: Union[str, List[str], Tuple[str, ...]], - arg_values: Iterable[Union[Sequence[Any], Any]], + arg_names: str | list[str] | tuple[str, ...], + arg_values: Iterable[Sequence[Any] | Any], *, - ids: Optional[ - Union[Iterable[Union[None, str, float, int, bool]], Callable[..., Any]] - ] = None, -) -> Tuple[ - Union[str, List[str], Tuple[str, ...]], - Iterable[Union[Sequence[Any], Any]], - Optional[Union[Iterable[Union[None, str, float, int, bool]], Callable[..., Any]]], + ids: None | (Iterable[None | str | float | int | bool] | Callable[..., Any]) = None, +) -> tuple[ + str | list[str] | tuple[str, ...], + Iterable[Sequence[Any] | Any], + Iterable[None | str | float | int | bool] | Callable[..., Any] | None, ]: """Parametrize a task function. @@ -63,7 +58,7 @@ def parametrize( @hookimpl -def pytask_parse_config(config: Dict[str, Any]) -> None: +def pytask_parse_config(config: dict[str, Any]) -> None: config["markers"]["parametrize"] = ( "Call a task function multiple times passing in different arguments each turn. " "arg_values generally needs to be a list of values if arg_names specifies only " @@ -78,7 +73,7 @@ def pytask_parse_config(config: Dict[str, Any]) -> None: @hookimpl def pytask_parametrize_task( session: Session, name: str, obj: Callable[..., Any] -) -> List[Tuple[str, Callable[..., Any]]]: +) -> list[tuple[str, Callable[..., Any]]]: """Parametrize a task. This function takes a single Python function and all parametrize decorators and @@ -108,7 +103,7 @@ def pytask_parametrize_task( product_arg_names = list(itertools.product(*arg_names)) product_arg_values = list(itertools.product(*arg_values)) - names_and_functions: List[Tuple[str, Callable[..., Any]]] = [] + names_and_functions: list[tuple[str, Callable[..., Any]]] = [] for names, values in zip(product_arg_names, product_arg_values): kwargs = dict( zip( @@ -148,7 +143,7 @@ def pytask_parametrize_task( return names_and_functions -def _remove_parametrize_markers_from_func(obj: Any) -> Tuple[Any, List[Mark]]: +def _remove_parametrize_markers_from_func(obj: Any) -> tuple[Any, list[Mark]]: """Remove parametrize markers from the object.""" parametrize_markers = [i for i in obj.pytaskmark if i.name == "parametrize"] others = [i for i in obj.pytaskmark if i.name != "parametrize"] @@ -159,7 +154,7 @@ def _remove_parametrize_markers_from_func(obj: Any) -> Tuple[Any, List[Mark]]: def _parse_parametrize_marker( marker: Mark, name: str -) -> Tuple[Tuple[str, ...], List[Tuple[str, ...]], List[Tuple[Any, ...]]]: +) -> tuple[tuple[str, ...], list[tuple[str, ...]], list[tuple[Any, ...]]]: """Parse parametrize marker. Parameters @@ -198,11 +193,11 @@ def _parse_parametrize_marker( def _parse_parametrize_markers( - markers: List[Mark], name: str -) -> Tuple[ - List[Tuple[str, ...]], - List[List[Tuple[str, ...]]], - List[List[Tuple[Any, ...]]], + markers: list[Mark], name: str +) -> tuple[ + list[tuple[str, ...]], + list[list[tuple[str, ...]]], + list[list[tuple[Any, ...]]], ]: """Parse parametrize markers.""" parsed_markers = [_parse_parametrize_marker(marker, name) for marker in markers] @@ -213,9 +208,7 @@ def _parse_parametrize_markers( return base_arg_names, processed_arg_names, processed_arg_values -def _parse_arg_names( - arg_names: Union[str, List[str], Tuple[str, ...]] -) -> Tuple[str, ...]: +def _parse_arg_names(arg_names: str | list[str] | tuple[str, ...]) -> tuple[str, ...]: """Parse arg_names argument of parametrize decorator. There are three allowed formats: @@ -258,8 +251,8 @@ def _parse_arg_names( def _parse_arg_values( - arg_values: Iterable[Union[Sequence[Any], Any]] -) -> List[Tuple[Any, ...]]: + arg_values: Iterable[Sequence[Any] | Any], +) -> list[tuple[Any, ...]]: """Parse the values provided for each argument name. After processing the values, the return is a list where each value is an iteration @@ -280,7 +273,7 @@ def _parse_arg_values( def _check_if_n_arg_names_matches_n_arg_values( - arg_names: Tuple[str, ...], arg_values: List[Tuple[Any, ...]], name: str + arg_names: tuple[str, ...], arg_values: list[tuple[Any, ...]], name: str ) -> None: """Check if the number of argument names matches the number of arguments.""" n_names = len(arg_names) @@ -304,12 +297,10 @@ def _check_if_n_arg_names_matches_n_arg_values( def _create_parametrize_ids_components( - arg_names: Tuple[str, ...], - arg_values: List[Tuple[Any, ...]], - ids: Optional[ - Union[Iterable[Union[None, str, float, int, bool]], Callable[..., Any]] - ], -) -> List[Tuple[str, ...]]: + arg_names: tuple[str, ...], + arg_values: list[tuple[Any, ...]], + ids: None | (Iterable[None | str | float | int | bool] | Callable[..., Any]), +) -> list[tuple[str, ...]]: """Create the ids for each parametrization. Parameters @@ -345,7 +336,7 @@ def _create_parametrize_ids_components( "None." ) - parsed_ids: List[Tuple[str, ...]] = [ + parsed_ids: list[tuple[str, ...]] = [ (str(id_),) for id_ in itertools.chain.from_iterable(raw_ids) ] @@ -362,7 +353,7 @@ def _create_parametrize_ids_components( def _arg_value_to_id_component( - arg_name: str, arg_value: Any, i: int, id_func: Union[Callable[..., Any], None] + arg_name: str, arg_value: Any, i: int, id_func: Callable[..., Any] | None ) -> str: """Create id component from the name and value of the argument. @@ -401,7 +392,7 @@ def _arg_value_to_id_component( @hookimpl -def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: Dict[str, str]) -> None: +def pytask_parametrize_kwarg_to_marker(obj: Any, kwargs: dict[str, str]) -> None: """Add some parametrized keyword arguments as decorator.""" if callable(obj): for marker_name in ["depends_on", "produces"]: diff --git a/src/_pytask/path.py b/src/_pytask/path.py index e5a43bfb..2c8d894b 100644 --- a/src/_pytask/path.py +++ b/src/_pytask/path.py @@ -1,13 +1,14 @@ """This module contains code to handle paths.""" +from __future__ import annotations + import functools import os from pathlib import Path from typing import Sequence -from typing import Union def relative_to( - path: Union[str, Path], source: Union[str, Path], include_source: bool = True + path: str | Path, source: str | Path, include_source: bool = True ) -> Path: """Make a path relative to another path. @@ -34,7 +35,7 @@ def relative_to( def find_closest_ancestor( - path: Union[str, Path], potential_ancestors: Sequence[Union[str, Path]] + path: str | Path, potential_ancestors: Sequence[str | Path] ) -> Path: """Find the closest ancestor of a path. @@ -89,7 +90,7 @@ def find_common_ancestor_of_nodes(*names: str) -> Path: return find_common_ancestor(*cleaned_names) -def find_common_ancestor(*paths: Union[str, Path]) -> Path: +def find_common_ancestor(*paths: str | Path) -> Path: """Find a common ancestor of many paths.""" common_ancestor = Path(os.path.commonpath(paths)) return common_ancestor diff --git a/src/_pytask/persist.py b/src/_pytask/persist.py index 4dbf2282..b1b7c6d3 100644 --- a/src/_pytask/persist.py +++ b/src/_pytask/persist.py @@ -1,7 +1,7 @@ """Implement the ability for tasks to persist.""" +from __future__ import annotations + from typing import Any -from typing import Dict -from typing import Optional from typing import TYPE_CHECKING from _pytask.config import hookimpl @@ -20,7 +20,7 @@ @hookimpl -def pytask_parse_config(config: Dict[str, Any]) -> None: +def pytask_parse_config(config: dict[str, Any]) -> None: """Add the marker to the configuration.""" config["markers"]["persist"] = ( "Prevent execution of a task if all products exist and even if something has " @@ -32,7 +32,7 @@ def pytask_parse_config(config: Dict[str, Any]) -> None: @hookimpl -def pytask_execute_task_setup(session: "Session", task: "MetaTask") -> None: +def pytask_execute_task_setup(session: Session, task: MetaTask) -> None: """Exit persisting tasks early. The decorator needs to be set and all nodes need to exist. @@ -57,8 +57,8 @@ def pytask_execute_task_setup(session: "Session", task: "MetaTask") -> None: @hookimpl def pytask_execute_task_process_report( - session: "Session", report: "ExecutionReport" -) -> Optional[bool]: + session: Session, report: ExecutionReport +) -> bool | None: """Set task status to success. Do not return ``True`` so that states will be updated in database. diff --git a/src/_pytask/pluginmanager.py b/src/_pytask/pluginmanager.py index 570d0da4..0ba78c51 100644 --- a/src/_pytask/pluginmanager.py +++ b/src/_pytask/pluginmanager.py @@ -1,4 +1,6 @@ """This module holds the plugin manager.""" +from __future__ import annotations + import pluggy from _pytask import hookspecs diff --git a/src/_pytask/profile.py b/src/_pytask/profile.py index 8091b068..3b03094d 100644 --- a/src/_pytask/profile.py +++ b/src/_pytask/profile.py @@ -1,4 +1,6 @@ """This module contains the code to profile the execution.""" +from __future__ import annotations + import csv import json import sys @@ -6,13 +8,7 @@ from pathlib import Path from types import TracebackType from typing import Any -from typing import Dict from typing import Generator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING import click @@ -55,7 +51,7 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_parse_config( - config: Dict[str, Any], config_from_cli: Dict[str, Any] + config: dict[str, Any], config_from_cli: dict[str, Any] ) -> None: """Parse the configuration.""" config["export"] = get_first_non_none_value( @@ -64,7 +60,7 @@ def pytask_parse_config( @hookimpl -def pytask_post_parse(config: Dict[str, Any]) -> None: +def pytask_post_parse(config: dict[str, Any]) -> None: """Register the export option.""" config["pm"].register(ExportNameSpace) config["pm"].register(DurationNameSpace) @@ -108,7 +104,7 @@ def _create_or_update_runtime(task_name: str, start: float, end: float) -> None: default=None, help="Export the profile in the specified format.", ) -def profile(**config_from_cli: Any) -> "NoReturn": +def profile(**config_from_cli: Any) -> NoReturn: """Show profile information on collected tasks.""" config_from_cli["command"] = "profile" @@ -126,8 +122,8 @@ def profile(**config_from_cli: Any) -> "NoReturn": except (ConfigurationError, Exception): session = Session({}, None) session.exit_code = ExitCode.CONFIGURATION_FAILED - exc_info: Tuple[ - Type[BaseException], BaseException, Optional[TracebackType] + exc_info: tuple[ + type[BaseException], BaseException, TracebackType | None ] = sys.exc_info() console.print(render_exc_info(*exc_info, show_locals=config["show_locals"])) @@ -137,7 +133,7 @@ def profile(**config_from_cli: Any) -> "NoReturn": session.hook.pytask_collect(session=session) session.hook.pytask_resolve_dependencies(session=session) - profile: Dict[str, Dict[str, Any]] = { + profile: dict[str, dict[str, Any]] = { task.name: {} for task in session.tasks } session.hook.pytask_profile_add_info_on_task( @@ -163,7 +159,7 @@ def profile(**config_from_cli: Any) -> "NoReturn": def _print_profile_table( - profile: Dict[str, Dict[str, Any]], tasks: List[MetaTask], config: Dict[str, Any] + profile: dict[str, dict[str, Any]], tasks: list[MetaTask], config: dict[str, Any] ) -> None: """Print the profile table.""" name_to_task = {task.name: task for task in tasks} @@ -193,7 +189,7 @@ class DurationNameSpace: @staticmethod @hookimpl def pytask_profile_add_info_on_task( - tasks: List[MetaTask], profile: Dict[str, Dict[str, Any]] + tasks: list[MetaTask], profile: dict[str, dict[str, Any]] ) -> None: runtimes = _collect_runtimes([task.name for task in tasks]) for name, duration in runtimes.items(): @@ -201,7 +197,7 @@ def pytask_profile_add_info_on_task( @orm.db_session -def _collect_runtimes(task_names: List[str]) -> Dict[str, float]: +def _collect_runtimes(task_names: list[str]) -> dict[str, float]: """Collect runtimes.""" runtimes = [Runtime.get(task=task_name) for task_name in task_names] runtimes = [r for r in runtimes if r is not None] @@ -212,7 +208,7 @@ class FileSizeNameSpace: @staticmethod @hookimpl def pytask_profile_add_info_on_task( - session: Session, tasks: List[MetaTask], profile: Dict[str, Dict[str, Any]] + session: Session, tasks: list[MetaTask], profile: dict[str, dict[str, Any]] ) -> None: for task in tasks: successors = list(session.dag.successors(task.name)) @@ -231,7 +227,7 @@ def pytask_profile_add_info_on_task( ) -def _to_human_readable_size(bytes_: int, units: Optional[List[str]] = None) -> str: +def _to_human_readable_size(bytes_: int, units: list[str] | None = None) -> str: """Convert bytes to a human readable size.""" units = [" bytes", "KB", "MB", "GB", "TB"] if units is None else units return ( @@ -241,7 +237,7 @@ def _to_human_readable_size(bytes_: int, units: Optional[List[str]] = None) -> s ) -def _process_profile(profile: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: +def _process_profile(profile: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]: """Process profile to make it ready for printing and storing.""" info_names = _get_info_names(profile) if info_names: @@ -261,7 +257,7 @@ class ExportNameSpace: @staticmethod @hookimpl(trylast=True) def pytask_profile_export_profile( - session: Session, profile: Dict[str, Dict[str, Any]] + session: Session, profile: dict[str, dict[str, Any]] ) -> None: extension = session.config["export"] @@ -275,7 +271,7 @@ def pytask_profile_export_profile( raise ValueError(f"The export option {extension!r} cannot be handled.") -def _export_to_csv(profile: Dict[str, Dict[str, Any]]) -> None: +def _export_to_csv(profile: dict[str, dict[str, Any]]) -> None: """Export profile to csv.""" info_names = _get_info_names(profile) path = Path.cwd().joinpath("profile.csv") @@ -287,14 +283,14 @@ def _export_to_csv(profile: Dict[str, Dict[str, Any]]) -> None: writer.writerow((task_name, *info.values())) -def _export_to_json(profile: Dict[str, Dict[str, Any]]) -> None: +def _export_to_json(profile: dict[str, dict[str, Any]]) -> None: """Export profile to json.""" json_ = json.dumps(profile) path = Path.cwd().joinpath("profile.json") path.write_text(json_) -def _get_info_names(profile: Dict[str, Dict[str, Any]]) -> List[str]: +def _get_info_names(profile: dict[str, dict[str, Any]]) -> list[str]: """Get names of infos of tasks. Examples @@ -307,6 +303,6 @@ def _get_info_names(profile: Dict[str, Dict[str, Any]]) -> List[str]: [] """ - base: Set[str] = set() - info_names: List[str] = sorted(base.union(*(set(val) for val in profile.values()))) + base: set[str] = set() + info_names: list[str] = sorted(base.union(*(set(val) for val in profile.values()))) return info_names diff --git a/src/_pytask/report.py b/src/_pytask/report.py index d5ca6be8..7c67fec9 100644 --- a/src/_pytask/report.py +++ b/src/_pytask/report.py @@ -1,4 +1,6 @@ """This module contains everything related to reports.""" +from __future__ import annotations + from types import TracebackType from typing import List from typing import Optional @@ -30,18 +32,16 @@ class CollectionReport: exc_info = attr.ib(default=None, type=ExceptionInfo) @classmethod - def from_node( - cls, outcome: CollectionOutcome, node: "MetaNode" - ) -> "CollectionReport": + def from_node(cls, outcome: CollectionOutcome, node: MetaNode) -> CollectionReport: return cls(outcome=outcome, node=node) @classmethod def from_exception( - cls: Type["CollectionReport"], + cls: type[CollectionReport], outcome: CollectionOutcome, exc_info: ExceptionInfo, - node: Optional["MetaNode"] = None, - ) -> "CollectionReport": + node: MetaNode | None = None, + ) -> CollectionReport: exc_info = remove_internal_traceback_frames_from_exc_info(exc_info) return cls(outcome=outcome, node=node, exc_info=exc_info) @@ -53,7 +53,7 @@ class ResolvingDependenciesReport: exc_info = attr.ib(type=ExceptionInfo) @classmethod - def from_exception(cls, exc_info: ExceptionInfo) -> "ResolvingDependenciesReport": + def from_exception(cls, exc_info: ExceptionInfo) -> ResolvingDependenciesReport: exc_info = remove_internal_traceback_frames_from_exc_info(exc_info) return cls(exc_info) @@ -69,11 +69,11 @@ class ExecutionReport: @classmethod def from_task_and_exception( - cls, task: "MetaTask", exc_info: ExceptionInfo - ) -> "ExecutionReport": + cls, task: MetaTask, exc_info: ExceptionInfo + ) -> ExecutionReport: exc_info = remove_internal_traceback_frames_from_exc_info(exc_info) return cls(task, TaskOutcome.FAIL, exc_info, task._report_sections) @classmethod - def from_task(cls, task: "MetaTask") -> "ExecutionReport": + def from_task(cls, task: MetaTask) -> ExecutionReport: return cls(task, TaskOutcome.SUCCESS, None, task._report_sections) diff --git a/src/_pytask/resolve_dependencies.py b/src/_pytask/resolve_dependencies.py index 08bfc73f..33824c32 100644 --- a/src/_pytask/resolve_dependencies.py +++ b/src/_pytask/resolve_dependencies.py @@ -1,10 +1,7 @@ +from __future__ import annotations + import itertools import sys -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple -from typing import Union import networkx as nx from _pytask.config import hookimpl @@ -36,7 +33,7 @@ @hookimpl -def pytask_resolve_dependencies(session: Session) -> Optional[bool]: +def pytask_resolve_dependencies(session: Session) -> bool | None: """Create a directed acyclic graph (DAG) capturing dependencies between functions. Parameters @@ -71,7 +68,7 @@ def pytask_resolve_dependencies(session: Session) -> Optional[bool]: @hookimpl -def pytask_resolve_dependencies_create_dag(tasks: List[MetaTask]) -> nx.DiGraph: +def pytask_resolve_dependencies_create_dag(tasks: list[MetaTask]) -> nx.DiGraph: """Create the DAG from tasks, dependencies and products.""" dag = nx.DiGraph() @@ -125,7 +122,7 @@ def _have_task_or_neighbors_changed(task_name: str, dag: nx.DiGraph) -> bool: @orm.db_session def _has_node_changed( - task_name: str, node_dict: Dict[str, Union[MetaNode, MetaTask]] + task_name: str, node_dict: dict[str, MetaNode | MetaTask] ) -> bool: """Indicate whether a single dependency or product has changed.""" node = node_dict.get("task") or node_dict["node"] @@ -159,7 +156,7 @@ def _check_if_dag_has_cycles(dag: nx.DiGraph) -> None: ) -def _format_cycles(cycles: List[Tuple[str, ...]]) -> str: +def _format_cycles(cycles: list[tuple[str, ...]]) -> str: """Format cycles as a paths connected by arrows.""" chain = [x for i, x in enumerate(itertools.chain(*cycles)) if i % 2 == 0] chain += [cycles[-1][1]] @@ -183,7 +180,7 @@ def _format_cycles(cycles: List[Tuple[str, ...]]) -> str: def _check_if_root_nodes_are_available(dag: nx.DiGraph) -> None: missing_root_nodes = [] - is_task_skipped: Dict[str, bool] = {} + is_task_skipped: dict[str, bool] = {} for node in dag.nodes: is_node = "node" in dag.nodes[node] @@ -225,8 +222,8 @@ def _check_if_root_nodes_are_available(dag: nx.DiGraph) -> None: def _check_if_tasks_are_skipped( - node: MetaNode, dag: nx.DiGraph, is_task_skipped: Dict[str, bool] -) -> Tuple[bool, Dict[str, bool]]: + node: MetaNode, dag: nx.DiGraph, is_task_skipped: dict[str, bool] +) -> tuple[bool, dict[str, bool]]: """Check for a given node whether it is only used by skipped tasks.""" are_all_tasks_skipped = [] for successor in dag.successors(node): @@ -251,12 +248,12 @@ def _check_if_task_is_skipped(task_name: str, dag: nx.DiGraph) -> bool: return is_any_true -def _skipif(condition: bool, *, reason: str) -> Tuple[bool, str]: +def _skipif(condition: bool, *, reason: str) -> tuple[bool, str]: """Shameless copy to circumvent circular imports.""" return condition, reason -def _format_dictionary_to_tree(dict_: Dict[str, List[str]], title: str) -> str: +def _format_dictionary_to_tree(dict_: dict[str, list[str]], title: str) -> str: """Format missing root nodes.""" tree = Tree(title) diff --git a/src/_pytask/session.py b/src/_pytask/session.py index dc74eb09..8264767a 100644 --- a/src/_pytask/session.py +++ b/src/_pytask/session.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any from typing import Dict from typing import List # noqa: F401 @@ -61,6 +63,6 @@ class Session: """Optional[bool]: Indicates whether the session should be stopped.""" @classmethod - def from_config(cls, config: Dict[str, Any]) -> "Session": + def from_config(cls, config: dict[str, Any]) -> Session: """Construct the class from a config.""" return cls(config, config["pm"].hook) diff --git a/src/_pytask/shared.py b/src/_pytask/shared.py index ca5e4abb..9b962157 100644 --- a/src/_pytask/shared.py +++ b/src/_pytask/shared.py @@ -1,13 +1,11 @@ """Functions which are used across various modules.""" +from __future__ import annotations + import glob from pathlib import Path from typing import Any from typing import Callable -from typing import Dict -from typing import List -from typing import Optional from typing import Sequence -from typing import Union import networkx as nx from _pytask.console import format_task_id @@ -18,7 +16,7 @@ from _pytask.path import relative_to -def to_list(scalar_or_iter: Any) -> List[Any]: +def to_list(scalar_or_iter: Any) -> list[Any]: """Convert scalars and iterables to list. Parameters @@ -44,7 +42,7 @@ def to_list(scalar_or_iter: Any) -> List[Any]: ) -def parse_paths(x: Optional[Any]) -> Optional[List[Path]]: +def parse_paths(x: Any | None) -> list[Path] | None: """Parse paths.""" if x is not None: paths = [Path(p) for p in to_list(x)] @@ -59,7 +57,7 @@ def parse_paths(x: Optional[Any]) -> Optional[List[Path]]: def falsy_to_none_callback( ctx: Any, param: Any, value: Any # noqa: U100 -) -> Optional[Any]: +) -> Any | None: """Convert falsy object to ``None``. Some click arguments accept multiple inputs and instead of ``None`` as a default if @@ -82,10 +80,10 @@ def falsy_to_none_callback( def get_first_non_none_value( - *configs: Dict[str, Any], + *configs: dict[str, Any], key: str, - default: Optional[Any] = None, - callback: Optional[Callable[..., Any]] = None, + default: Any | None = None, + callback: Callable[..., Any] | None = None, ) -> Any: """Get the first non-None value for a key from a list of dictionaries. @@ -109,9 +107,7 @@ def get_first_non_none_value( return next((value for value in processed_values if value is not None), default) -def parse_value_or_multiline_option( - value: Union[str, None] -) -> Union[None, str, List[str]]: +def parse_value_or_multiline_option(value: str | None) -> None | str | list[str]: """Parse option which can hold a single value or values separated by new lines.""" if value in ["none", "None", None, ""]: return None @@ -123,7 +119,7 @@ def parse_value_or_multiline_option( raise ValueError(f"Input {value!r} is neither a 'str' nor 'None'.") -def convert_truthy_or_falsy_to_bool(x: Union[bool, str, None]) -> bool: +def convert_truthy_or_falsy_to_bool(x: bool | str | None) -> bool: """Convert truthy or falsy value in .ini to Python boolean.""" if x in [True, "True", "true", "1"]: out = True @@ -138,7 +134,7 @@ def convert_truthy_or_falsy_to_bool(x: Union[bool, str, None]) -> bool: return out -def reduce_node_name(node: "MetaNode", paths: Sequence[Union[str, Path]]) -> str: +def reduce_node_name(node: MetaNode, paths: Sequence[str | Path]) -> str: """Reduce the node name. The whole name of the node - which includes the drive letter - can be very long @@ -164,8 +160,8 @@ def reduce_node_name(node: "MetaNode", paths: Sequence[Union[str, Path]]) -> str def reduce_names_of_multiple_nodes( - names: List[str], dag: nx.DiGraph, paths: Sequence[Union[str, Path]] -) -> List[str]: + names: list[str], dag: nx.DiGraph, paths: Sequence[str | Path] +) -> list[str]: """Reduce the names of multiple nodes in the DAG.""" short_names = [] for name in names: diff --git a/src/_pytask/skipping.py b/src/_pytask/skipping.py index 55ad56ee..ff07d8c8 100644 --- a/src/_pytask/skipping.py +++ b/src/_pytask/skipping.py @@ -1,8 +1,7 @@ """This module contains everything related to skipping tasks.""" +from __future__ import annotations + from typing import Any -from typing import Dict -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING from _pytask.config import hookimpl @@ -27,13 +26,13 @@ def skip_ancestor_failed(reason: str = "No reason provided.") -> str: return reason -def skipif(condition: bool, *, reason: str) -> Tuple[bool, str]: +def skipif(condition: bool, *, reason: str) -> tuple[bool, str]: """Function to parse information in ``@pytask.mark.skipif``.""" return condition, reason @hookimpl -def pytask_parse_config(config: Dict[str, Any]) -> None: +def pytask_parse_config(config: dict[str, Any]) -> None: markers = { "skip": "Skip a task and all its subsequent tasks as well.", "skip_ancestor_failed": "Internal decorator applied to tasks whose ancestor " @@ -47,7 +46,7 @@ def pytask_parse_config(config: Dict[str, Any]) -> None: @hookimpl -def pytask_execute_task_setup(task: "MetaTask") -> None: +def pytask_execute_task_setup(task: MetaTask) -> None: """Take a short-cut for skipped tasks during setup with an exception.""" markers = get_specific_markers_from_task(task, "skip_unchanged") if markers: @@ -75,8 +74,8 @@ def pytask_execute_task_setup(task: "MetaTask") -> None: @hookimpl def pytask_execute_task_process_report( - session: "Session", report: "ExecutionReport" -) -> Optional[bool]: + session: Session, report: ExecutionReport +) -> bool | None: """Process the execution reports for skipped tasks. This functions allows to turn skipped tasks to successful tasks. diff --git a/src/_pytask/traceback.py b/src/_pytask/traceback.py index eab34e9f..e384cec1 100644 --- a/src/_pytask/traceback.py +++ b/src/_pytask/traceback.py @@ -1,4 +1,6 @@ """Process tracebacks.""" +from __future__ import annotations + from pathlib import Path from types import TracebackType from typing import Generator @@ -19,11 +21,11 @@ def render_exc_info( - exc_type: Type[BaseException], + exc_type: type[BaseException], exc_value: BaseException, - traceback: Union[str, TracebackType], + traceback: str | TracebackType, show_locals: bool = False, -) -> Union[str, Traceback]: +) -> str | Traceback: if isinstance(traceback, str): renderable = traceback else: diff --git a/src/pytask/__init__.py b/src/pytask/__init__.py index e6d25448..d469dfa8 100644 --- a/src/pytask/__init__.py +++ b/src/pytask/__init__.py @@ -1,4 +1,6 @@ """This module contains the main namespace for pytask.""" +from __future__ import annotations + from _pytask import __version__ from _pytask.build import main from _pytask.cli import cli diff --git a/src/pytask/__main__.py b/src/pytask/__main__.py index 250c5c97..851341b9 100644 --- a/src/pytask/__main__.py +++ b/src/pytask/__main__.py @@ -1,4 +1,6 @@ """The pytask entry-point.""" +from __future__ import annotations + import sys import pytask diff --git a/tests/_test_console_helpers.py b/tests/_test_console_helpers.py index 992a8572..41f269d5 100644 --- a/tests/_test_console_helpers.py +++ b/tests/_test_console_helpers.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import functools diff --git a/tests/conftest.py b/tests/conftest.py index e4e71a16..4af92821 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/test_build.py b/tests/test_build.py index 2574f9ec..0ebc058a 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest diff --git a/tests/test_capture.py b/tests/test_capture.py index 8914a45c..b92d223c 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import io import os diff --git a/tests/test_clean.py b/tests/test_clean.py index 4873f3c8..fecbf4eb 100644 --- a/tests/test_clean.py +++ b/tests/test_clean.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest diff --git a/tests/test_cli.py b/tests/test_cli.py index 4faf44b1..261afbb6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import subprocess import pytest diff --git a/tests/test_collect.py b/tests/test_collect.py index 5c280a67..47ac9029 100644 --- a/tests/test_collect.py +++ b/tests/test_collect.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_collect_command.py b/tests/test_collect_command.py index 8ca8c434..8c6b555f 100644 --- a/tests/test_collect_command.py +++ b/tests/test_collect_command.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from pathlib import Path diff --git a/tests/test_compat.py b/tests/test_compat.py index 67ee0836..f5d6fa47 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import types from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_config.py b/tests/test_config.py index b9a8f7c0..157d6efc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_console.py b/tests/test_console.py index ae4f5e9a..2b678b7c 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect from pathlib import Path diff --git a/tests/test_dag.py b/tests/test_dag.py index 18cf2f78..e42df47c 100644 --- a/tests/test_dag.py +++ b/tests/test_dag.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import ExitStack as does_not_raise # noqa: N813 import attr diff --git a/tests/test_database.py b/tests/test_database.py index e1102544..9a12176d 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import textwrap diff --git a/tests/test_debugging.py b/tests/test_debugging.py index 562b1558..dbf635ce 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import re import sys diff --git a/tests/test_execute.py b/tests/test_execute.py index 20ef1363..ed025d39 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import subprocess import textwrap diff --git a/tests/test_graph.py b/tests/test_graph.py index 0e014909..908aec79 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import shutil import textwrap diff --git a/tests/test_ignore.py b/tests/test_ignore.py index c0b7415b..f71356c1 100644 --- a/tests/test_ignore.py +++ b/tests/test_ignore.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/test_live.py b/tests/test_live.py index da612691..6679bb31 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_logging.py b/tests/test_logging.py index f613bd8e..ec473b8a 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_mark.py b/tests/test_mark.py index 112e6d62..706b6361 100644 --- a/tests/test_mark.py +++ b/tests/test_mark.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import textwrap diff --git a/tests/test_mark_cli.py b/tests/test_mark_cli.py index 9ef8cb2a..6428ce14 100644 --- a/tests/test_mark_cli.py +++ b/tests/test_mark_cli.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest diff --git a/tests/test_mark_expression.py b/tests/test_mark_expression.py index 98db1860..85f78ef6 100644 --- a/tests/test_mark_expression.py +++ b/tests/test_mark_expression.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable import pytest diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 72b09144..ac603697 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import ExitStack as does_not_raise # noqa: N813 from pathlib import Path diff --git a/tests/test_outcomes.py b/tests/test_outcomes.py index b38d29a2..b145ad77 100644 --- a/tests/test_outcomes.py +++ b/tests/test_outcomes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytask.outcomes import CollectionOutcome from _pytask.outcomes import count_outcomes diff --git a/tests/test_parametrize.py b/tests/test_parametrize.py index 375e305c..9d4ac99a 100644 --- a/tests/test_parametrize.py +++ b/tests/test_parametrize.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_path.py b/tests/test_path.py index 625e86d4..8982902d 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from contextlib import ExitStack as does_not_raise # noqa: N813 from pathlib import Path diff --git a/tests/test_persist.py b/tests/test_persist.py index de8b7dd3..dab7f2bd 100644 --- a/tests/test_persist.py +++ b/tests/test_persist.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest diff --git a/tests/test_profile.py b/tests/test_profile.py index a31b48ae..fbb8904f 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import textwrap diff --git a/tests/test_resolve_dependencies.py b/tests/test_resolve_dependencies.py index dc8bd7a8..62c83ae9 100644 --- a/tests/test_resolve_dependencies.py +++ b/tests/test_resolve_dependencies.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 from pathlib import Path diff --git a/tests/test_shared.py b/tests/test_shared.py index bbdd8e13..fd093cb5 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import ExitStack as does_not_raise # noqa: N813 import pytest diff --git a/tests/test_skipping.py b/tests/test_skipping.py index b02e6620..202dc21e 100644 --- a/tests/test_skipping.py +++ b/tests/test_skipping.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from contextlib import ExitStack as does_not_raise # noqa: N813 diff --git a/tests/test_traceback.py b/tests/test_traceback.py index 49c30775..5d02dcc9 100644 --- a/tests/test_traceback.py +++ b/tests/test_traceback.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest From d57aa326229bee2c70bd1d59402167120bc3ec3c Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Tue, 18 Jan 2022 00:41:51 +0100 Subject: [PATCH 5/8] Fix. --- src/_pytask/clean.py | 2 +- src/_pytask/collect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytask/clean.py b/src/_pytask/clean.py index 33c015dc..0e7afa8c 100644 --- a/src/_pytask/clean.py +++ b/src/_pytask/clean.py @@ -233,7 +233,7 @@ class _RecursivePathNode: """ path = attr.ib(type=Path) - sub_nodes = attr.ib(type=list["_RecursivePathNode"]) + sub_nodes = attr.ib(type="list[_RecursivePathNode]") is_dir = attr.ib(type=bool) is_file = attr.ib(type=bool) is_unknown = attr.ib(type=bool) diff --git a/src/_pytask/collect.py b/src/_pytask/collect.py index 9d139225..4d1730ee 100644 --- a/src/_pytask/collect.py +++ b/src/_pytask/collect.py @@ -250,7 +250,7 @@ def _not_ignored_paths( Parameters ---------- - paths : List[pathlib.Path] + paths : Iterable[pathlib.Path] List of paths from which tasks are collected. session : _pytask.session.Session The session. From 354c5e7fd638c8af5d70beafa02e68ddad323b37 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Wed, 19 Jan 2022 00:02:19 +0100 Subject: [PATCH 6/8] remove unnessary skip. --- tests/test_debugging.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_debugging.py b/tests/test_debugging.py index dbf635ce..69ece020 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -325,7 +325,6 @@ def task_1(): @pytest.mark.end_to_end @pytest.mark.skipif(not IS_PEXPECT_INSTALLED, reason="pexpect is not installed.") @pytest.mark.skipif(sys.platform == "win32", reason="pexpect cannot spawn on Windows.") -@pytest.mark.skipif(sys.version_info < (3, 7), reason="Importing fails for <3.7.") def test_pdb_with_injected_do_debug(tmp_path): """Simulates pdbpp, which injects Pdb into do_debug, and uses self.__class__ in do_continue. From d66f49274542505665c760eb6d4dfbcd54e8be66 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 23 Jan 2022 20:36:36 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytask/task.py | 2 ++ src/_pytask/task_utils.py | 2 ++ tests/test_mark_utils.py | 2 ++ tests/test_task.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/_pytask/task.py b/src/_pytask/task.py index 41f2f9f6..bf05d6b7 100644 --- a/src/_pytask/task.py +++ b/src/_pytask/task.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from typing import Any from typing import Dict diff --git a/src/_pytask/task_utils.py b/src/_pytask/task_utils.py index f95d8fff..48783f92 100644 --- a/src/_pytask/task_utils.py +++ b/src/_pytask/task_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any from typing import Callable from typing import Optional diff --git a/tests/test_mark_utils.py b/tests/test_mark_utils.py index 35fa2b72..c78ef15f 100644 --- a/tests/test_mark_utils.py +++ b/tests/test_mark_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import attr import pytask import pytest diff --git a/tests/test_task.py b/tests/test_task.py index a32ca2f1..77241c2a 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap import pytest From 343c43f555cbed4c0dcc3dee021c55f4b429d7bb Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 23 Jan 2022 21:40:17 +0100 Subject: [PATCH 8/8] Fix. --- src/_pytask/task.py | 6 ++---- src/_pytask/task_utils.py | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/_pytask/task.py b/src/_pytask/task.py index bf05d6b7..627b9714 100644 --- a/src/_pytask/task.py +++ b/src/_pytask/task.py @@ -2,8 +2,6 @@ from pathlib import Path from typing import Any -from typing import Dict -from typing import Optional from _pytask.config import hookimpl from _pytask.mark_utils import has_marker @@ -13,14 +11,14 @@ @hookimpl -def pytask_parse_config(config: Dict[str, Any]) -> None: +def pytask_parse_config(config: dict[str, Any]) -> None: config["markers"]["task"] = "Mark a function as a task regardless of its name." @hookimpl def pytask_collect_task( session: Session, path: Path, name: str, obj: Any -) -> Optional[PythonFunctionTask]: +) -> PythonFunctionTask | None: """Collect a task which is a function. There is some discussion on how to detect functions in this `thread diff --git a/src/_pytask/task_utils.py b/src/_pytask/task_utils.py index 48783f92..e9927866 100644 --- a/src/_pytask/task_utils.py +++ b/src/_pytask/task_utils.py @@ -2,13 +2,12 @@ from typing import Any from typing import Callable -from typing import Optional from _pytask.mark import Mark from _pytask.mark_utils import remove_markers_from_func -def task(name: Optional[str] = None) -> str: +def task(name: str | None = None) -> str: return name