diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d3b6105c..9f3f7ca49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,14 @@ name: CI -on: [push, pull_request] +on: + pull_request: + branches: + - main + - development + push: + branches: + - main + - development jobs: diff --git a/docs/global_options.rst b/docs/global_options.rst index 09736aa3a..3a758b9e1 100644 --- a/docs/global_options.rst +++ b/docs/global_options.rst @@ -4,25 +4,25 @@ Global options The following options can be set for all tasks in a project directly under ``[tool.poe]``. These are called global options, in contrast with :doc:`task options`. -**env** : ``Dict[str, str]`` :ref:`📖` +**env** : ``dict[str, str]`` :ref:`📖` Define environment variables to be exposed to all tasks. These can be :ref:`extended on the task level`. -**envfile** : ``str`` | ``List[str]`` :ref:`📖` +**envfile** : ``str`` | ``list[str]`` :ref:`📖` Link to one or more files defining environment variables to be exposed to all tasks. -**executor** : ``Dict[str, str]`` :ref:`📖` +**executor** : ``dict[str, str]`` :ref:`📖` Override the default behavior for selecting an executor for tasks in this project. -**include** : ``str`` | ``List[str]`` | ``Dict[str, str]`` :doc:`📖<../guides/include_guide>` +**include** : ``str`` | ``list[str]`` | ``dict[str, str]`` :doc:`📖<../guides/include_guide>` Specify one or more other toml or json files to load tasks from. -**shell_interpreter** : ``str`` | ``List[str]`` :ref:`📖` +**shell_interpreter** : ``str`` | ``list[str]`` :ref:`📖` Change the default interpreter to use for executing :doc:`shell tasks<../tasks/task_types/shell>`. **poetry_command** : ``str`` :ref:`📖` Change the name of the task poe registers with poetry when used as a plugin. -**poetry_hooks** : ``Dict[str, str]`` :ref:`📖` +**poetry_hooks** : ``dict[str, str]`` :ref:`📖` Register tasks to run automatically before or after other poetry CLI commands. **verbosity** : ``int`` diff --git a/docs/guides/args_guide.rst b/docs/guides/args_guide.rst index 21eb991b9..b0102c2be 100644 --- a/docs/guides/args_guide.rst +++ b/docs/guides/args_guide.rst @@ -143,7 +143,7 @@ Named arguments support the following configuration options: - **name** : ``str`` The name of the task. Only applicable when *args* is an array. -- **options** : ``List[str]`` +- **options** : ``list[str]`` A list of options to accept for this argument, similar to `argsparse name or flags `_. If not provided then the name of the argument is used. You can use this option to expose a different name to the CLI vs the name that is used inside the task, or to specify long and short forms of the CLI option, e.g. ``["-h", "--help"]``. - **positional** : ``bool`` diff --git a/docs/tasks/options.rst b/docs/tasks/options.rst index 9678d5bb4..803d9d235 100644 --- a/docs/tasks/options.rst +++ b/docs/tasks/options.rst @@ -9,23 +9,23 @@ The following options can be configured on your tasks and are not specific to an **help** : ``str`` | ``int`` :doc:`📖<../guides/help_guide>` Help text to be displayed next to the task name in the documentation when poe is run without specifying a task. -**args** : ``Dict[str, dict]`` | ``List[Union[str, dict]]`` :doc:`📖<../guides/args_guide>` +**args** : ``dict[str, dict]`` | ``list[Union[str, dict]]`` :doc:`📖<../guides/args_guide>` Define CLI options, positional arguments, or flags that this task should accept. -**env** : ``Dict[str, str]`` :ref:`📖` +**env** : ``dict[str, str]`` :ref:`📖` A map of environment variables to be set for this task. -**envfile** : ``str`` | ``List[str]`` :ref:`📖` +**envfile** : ``str`` | ``list[str]`` :ref:`📖` Provide one or more env files to be loaded before running this task. **cwd** : ``str`` :ref:`📖` Specify the current working directory that this task should run with. The given path is resolved relative to the parent directory of the ``pyproject.toml``, or it may be absolute. Resolves environment variables in the format ``${VAR_NAME}``. -**deps** : ``List[str]`` :doc:`📖<../guides/composition_guide>` +**deps** : ``list[str]`` :doc:`📖<../guides/composition_guide>` A list of task invocations that will be executed before this one. -**uses** : ``Dict[str, str]`` :doc:`📖<../guides/composition_guide>` +**uses** : ``dict[str, str]`` :doc:`📖<../guides/composition_guide>` Allows this task to use the output of other tasks which are executed first. The value is a map where the values are invocations of the other tasks, and the keys are environment variables by which the results of those tasks will be accessible in this task. diff --git a/docs/tasks/task_types/expr.rst b/docs/tasks/task_types/expr.rst index df0eb9fd7..b6ae64a3a 100644 --- a/docs/tasks/task_types/expr.rst +++ b/docs/tasks/task_types/expr.rst @@ -33,7 +33,7 @@ Available task options The following options are also accepted: -**imports** : ``List[str]`` :ref:`📖` +**imports** : ``list[str]`` :ref:`📖` A list of modules to import for use in the expression. **assert** : ``bool`` :ref:`📖` diff --git a/docs/tasks/task_types/shell.rst b/docs/tasks/task_types/shell.rst index 3c99c82a9..67238e070 100644 --- a/docs/tasks/task_types/shell.rst +++ b/docs/tasks/task_types/shell.rst @@ -28,7 +28,7 @@ Available task options The following options are also accepted: -**interpreter** : ``str`` | ``List[str]`` :ref:`📖` +**interpreter** : ``str`` | ``list[str]`` :ref:`📖` Specify the shell interpreter that this task should execute with, or a list of interpreters in order of preference. diff --git a/poethepoet/app.py b/poethepoet/app.py index cb98d9a39..57cb0e384 100644 --- a/poethepoet/app.py +++ b/poethepoet/app.py @@ -1,17 +1,8 @@ import os import sys +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import ( - IO, - TYPE_CHECKING, - Any, - Dict, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) +from typing import IO, TYPE_CHECKING, Any, Optional, Union from .exceptions import ExecutionError, PoeException @@ -57,7 +48,7 @@ class PoeThePoet: ui: "PoeUi" config: "PoeConfig" - _task_specs: Optional[Dict[str, "PoeTask.TaskSpec"]] = None + _task_specs: Optional[dict[str, "PoeTask.TaskSpec"]] = None def __init__( self, @@ -242,8 +233,8 @@ def print_help( if isinstance(error, str): error = PoeException(error) - tasks_help: Dict[ - str, Tuple[str, Sequence[Tuple[Tuple[str, ...], str, str]]] + tasks_help: dict[ + str, tuple[str, Sequence[tuple[tuple[str, ...], str, str]]] ] = { task_name: ( ( diff --git a/poethepoet/completion/zsh.py b/poethepoet/completion/zsh.py index c528eb92f..498336164 100644 --- a/poethepoet/completion/zsh.py +++ b/poethepoet/completion/zsh.py @@ -1,4 +1,7 @@ -from typing import Any, Iterable, Set +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Iterable def get_zsh_completion_script(name: str = "") -> str: @@ -50,7 +53,7 @@ def format_exclusions(excl_option_strings): tuple(), ) # collect all option strings that are exclusive with this one - excl_option_strings: Set[str] = { + excl_option_strings: set[str] = { option_string for excl_option in excl_options for option_string in excl_option.option_strings diff --git a/poethepoet/config/__init__.py b/poethepoet/config/__init__.py index 6310028e1..98536717f 100644 --- a/poethepoet/config/__init__.py +++ b/poethepoet/config/__init__.py @@ -1,4 +1,4 @@ from .config import PoeConfig from .partition import KNOWN_SHELL_INTERPRETERS, ConfigPartition -__all__ = ["PoeConfig", "ConfigPartition", "KNOWN_SHELL_INTERPRETERS"] +__all__ = ["KNOWN_SHELL_INTERPRETERS", "ConfigPartition", "PoeConfig"] diff --git a/poethepoet/config/config.py b/poethepoet/config/config.py index 333b6434d..56d9a3460 100644 --- a/poethepoet/config/config.py +++ b/poethepoet/config/config.py @@ -1,16 +1,7 @@ +from collections.abc import Iterator, Mapping, Sequence from os import environ from pathlib import Path -from typing import ( - Any, - Dict, - Iterator, - List, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) +from typing import Any, Optional, Union from ..exceptions import ConfigValidationError, PoeException from .file import PoeConfigFile @@ -21,12 +12,12 @@ class PoeConfig: _project_config: ProjectConfig - _included_config: List[IncludedConfig] + _included_config: list[IncludedConfig] """ The filenames to look for when loading config """ - _config_filenames: Tuple[str, ...] = ( + _config_filenames: tuple[str, ...] = ( "pyproject.toml", "poe_tasks.toml", "poe_tasks.yaml", @@ -61,7 +52,7 @@ def __init__( def lookup_task( self, name: str - ) -> Union[Tuple[Mapping[str, Any], ConfigPartition], Tuple[None, None]]: + ) -> Union[tuple[Mapping[str, Any], ConfigPartition], tuple[None, None]]: task = self._project_config.get("tasks", {}).get(name, None) if task is not None: return task, self._project_config @@ -95,7 +86,7 @@ def task_names(self) -> Iterator[str]: yield from result @property - def tasks(self) -> Dict[str, Any]: + def tasks(self) -> dict[str, Any]: result = dict(self._project_config.get("tasks", {})) for config in self._included_config: for task_name, task_def in config.get("tasks", {}).items(): @@ -117,7 +108,7 @@ def default_array_item_task_type(self) -> str: return self._project_config.options.default_array_item_task_type @property - def shell_interpreter(self) -> Tuple[str, ...]: + def shell_interpreter(self) -> tuple[str, ...]: raw_value = self._project_config.options.shell_interpreter if isinstance(raw_value, list): return tuple(raw_value) diff --git a/poethepoet/config/file.py b/poethepoet/config/file.py index 0ec05211f..fab0ee8f8 100644 --- a/poethepoet/config/file.py +++ b/poethepoet/config/file.py @@ -1,11 +1,6 @@ +from collections.abc import Iterator, Mapping, Sequence from pathlib import Path -from typing import ( - Any, - Iterator, - Mapping, - Optional, - Sequence, -) +from typing import Any, Optional from ..exceptions import PoeException diff --git a/poethepoet/config/partition.py b/poethepoet/config/partition.py index 6cfc2fc38..b0fbbd444 100644 --- a/poethepoet/config/partition.py +++ b/poethepoet/config/partition.py @@ -1,14 +1,7 @@ +from collections.abc import Mapping, Sequence from pathlib import Path from types import MappingProxyType -from typing import ( - Any, - Mapping, - Optional, - Sequence, - Type, - TypedDict, - Union, -) +from typing import Any, Optional, TypedDict, Union from ..exceptions import ConfigValidationError from ..options import NoValue, PoeOptions @@ -42,7 +35,7 @@ class ConfigPartition: project_dir: Path _cwd: Optional[Path] - ConfigOptions: Type[PoeOptions] + ConfigOptions: type[PoeOptions] is_primary: bool = False def __init__( @@ -115,7 +108,7 @@ def normalize( raise ConfigValidationError("Expected ") # Normalize include option: - # > Union[str, Sequence[str], Mapping[str, str]] => List[dict] + # > Union[str, Sequence[str], Mapping[str, str]] => list[dict] if "include" in config: includes: Any = [] include_option = config.get("include", None) diff --git a/poethepoet/config/primitives.py b/poethepoet/config/primitives.py index daeff634c..86ddb6acb 100644 --- a/poethepoet/config/primitives.py +++ b/poethepoet/config/primitives.py @@ -1,5 +1,6 @@ +from collections.abc import Mapping from types import MappingProxyType -from typing import Mapping, TypedDict +from typing import TypedDict EmptyDict: Mapping = MappingProxyType({}) diff --git a/poethepoet/context.py b/poethepoet/context.py index 901a2ad32..e18e0eb22 100644 --- a/poethepoet/context.py +++ b/poethepoet/context.py @@ -1,6 +1,7 @@ import re +from collections.abc import Mapping from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: from .config import PoeConfig @@ -17,8 +18,8 @@ class RunContext: poe_active: Optional[str] project_dir: Path multistage: bool = False - exec_cache: Dict[str, Any] - captured_stdout: Dict[Tuple[str, ...], str] + exec_cache: dict[str, Any] + captured_stdout: dict[tuple[str, ...], str] def __init__( self, @@ -52,8 +53,8 @@ def __init__( ) def _get_dep_values( - self, used_task_invocations: Mapping[str, Tuple[str, ...]] - ) -> Dict[str, str]: + self, used_task_invocations: Mapping[str, tuple[str, ...]] + ) -> dict[str, str]: """ Get env vars from upstream tasks declared via the uses option. """ @@ -62,7 +63,7 @@ def _get_dep_values( for var_name, invocation in used_task_invocations.items() } - def save_task_output(self, invocation: Tuple[str, ...], captured_stdout: bytes): + def save_task_output(self, invocation: tuple[str, ...], captured_stdout: bytes): """ Store the stdout data from a task so that it can be reused by other tasks """ @@ -76,7 +77,7 @@ def save_task_output(self, invocation: Tuple[str, ...], captured_stdout: bytes): else: raise - def get_task_output(self, invocation: Tuple[str, ...]): + def get_task_output(self, invocation: tuple[str, ...]): """ Get the stored stdout data from a task so that it can be reused by other tasks @@ -87,7 +88,7 @@ def get_task_output(self, invocation: Tuple[str, ...]): def get_executor( self, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], env: "EnvVarsManager", working_dir: Path, *, diff --git a/poethepoet/env/cache.py b/poethepoet/env/cache.py index 7a0924796..815b2239b 100644 --- a/poethepoet/env/cache.py +++ b/poethepoet/env/cache.py @@ -1,6 +1,6 @@ from os import environ from pathlib import Path -from typing import TYPE_CHECKING, Dict, Optional, Union +from typing import TYPE_CHECKING, Optional, Union from ..exceptions import ExecutionError @@ -11,7 +11,7 @@ class EnvFileCache: - _cache: Dict[str, Dict[str, str]] = {} + _cache: dict[str, dict[str, str]] = {} _ui: Optional["PoeUi"] _project_dir: Path @@ -19,7 +19,7 @@ def __init__(self, project_dir: Path, ui: Optional["PoeUi"]): self._project_dir = project_dir self._ui = ui - def get(self, envfile: Union[str, Path]) -> Dict[str, str]: + def get(self, envfile: Union[str, Path]) -> dict[str, str]: """ Parse, cache, and return the environment variables from the envfile at the given path. The path is used as the cache key. diff --git a/poethepoet/env/manager.py b/poethepoet/env/manager.py index 566678086..250d3cb70 100644 --- a/poethepoet/env/manager.py +++ b/poethepoet/env/manager.py @@ -1,6 +1,7 @@ import os +from collections.abc import Mapping from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union from .template import apply_envvars_to_template @@ -13,7 +14,7 @@ class EnvVarsManager(Mapping): _config: "PoeConfig" _ui: Optional["PoeUi"] - _vars: Dict[str, str] + _vars: dict[str, str] envfiles: "EnvFileCache" def __init__( # TODO: check if we still need all these args! @@ -77,7 +78,7 @@ def set(self, key: str, value: str): def apply_env_config( self, - envfile: Optional[Union[str, List[str]]], + envfile: Optional[Union[str, list[str]]], config_env: Optional[Mapping[str, Union[str, Mapping[str, str]]]], config_dir: Path, config_working_dir: Path, @@ -123,7 +124,7 @@ def apply_env_config( def update(self, env_vars: Mapping[str, Any]): # ensure all values are strings - str_vars: Dict[str, str] = {} + str_vars: dict[str, str] = {} for key, value in env_vars.items(): if isinstance(value, list): str_vars[key] = " ".join(str(item) for item in value) diff --git a/poethepoet/env/parse.py b/poethepoet/env/parse.py index 65ff7c514..f25576d5c 100644 --- a/poethepoet/env/parse.py +++ b/poethepoet/env/parse.py @@ -1,6 +1,7 @@ import re +from collections.abc import Iterable, Sequence from enum import Enum -from typing import Iterable, Optional, Sequence +from typing import Optional class ParseError(ValueError): diff --git a/poethepoet/env/template.py b/poethepoet/env/template.py index f08d3437d..627daae52 100644 --- a/poethepoet/env/template.py +++ b/poethepoet/env/template.py @@ -1,5 +1,5 @@ import re -from typing import Mapping +from collections.abc import Mapping _SHELL_VAR_PATTERN = re.compile( # Matches shell variable patterns, distinguishing escaped examples (to be ignored) diff --git a/poethepoet/executor/base.py b/poethepoet/executor/base.py index 71c7acf23..a1402a011 100644 --- a/poethepoet/executor/base.py +++ b/poethepoet/executor/base.py @@ -1,20 +1,9 @@ import os import shutil import sys +from collections.abc import Mapping, MutableMapping, Sequence from pathlib import Path -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - Mapping, - MutableMapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union from ..exceptions import ConfigValidationError, ExecutionError, PoeException @@ -50,12 +39,12 @@ class PoeExecutor(metaclass=MetaPoeExecutor): working_dir: Optional[Path] - __executor_types: ClassVar[Dict[str, Type["PoeExecutor"]]] = {} + __executor_types: ClassVar[dict[str, type["PoeExecutor"]]] = {} __key__: ClassVar[Optional[str]] = None def __init__( self, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], context: "RunContext", options: Mapping[str, str], env: "EnvVarsManager", @@ -88,7 +77,7 @@ def works_with_context(cls, context: "RunContext") -> bool: @classmethod def get( cls, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], context: "RunContext", executor_config: Mapping[str, str], env: "EnvVarsManager", @@ -268,7 +257,7 @@ def handle_sigint(signum, _frame): return proc.returncode @classmethod - def validate_config(cls, config: Dict[str, Any]): + def validate_config(cls, config: dict[str, Any]): if "type" not in config: raise ConfigValidationError( "Missing required key 'type' from executor option", @@ -294,7 +283,7 @@ def validate_config(cls, config: Dict[str, Any]): cls.__executor_types[executor_type].validate_executor_config(config) @classmethod - def validate_executor_config(cls, config: Dict[str, Any]): + def validate_executor_config(cls, config: dict[str, Any]): """To be overridden by subclasses if they accept options""" extra_options = set(config.keys()) - {"type"} if extra_options: diff --git a/poethepoet/executor/poetry.py b/poethepoet/executor/poetry.py index 2e496639a..f1336a663 100644 --- a/poethepoet/executor/poetry.py +++ b/poethepoet/executor/poetry.py @@ -1,6 +1,7 @@ +from collections.abc import Sequence from os import environ from pathlib import Path -from typing import TYPE_CHECKING, Dict, Optional, Sequence, Type +from typing import TYPE_CHECKING, Optional from ..exceptions import ExecutionError from .base import PoeExecutor @@ -16,7 +17,7 @@ class PoetryExecutor(PoeExecutor): """ __key__ = "poetry" - __options__: Dict[str, Type] = {} + __options__: dict[str, type] = {} @classmethod def works_with_context(cls, context: "RunContext") -> bool: diff --git a/poethepoet/executor/simple.py b/poethepoet/executor/simple.py index e81309f8c..1a8e350eb 100644 --- a/poethepoet/executor/simple.py +++ b/poethepoet/executor/simple.py @@ -1,5 +1,3 @@ -from typing import Dict, Type - from .base import PoeExecutor @@ -9,4 +7,4 @@ class SimpleExecutor(PoeExecutor): """ __key__ = "simple" - __options__: Dict[str, Type] = {} + __options__: dict[str, type] = {} diff --git a/poethepoet/executor/virtualenv.py b/poethepoet/executor/virtualenv.py index 9f6587723..787bf3332 100644 --- a/poethepoet/executor/virtualenv.py +++ b/poethepoet/executor/virtualenv.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Type +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Optional from ..exceptions import ConfigValidationError, ExecutionError from .base import PoeExecutor @@ -14,7 +15,7 @@ class VirtualenvExecutor(PoeExecutor): """ __key__ = "virtualenv" - __options__: Dict[str, Type] = {"location": str} + __options__: dict[str, type] = {"location": str} @classmethod def works_with_context(cls, context: "RunContext") -> bool: @@ -74,7 +75,7 @@ def _resolve_virtualenv(self) -> "Virtualenv": ) @classmethod - def validate_executor_config(cls, config: Dict[str, Any]): + def validate_executor_config(cls, config: dict[str, Any]): """ Validate that location is a string if given and no other options are given. """ diff --git a/poethepoet/helpers/command/__init__.py b/poethepoet/helpers/command/__init__.py index 9876ff960..3a67c6ae0 100644 --- a/poethepoet/helpers/command/__init__.py +++ b/poethepoet/helpers/command/__init__.py @@ -1,15 +1,7 @@ import re +from collections.abc import Iterable, Iterator, Mapping from glob import escape -from typing import ( - TYPE_CHECKING, - Iterable, - Iterator, - List, - Mapping, - Optional, - Tuple, - cast, -) +from typing import TYPE_CHECKING, Optional, cast from .ast import Comment @@ -33,7 +25,7 @@ def resolve_command_tokens( lines: Iterable["Line"], env: Mapping[str, str], config: Optional["ParseConfig"] = None, -) -> Iterator[Tuple[str, bool]]: +) -> Iterator[tuple[str, bool]]: """ Generates a sequence of tokens, and indicates for each whether it includes glob patterns that are not escaped or quoted. In case there are glob patterns in the @@ -72,7 +64,7 @@ def finalize_token(token_parts): continue # For each token part indicate whether it is a glob - token_parts: List[Tuple[str, bool]] = [] + token_parts: list[tuple[str, bool]] = [] for segment in word: for element in segment: if isinstance(element, ParamExpansion): diff --git a/poethepoet/helpers/command/ast.py b/poethepoet/helpers/command/ast.py index 0373f5cf1..ee4b432d6 100644 --- a/poethepoet/helpers/command/ast.py +++ b/poethepoet/helpers/command/ast.py @@ -20,7 +20,8 @@ DOUBLE_QUOTED_CONTENT : /([^\$"]|\[\$"])+/ """ -from typing import Iterable, List, Literal, Optional, Tuple, Union, cast +from collections.abc import Iterable +from typing import Literal, Optional, Union, cast from .ast_core import ContentNode, ParseConfig, ParseCursor, ParseError, SyntaxNode @@ -32,7 +33,7 @@ class SingleQuotedText(ContentNode): def _parse(self, chars: ParseCursor): - content: List[str] = [] + content: list[str] = [] for char in chars: if char == "'": self._content = "".join(content) @@ -44,7 +45,7 @@ def _parse(self, chars: ParseCursor): class DoubleQuotedText(ContentNode): def _parse(self, chars: ParseCursor): - content: List[str] = [] + content: list[str] = [] for char in chars: if char == "\\": # backslash is only special if escape is necessary @@ -63,7 +64,7 @@ def _parse(self, chars: ParseCursor): class UnquotedText(ContentNode): def _parse(self, chars: ParseCursor): - content: List[str] = [] + content: list[str] = [] for char in chars: if char == "\\": # Backslash is always an escape when outside of quotes @@ -171,7 +172,7 @@ def _parse(self, chars: ParseCursor): if char == "[": # Match pattern [groups] - group_chars: List[str] = [] + group_chars: list[str] = [] chars.take() for char in chars: if char == "]": @@ -205,7 +206,7 @@ def param_name(self) -> str: def _parse(self, chars: ParseCursor): assert chars.take() == "$" - param: List[str] = [] + param: list[str] = [] if chars.peek() == "{": chars.take() for char in chars: @@ -356,7 +357,7 @@ def __consume_unquoted(self, chars): class Word(SyntaxNode[Segment]): @property - def segments(self) -> Tuple[Segment, ...]: + def segments(self) -> tuple[Segment, ...]: return tuple(self._children) def _parse(self, chars: ParseCursor): @@ -378,7 +379,7 @@ class Line(SyntaxNode[Union[Word, Comment]]): _terminator: str @property - def words(self) -> Tuple[Word, ...]: + def words(self) -> tuple[Word, ...]: if self._children and isinstance(self._children[-1], Comment): return tuple(cast(Iterable[Word], self._children[:-1])) return tuple(cast(Iterable[Word], self._children)) diff --git a/poethepoet/helpers/command/ast_core.py b/poethepoet/helpers/command/ast_core.py index e9e7d6968..489cbe6ad 100644 --- a/poethepoet/helpers/command/ast_core.py +++ b/poethepoet/helpers/command/ast_core.py @@ -4,18 +4,8 @@ """ from abc import ABC, abstractmethod -from typing import ( - IO, - Dict, - Generic, - Iterator, - List, - Optional, - Tuple, - Type, - TypeVar, - cast, -) +from collections.abc import Iterator +from typing import IO, Generic, Optional, TypeVar, cast class ParseCursor: @@ -30,7 +20,7 @@ class ParseCursor: _line: int _position: int _source: Iterator[str] - _pushback_stack: List[str] + _pushback_stack: list[str] def __init__(self, source: Iterator[str]): self._source = source @@ -88,18 +78,18 @@ def __bool__(self): class ParseConfig: - substitute_nodes: Dict[Type["AstNode"], Type["AstNode"]] + substitute_nodes: dict[type["AstNode"], type["AstNode"]] line_separators: str def __init__( self, - substitute_nodes: Optional[Dict[Type["AstNode"], Type["AstNode"]]] = None, + substitute_nodes: Optional[dict[type["AstNode"], type["AstNode"]]] = None, line_separators="", ): self.substitute_nodes = substitute_nodes or {} self.line_separators = line_separators - def resolve_node_cls(self, klass: Type["AstNode"]) -> Type["AstNode"]: + def resolve_node_cls(self, klass: type["AstNode"]) -> type["AstNode"]: return self.substitute_nodes.get(klass, klass) @@ -130,17 +120,17 @@ def __len__(self): class SyntaxNode(AstNode, Generic[T]): - _children: List[T] + _children: list[T] - def get_child_node_cls(self, node_type: Type[AstNode]) -> Type[T]: + def get_child_node_cls(self, node_type: type[AstNode]) -> type[T]: """ Apply Node class substitution for the given node AstNode if specified in the ParseConfig. """ - return cast(Type[T], self.config.resolve_node_cls(node_type)) + return cast(type[T], self.config.resolve_node_cls(node_type)) @property - def children(self) -> Tuple["SyntaxNode", ...]: + def children(self) -> tuple["SyntaxNode", ...]: return tuple(getattr(self, "_children", tuple())) def pretty(self, indent: int = 0, increment: int = 4): diff --git a/poethepoet/helpers/git.py b/poethepoet/helpers/git.py index f76767742..d0a60dcf1 100644 --- a/poethepoet/helpers/git.py +++ b/poethepoet/helpers/git.py @@ -1,7 +1,7 @@ import shutil from pathlib import Path from subprocess import PIPE, Popen -from typing import Optional, Tuple +from typing import Optional class GitRepo: @@ -55,7 +55,7 @@ def _resolve_main_path(self) -> Optional[Path]: return Path(captured_stdout.decode().strip().split("\n")[0]) return None - def _exec(self, *args: str) -> Tuple[Popen, bytes]: + def _exec(self, *args: str) -> tuple[Popen, bytes]: proc = Popen( ["git", *args], cwd=self._seed_path, diff --git a/poethepoet/helpers/python.py b/poethepoet/helpers/python.py index ab78dae5c..0c2be6a6b 100644 --- a/poethepoet/helpers/python.py +++ b/poethepoet/helpers/python.py @@ -5,19 +5,8 @@ import ast import re -import sys -from typing import ( - Any, - Collection, - Container, - Dict, - Iterator, - List, - NamedTuple, - Optional, - Tuple, - cast, -) +from collections.abc import Collection, Container, Iterator +from typing import Any, NamedTuple, Optional, cast from ..exceptions import ExpressionParseError @@ -75,7 +64,7 @@ } -Substitution = Tuple[Tuple[int, int], str] +Substitution = tuple[tuple[int, int], str] class FunctionCall(NamedTuple): @@ -85,8 +74,8 @@ class FunctionCall(NamedTuple): expression: str function_ref: str - referenced_args: Tuple[str, ...] = tuple() - referenced_globals: Tuple[str, ...] = tuple() + referenced_args: tuple[str, ...] = tuple() + referenced_globals: tuple[str, ...] = tuple() @classmethod def parse( @@ -100,9 +89,9 @@ def parse( root_node = cast(ast.Call, parse_and_validate(source, True, "script")) name_nodes = _validate_nodes_and_get_names(root_node, source) - substitutions: List[Substitution] = [] - referenced_args: List[str] = [] - referenced_globals: List[str] = [] + substitutions: list[Substitution] = [] + referenced_args: list[str] = [] + referenced_globals: list[str] = [] for node in name_nodes: if node.id in arguments: substitutions.append( @@ -114,7 +103,7 @@ def parse( else: raise ExpressionParseError( "Invalid variable reference in script: " - + _get_name_source_segment(source, node) + f"{ast.get_source_segment(source, node)}" ) # Prefix references to arguments with args_prefix @@ -151,7 +140,7 @@ def resolve_expression( root_node = parse_and_validate(source, False, "expr") name_nodes = _validate_nodes_and_get_names(root_node, source) - substitutions: List[Substitution] = [] + substitutions: list[Substitution] = [] for node in name_nodes: if node.id in arguments: substitutions.append( @@ -160,7 +149,7 @@ def resolve_expression( elif node.id not in _ALLOWED_BUILTINS and node.id not in allowed_vars: raise ExpressionParseError( "Invalid variable reference in expr: " - + _get_name_source_segment(source, node) + f"{ast.get_source_segment(source, node)}" ) # Prefix references to arguments with args_prefix @@ -204,7 +193,7 @@ def parse_and_validate( return root_node -def format_class(attrs: Optional[Dict[str, Any]], classname: str = "__args") -> str: +def format_class(attrs: Optional[dict[str, Any]], classname: str = "__args") -> str: """ Generates source for a python class with the entries of the given dictionary represented as class attributes. Output is a one-liner. @@ -313,13 +302,13 @@ def _validate_nodes_and_get_names( ) -def _apply_substitutions(content: str, subs: List[Substitution]) -> str: +def _apply_substitutions(content: str, subs: list[Substitution]) -> str: """ Returns a copy of content with all of the substitutions applied. Uses a single pass for efficiency. """ cursor = 0 - segments: List[str] = [] + segments: list[str] = [] for (start, end), replacement in sorted(subs, key=lambda x: x[0][0]): in_between = content[cursor:start] @@ -357,37 +346,6 @@ def _get_name_node_abs_range(source: str, node: ast.Name): return (total_start_chars_offset, total_start_chars_offset + len(name_content)) -def _get_name_source_segment(source: str, node: ast.Name): - """ - Before python 3.8 the ast module didn't allow for easily identifying the source - segment of a node, so this function provides this functionality specifically for - name nodes as needed here. - - The fallback logic is specialised for name nodes which cannot span multiple lines - and must be valid identifiers. It is expected to be correct in all cases, and - performant in common cases. - """ - if sys.version_info >= (3, 8): - return ast.get_source_segment(source, node) - - partial_result = ( - re.split(r"(?:\r\n|\r|\n)", source)[node.lineno - 1] - .encode()[node.col_offset :] - .decode() - ) - - # The name probably extends to the first ascii char outside of [a-zA-Z\d_] - # regex will always match with valid arguments to this function - # type: ignore[union-attr] - partial_result = re.match(IDENTIFIER_PATTERN, partial_result).group() - - # This bit is a nasty hack, but probably always gets skipped - while not partial_result.isidentifier() and partial_result: - partial_result = partial_result[:-1] - - return partial_result - - def _clean_linebreaks(expression: str): """ Strip out any new lines because they can be problematic on windows diff --git a/poethepoet/options/__init__.py b/poethepoet/options/__init__.py index d097457d1..67e69e462 100644 --- a/poethepoet/options/__init__.py +++ b/poethepoet/options/__init__.py @@ -1,7 +1,10 @@ from __future__ import annotations from keyword import iskeyword -from typing import Any, Mapping, Sequence, get_type_hints +from typing import TYPE_CHECKING, Any, get_type_hints + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence from ..exceptions import ConfigValidationError from .annotations import TypeAnnotation @@ -186,7 +189,9 @@ def get_fields(cls) -> dict[str, TypeAnnotation]: annotations = {} for base_cls in cls.__bases__: annotations.update(get_type_hints(base_cls)) - annotations.update(get_type_hints(cls)) + annotations.update( + get_type_hints(cls, globalns=TypeAnnotation.get_type_hint_globals()) + ) cls.__annotations = { key: TypeAnnotation.parse(type_) diff --git a/poethepoet/options/annotations.py b/poethepoet/options/annotations.py index 7b9926378..67ef70e03 100644 --- a/poethepoet/options/annotations.py +++ b/poethepoet/options/annotations.py @@ -1,14 +1,12 @@ from __future__ import annotations -import collections import sys +import typing +from collections.abc import Iterator, Mapping, MutableMapping, Sequence from typing import ( Any, - Iterator, Literal, - Mapping, - MutableMapping, - Sequence, + Optional, Union, get_args, get_origin, @@ -22,6 +20,22 @@ class TypeAnnotation: enforcing pythonic type annotations for PoeOptions. """ + @classmethod + def get_type_hint_globals(cls): + return { + "Any": Any, + "Optional": Optional, + "Mapping": Mapping, + "MutableMapping": MutableMapping, + "typing.Mapping": typing.Mapping, + "typing.MutableMapping": typing.MutableMapping, + "Sequence": Sequence, + "typing.Sequence": typing.Sequence, + "Literal": Literal, + "Union": Union, + "TypeAnnotation": cls, + } + @staticmethod def parse(annotation: Any): origin = get_origin(annotation) @@ -33,8 +47,8 @@ def parse(annotation: Any): dict, Mapping, MutableMapping, - collections.abc.Mapping, - collections.abc.MutableMapping, + typing.Mapping, + typing.MutableMapping, ): return DictType(annotation) @@ -42,7 +56,7 @@ def parse(annotation: Any): list, tuple, Sequence, - collections.abc.Sequence, + typing.Sequence, ): return ListType(annotation) @@ -89,7 +103,7 @@ class DictType(TypeAnnotation): def __init__(self, annotation: Any): super().__init__(annotation) if args := get_args(annotation): - assert args[0] == str + assert args[0] is str self._value_type = TypeAnnotation.parse(get_args(annotation)[1]) else: self._value_type = AnyType() diff --git a/poethepoet/plugin.py b/poethepoet/plugin.py index c8a9333f5..31b70b774 100644 --- a/poethepoet/plugin.py +++ b/poethepoet/plugin.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, List +from typing import TYPE_CHECKING, Any from cleo.commands.command import Command from cleo.events.console_command_event import ConsoleCommandEvent @@ -183,7 +183,7 @@ def _register_command( ) def _register_command_event_handler( - self, application: Application, hooks: Dict[str, str] + self, application: Application, hooks: dict[str, str] ): if not hooks: return @@ -210,7 +210,7 @@ def _register_command_event_handler( ) def _get_command_event_handler( - self, hooks: Dict[str, str], application: Application + self, hooks: dict[str, str], application: Application ): def command_event_handler( event: ConsoleCommandEvent, @@ -235,7 +235,7 @@ def command_event_handler( return command_event_handler - def _monkey_patch_cleo(self, prefix: str, task_names: List[str]): + def _monkey_patch_cleo(self, prefix: str, task_names: list[str]): """ Cleo is quite opinionated about CLI structure and loose about how options are used, and so doesn't currently support individual commands having their own way @@ -279,7 +279,7 @@ def _run(self, io): cleo.application.Application._run = _run -def _index_of_first_non_option(tokens: List[str]): +def _index_of_first_non_option(tokens: list[str]): """ Find the index of the first token that doesn't start with `-` Returns len(tokens) if none is found. diff --git a/poethepoet/task/args.py b/poethepoet/task/args.py index d281c065d..d8db3ae96 100644 --- a/poethepoet/task/args.py +++ b/poethepoet/task/args.py @@ -1,9 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, Mapping, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, Union if TYPE_CHECKING: from argparse import ArgumentParser + from collections.abc import Mapping, Sequence from ..env.manager import EnvVarsManager diff --git a/poethepoet/task/base.py b/poethepoet/task/base.py index e3a87fecf..bde3b1a37 100644 --- a/poethepoet/task/base.py +++ b/poethepoet/task/base.py @@ -1,22 +1,9 @@ import re import sys +from collections.abc import Iterator, Mapping, Sequence from os import environ from pathlib import Path -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - Iterator, - List, - Mapping, - NamedTuple, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, Optional, Union from ..config.primitives import EmptyDict, EnvDefault from ..exceptions import ConfigValidationError, PoeException @@ -59,7 +46,7 @@ def __init__(cls, *args): class TaskSpecFactory: - __cache: Dict[str, "PoeTask.TaskSpec"] + __cache: dict[str, "PoeTask.TaskSpec"] config: "PoeConfig" def __init__(self, config: "PoeConfig"): @@ -167,7 +154,7 @@ def from_task(cls, parent_task: "PoeTask"): class PoeTask(metaclass=MetaPoeTask): __key__: ClassVar[str] - __content_type__: ClassVar[Type] = str + __content_type__: ClassVar[type] = str class TaskOptions(PoeOptions): args: Optional[Union[dict, list]] = None @@ -190,7 +177,7 @@ class TaskSpec: name: str content: TaskContent options: "PoeTask.TaskOptions" - task_type: Type["PoeTask"] + task_type: type["PoeTask"] source: "ConfigPartition" parent: Optional["PoeTask.TaskSpec"] = None @@ -199,7 +186,7 @@ class TaskSpec: def __init__( self, name: str, - task_def: Dict[str, Any], + task_def: dict[str, Any], factory: TaskSpecFactory, source: "ConfigPartition", parent: Optional["PoeTask.TaskSpec"] = None, @@ -210,7 +197,7 @@ def __init__( self.source = source self.parent = parent - def _parse_options(self, task_def: Dict[str, Any]): + def _parse_options(self, task_def: dict[str, Any]): try: return next( self.task_type.TaskOptions.parse( @@ -259,7 +246,7 @@ def args(self) -> Optional["PoeTaskArgs"]: def create_task( self, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], ctx: TaskContext, capture_stdout: Union[str, bool] = False, ) -> "PoeTask": @@ -349,17 +336,17 @@ def _task_validations(self, config: "PoeConfig", task_specs: TaskSpecFactory): spec: TaskSpec ctx: TaskContext - _parsed_args: Optional[Tuple[Dict[str, str], Tuple[str, ...]]] = None + _parsed_args: Optional[tuple[dict[str, str], tuple[str, ...]]] = None - __task_types: ClassVar[Dict[str, Type["PoeTask"]]] = {} + __task_types: ClassVar[dict[str, type["PoeTask"]]] = {} __upstream_invocations: Optional[ - Dict[str, Union[List[Tuple[str, ...]], Dict[str, Tuple[str, ...]]]] + dict[str, Union[list[tuple[str, ...]], dict[str, tuple[str, ...]]]] ] = None def __init__( self, spec: TaskSpec, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], ctx: TaskContext, capture_stdout: Union[str, bool] = False, ): @@ -374,7 +361,7 @@ def name(self): return self.spec.name @classmethod - def lookup_task_spec_cls(cls, task_key: str) -> Type[TaskSpec]: + def lookup_task_spec_cls(cls, task_key: str) -> type[TaskSpec]: return cls.__task_types[task_key].TaskSpec @classmethod @@ -406,7 +393,7 @@ def resolve_task_type( def _parse_named_args( self, extra_args: Sequence[str], env: "EnvVarsManager" - ) -> Optional[Dict[str, str]]: + ) -> Optional[dict[str, str]]: if task_args := self.spec.args: return task_args.parse(extra_args, env, self.ctx.ui.program_name) @@ -414,7 +401,7 @@ def _parse_named_args( def get_parsed_arguments( self, env: "EnvVarsManager" - ) -> Tuple[Dict[str, str], Tuple[str, ...]]: + ) -> tuple[dict[str, str], tuple[str, ...]]: if self._parsed_args is None: all_args = self.invocation[1:] @@ -517,7 +504,7 @@ def get_working_dir( def iter_upstream_tasks( self, context: "RunContext" - ) -> Iterator[Tuple[str, "PoeTask"]]: + ) -> Iterator[tuple[str, "PoeTask"]]: invocations = self._get_upstream_invocations(context) for invocation in invocations["deps"]: yield ("", self._instantiate_dep(invocation, capture_stdout=False)) @@ -553,7 +540,7 @@ def _get_upstream_invocations(self, context: "RunContext"): return self.__upstream_invocations def _instantiate_dep( - self, invocation: Tuple[str, ...], capture_stdout: bool + self, invocation: tuple[str, ...], capture_stdout: bool ) -> "PoeTask": return self.ctx.specs.get(invocation[0]).create_task( invocation=invocation, @@ -573,7 +560,7 @@ def has_deps(self) -> bool: @classmethod def is_task_type( - cls, task_def_key: str, content_type: Optional[Type] = None + cls, task_def_key: str, content_type: Optional[type] = None ) -> bool: """ Checks whether the given key identifies a known task type. @@ -586,7 +573,7 @@ def is_task_type( ) @classmethod - def get_task_types(cls, content_type: Optional[Type] = None) -> Tuple[str, ...]: + def get_task_types(cls, content_type: Optional[type] = None) -> tuple[str, ...]: if content_type: return tuple( task_type diff --git a/poethepoet/task/expr.py b/poethepoet/task/expr.py index 969a142c3..19f731e78 100644 --- a/poethepoet/task/expr.py +++ b/poethepoet/task/expr.py @@ -1,15 +1,6 @@ import re -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Iterable, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) +from collections.abc import Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional, Union from ..exceptions import ConfigValidationError, ExpressionParseError from .base import PoeTask @@ -102,10 +93,10 @@ def _handle_run( def parse_content( self, - args: Optional[Dict[str, Any]], + args: Optional[dict[str, Any]], env: "EnvVarsManager", imports=Iterable[str], - ) -> Tuple[str, Dict[str, str]]: + ) -> tuple[str, dict[str, str]]: """ Returns the expression to evaluate and the subset of env vars that it references @@ -145,7 +136,7 @@ def _substitute_env_vars(cls, content: str, env: Mapping[str, str]): # Spy on access to the env, so that instead of replacing template ${keys} with # the corresponding value, replace them with a python name and keep track of # referenced env vars. - accessed_vars: Dict[str, str] = {} + accessed_vars: dict[str, str] = {} def getitem_spy(obj: SpyDict, key: str, value: str): accessed_vars[key] = value diff --git a/poethepoet/task/graph.py b/poethepoet/task/graph.py index 18d8d6e56..4180c2bc1 100644 --- a/poethepoet/task/graph.py +++ b/poethepoet/task/graph.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict, List, Set, Tuple +from typing import TYPE_CHECKING from ..exceptions import CyclicDependencyError @@ -9,16 +9,16 @@ class TaskExecutionNode: task: "PoeTask" - direct_dependants: List["TaskExecutionNode"] - direct_dependencies: Set[Tuple[str, ...]] - path_dependants: Tuple[str, ...] + direct_dependants: list["TaskExecutionNode"] + direct_dependencies: set[tuple[str, ...]] + path_dependants: tuple[str, ...] capture_stdout: bool def __init__( self, task: "PoeTask", - direct_dependants: List["TaskExecutionNode"], - path_dependants: Tuple[str, ...], + direct_dependants: list["TaskExecutionNode"], + path_dependants: tuple[str, ...], capture_stdout: bool = False, ): self.task = task @@ -31,7 +31,7 @@ def is_source(self): return not self.task.has_deps() @property - def identifier(self) -> Tuple[str, ...]: + def identifier(self) -> tuple[str, ...]: return self.task.invocation @@ -47,9 +47,9 @@ class TaskExecutionGraph: _context: "RunContext" sink: TaskExecutionNode - sources: List[TaskExecutionNode] - captured_tasks: Dict[Tuple[str, ...], TaskExecutionNode] - uncaptured_tasks: Dict[Tuple[str, ...], TaskExecutionNode] + sources: list[TaskExecutionNode] + captured_tasks: dict[tuple[str, ...], TaskExecutionNode] + uncaptured_tasks: dict[tuple[str, ...], TaskExecutionNode] def __init__( self, @@ -65,7 +65,7 @@ def __init__( # Build graph self._resolve_node_deps(self.sink) - def get_execution_plan(self) -> List[List["PoeTask"]]: + def get_execution_plan(self) -> list[list["PoeTask"]]: """ Derive an execution plan from the DAG in terms of stages consisting of tasks that could theoretically be parallelized. @@ -73,7 +73,7 @@ def get_execution_plan(self) -> List[List["PoeTask"]]: # TODO: if we parallelize tasks then this should be modified to support lazy # scheduling - stages: List[List[TaskExecutionNode]] = [self.sources] + stages: list[list[TaskExecutionNode]] = [self.sources] visited = {source.identifier for source in self.sources} while True: diff --git a/poethepoet/task/script.py b/poethepoet/task/script.py index 4009e3adb..66a660f73 100644 --- a/poethepoet/task/script.py +++ b/poethepoet/task/script.py @@ -1,5 +1,5 @@ import shlex -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, Optional from ..exceptions import ConfigValidationError, ExpressionParseError from .base import PoeTask @@ -108,8 +108,8 @@ def _handle_run( ).execute(cmd, use_exec=self.spec.options.get("use_exec", False)) def parse_content( - self, args: Optional[Dict[str, Any]] - ) -> Tuple[str, "FunctionCall"]: + self, args: Optional[dict[str, Any]] + ) -> tuple[str, "FunctionCall"]: """ Returns the module to load, and the function call to execute. diff --git a/poethepoet/task/sequence.py b/poethepoet/task/sequence.py index dbabc11bf..4121268ea 100644 --- a/poethepoet/task/sequence.py +++ b/poethepoet/task/sequence.py @@ -1,16 +1,5 @@ -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - List, - Literal, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, Union from ..exceptions import ConfigValidationError, ExecutionError, PoeException from .base import PoeTask, TaskContext @@ -27,10 +16,10 @@ class SequenceTask(PoeTask): A task consisting of a sequence of other tasks """ - content: List[Union[str, Dict[str, Any]]] + content: list[Union[str, dict[str, Any]]] __key__ = "sequence" - __content_type__: ClassVar[Type] = list + __content_type__: ClassVar[type] = list class TaskOptions(PoeTask.TaskOptions): ignore_fail: Literal[True, False, "return_zero", "return_non_zero"] = False @@ -57,7 +46,7 @@ class TaskSpec(PoeTask.TaskSpec): def __init__( self, name: str, - task_def: Dict[str, Any], + task_def: dict[str, Any], factory: "TaskSpecFactory", source: "ConfigPartition", parent: Optional["PoeTask.TaskSpec"] = None, @@ -116,7 +105,7 @@ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"): def __init__( self, spec: TaskSpec, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], ctx: TaskContext, capture_stdout: bool = False, ): @@ -146,7 +135,7 @@ def _handle_run( context.multistage = True ignore_fail = self.spec.options.ignore_fail - non_zero_subtasks: List[str] = list() + non_zero_subtasks: list[str] = list() for subtask in self.subtasks: try: task_result = subtask.run(context=context, parent_env=env) diff --git a/poethepoet/task/shell.py b/poethepoet/task/shell.py index ae43e2a4a..6cd4cfe3d 100644 --- a/poethepoet/task/shell.py +++ b/poethepoet/task/shell.py @@ -1,13 +1,7 @@ import re +from collections.abc import Sequence from os import environ -from typing import ( - TYPE_CHECKING, - List, - Optional, - Sequence, - Tuple, - Union, -) +from typing import TYPE_CHECKING, Optional, Union from ..exceptions import ConfigValidationError, PoeException from .base import PoeTask @@ -96,15 +90,15 @@ def _handle_run( interpreter_cmd, input=content.encode() ) - def _get_interpreter_config(self) -> Tuple[str, ...]: - result: Union[str, Tuple[str, ...]] = self.spec.options.get( + def _get_interpreter_config(self) -> tuple[str, ...]: + result: Union[str, tuple[str, ...]] = self.spec.options.get( "interpreter", self.ctx.config.shell_interpreter ) if isinstance(result, str): return (result,) return tuple(result) - def resolve_interpreter_cmd(self) -> Optional[List[str]]: + def resolve_interpreter_cmd(self) -> Optional[list[str]]: """ Return a formatted command for the first specified interpreter that can be located. diff --git a/poethepoet/task/switch.py b/poethepoet/task/switch.py index e553c2110..8675de33d 100644 --- a/poethepoet/task/switch.py +++ b/poethepoet/task/switch.py @@ -1,20 +1,11 @@ -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Dict, - Literal, - MutableMapping, - Optional, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, Union from ..exceptions import ConfigValidationError, ExecutionError, PoeException from .base import PoeTask, TaskContext if TYPE_CHECKING: + from collections.abc import MutableMapping + from ..config import ConfigPartition, PoeConfig from ..context import RunContext from ..env.manager import EnvVarsManager @@ -32,7 +23,7 @@ class SwitchTask(PoeTask): """ __key__ = "switch" - __content_type__: ClassVar[Type] = list + __content_type__: ClassVar[type] = list class TaskOptions(PoeTask.TaskOptions): control: Union[str, dict] @@ -66,13 +57,13 @@ def normalize( class TaskSpec(PoeTask.TaskSpec): control_task_spec: PoeTask.TaskSpec - case_task_specs: Tuple[Tuple[Tuple[Any, ...], PoeTask.TaskSpec], ...] + case_task_specs: tuple[tuple[tuple[Any, ...], PoeTask.TaskSpec], ...] options: "SwitchTask.TaskOptions" def __init__( self, name: str, - task_def: Dict[str, Any], + task_def: dict[str, Any], factory: "TaskSpecFactory", source: "ConfigPartition", parent: Optional["PoeTask.TaskSpec"] = None, @@ -154,19 +145,19 @@ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"): spec: TaskSpec control_task: PoeTask - switch_tasks: Dict[str, PoeTask] + switch_tasks: dict[str, PoeTask] def __init__( self, spec: TaskSpec, - invocation: Tuple[str, ...], + invocation: tuple[str, ...], ctx: TaskContext, capture_stdout: bool = False, ): super().__init__(spec, invocation, ctx, capture_stdout) control_task_name = f"{spec.name}[__control__]" - control_invocation: Tuple[str, ...] = (control_task_name,) + control_invocation: tuple[str, ...] = (control_task_name,) options = self.spec.options if options.get("args"): control_invocation = (*control_invocation, *invocation[1:]) @@ -179,7 +170,7 @@ def __init__( self.switch_tasks = {} for case_keys, case_spec in spec.case_task_specs: - task_invocation: Tuple[str, ...] = (f"{spec.name}[{','.join(case_keys)}]",) + task_invocation: tuple[str, ...] = (f"{spec.name}[{','.join(case_keys)}]",) if options.get("args"): task_invocation = (*task_invocation, *invocation[1:]) diff --git a/poethepoet/ui.py b/poethepoet/ui.py index f36bdda88..9f5d35041 100644 --- a/poethepoet/ui.py +++ b/poethepoet/ui.py @@ -1,6 +1,7 @@ import os import sys -from typing import IO, TYPE_CHECKING, List, Mapping, Optional, Sequence, Tuple, Union +from collections.abc import Mapping, Sequence +from typing import IO, TYPE_CHECKING, Optional, Union from .__version__ import __version__ from .exceptions import ConfigValidationError, ExecutionError, PoeException @@ -176,7 +177,7 @@ def set_default_verbosity(self, default_verbosity: int): def print_help( self, tasks: Optional[ - Mapping[str, Tuple[str, Sequence[Tuple[Tuple[str, ...], str, str]]]] + Mapping[str, tuple[str, Sequence[tuple[tuple[str, ...], str, str]]]] ] = None, info: Optional[str] = None, error: Optional[PoeException] = None, @@ -186,7 +187,7 @@ def print_help( # Ignore verbosity mode if help flag is set verbosity = 0 if self["help"] else self.verbosity - result: List[Union[str, Sequence[str]]] = [] + result: list[Union[str, Sequence[str]]] = [] if verbosity >= 0: result.append( ( diff --git a/poethepoet/virtualenv.py b/poethepoet/virtualenv.py index 4987ecbaf..dcdb0113d 100644 --- a/poethepoet/virtualenv.py +++ b/poethepoet/virtualenv.py @@ -1,8 +1,8 @@ import os import shutil import sys +from collections.abc import Mapping from pathlib import Path -from typing import Dict, Mapping class Virtualenv: @@ -80,7 +80,7 @@ def valid(self) -> bool: ) ) - def get_env_vars(self, base_env: Mapping[str, str]) -> Dict[str, str]: + def get_env_vars(self, base_env: Mapping[str, str]) -> dict[str, str]: bin_dir = str(self.bin_dir()) # Revert path update from existing virtualenv if applicable path_var = os.environ.get("_OLD_VIRTUAL_PATH", "") or os.environ.get("PATH", "") diff --git a/poetry.lock b/poetry.lock index eac505582..2a5dafdc3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1805,28 +1805,29 @@ yaml = ["pyyaml (>=6.0.0)"] [[package]] name = "ruff" -version = "0.0.291" -description = "An extremely fast Python linter, written in Rust." +version = "0.8.0" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.291-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b97d0d7c136a85badbc7fd8397fdbb336e9409b01c07027622f28dcd7db366f2"}, - {file = "ruff-0.0.291-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6ab44ea607967171e18aa5c80335237be12f3a1523375fa0cede83c5cf77feb4"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04b384f2d36f00d5fb55313d52a7d66236531195ef08157a09c4728090f2ef0"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b727c219b43f903875b7503a76c86237a00d1a39579bb3e21ce027eec9534051"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87671e33175ae949702774071b35ed4937da06f11851af75cd087e1b5a488ac4"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b75f5801547f79b7541d72a211949754c21dc0705c70eddf7f21c88a64de8b97"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b09b94efdcd162fe32b472b2dd5bf1c969fcc15b8ff52f478b048f41d4590e09"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d5b56bc3a2f83a7a1d7f4447c54d8d3db52021f726fdd55d549ca87bca5d747"}, - {file = "ruff-0.0.291-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13f0d88e5f367b2dc8c7d90a8afdcfff9dd7d174e324fd3ed8e0b5cb5dc9b7f6"}, - {file = "ruff-0.0.291-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b3eeee1b1a45a247758ecdc3ab26c307336d157aafc61edb98b825cadb153df3"}, - {file = "ruff-0.0.291-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6c06006350c3bb689765d71f810128c9cdf4a1121fd01afc655c87bab4fb4f83"}, - {file = "ruff-0.0.291-py3-none-musllinux_1_2_i686.whl", hash = "sha256:fd17220611047de247b635596e3174f3d7f2becf63bd56301fc758778df9b629"}, - {file = "ruff-0.0.291-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5383ba67ad360caf6060d09012f1fb2ab8bd605ab766d10ca4427a28ab106e0b"}, - {file = "ruff-0.0.291-py3-none-win32.whl", hash = "sha256:1d5f0616ae4cdc7a938b493b6a1a71c8a47d0300c0d65f6e41c281c2f7490ad3"}, - {file = "ruff-0.0.291-py3-none-win_amd64.whl", hash = "sha256:8a69bfbde72db8ca1c43ee3570f59daad155196c3fbe357047cd9b77de65f15b"}, - {file = "ruff-0.0.291-py3-none-win_arm64.whl", hash = "sha256:d867384a4615b7f30b223a849b52104214442b5ba79b473d7edd18da3cde22d6"}, - {file = "ruff-0.0.291.tar.gz", hash = "sha256:c61109661dde9db73469d14a82b42a88c7164f731e6a3b0042e71394c1c7ceed"}, + {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"}, + {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"}, + {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"}, + {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"}, + {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"}, + {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"}, + {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"}, ] [[package]] @@ -2252,4 +2253,4 @@ poetry-plugin = ["poetry"] [metadata] lock-version = "2.0" python-versions = ">=3.9" -content-hash = "0ae49650c8ed639cf6e1b8ec55e130132bec8b11fcf523d28a280560fd48a424" +content-hash = "30709e109d8a57de421bf6c320326f415e2fe1a4abbc8e7a8af921a8340eefff" diff --git a/pyproject.toml b/pyproject.toml index 30081d0c7..a9c754c74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ mypy = "^1.1.1" pytest = "^7.1.2" pytest-cov = "^3.0.0" rstcheck = { version = "^6.2.4", python = "<4" } -ruff = "^0.0.291" +ruff = "^0.8.0" types-pyyaml = "^6.0.12.20240808" virtualenv = "^20.14.1" @@ -166,7 +166,7 @@ markers = [ ] -[tool.ruff] +[tool.ruff.lint] select = [ "E", # error "F", # pyflakes diff --git a/tests/conftest.py b/tests/conftest.py index d5753a379..bdd31ff46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,11 +4,12 @@ import sys import time import venv +from collections.abc import Mapping from contextlib import contextmanager from io import StringIO from pathlib import Path from subprocess import PIPE, Popen -from typing import Any, Dict, List, Mapping, NamedTuple, Optional +from typing import Any, NamedTuple, Optional import pytest import virtualenv @@ -77,7 +78,7 @@ def high_verbosity_project_path(): return PROJECT_ROOT.joinpath("tests", "fixtures", "high_verbosity") -@pytest.fixture() +@pytest.fixture def temp_file(tmp_path): # not using NamedTemporaryFile here because it doesn't work on windows tmpfilepath = tmp_path / "tmp_test_file" @@ -110,7 +111,7 @@ def assert_no_err(self): ) -@pytest.fixture() +@pytest.fixture def run_poe_subproc(projects, temp_file, tmp_path, is_windows): coverage_setup = ( "from coverage import Coverage;" @@ -137,7 +138,7 @@ def run_poe_subproc( cwd: Optional[str] = None, config: Optional[Mapping[str, Any]] = None, coverage: bool = not is_windows, - env: Optional[Dict[str, str]] = None, + env: Optional[dict[str, str]] = None, project: Optional[str] = None, ) -> PoeRunResult: if cwd is None: @@ -194,7 +195,7 @@ def run_poe_subproc( return run_poe_subproc -@pytest.fixture() +@pytest.fixture def run_poe(capsys, projects): def run_poe( *run_args: str, @@ -220,7 +221,7 @@ def run_poe( return run_poe -@pytest.fixture() +@pytest.fixture def run_poe_main(capsys, projects): def run_poe_main( *cli_args: str, @@ -245,7 +246,7 @@ def run_poe_main( def run_poetry(use_venv, poe_project_path): venv_location = poe_project_path / "tests" / "temp" / "poetry_venv" - def run_poetry(args: List[str], cwd: str, env: Optional[Dict[str, str]] = None): + def run_poetry(args: list[str], cwd: str, env: Optional[dict[str, str]] = None): venv = Virtualenv(venv_location) cmd = (venv.resolve_executable("python"), "-m", "poetry", *args) @@ -291,7 +292,7 @@ def esc_prefix(is_windows): @pytest.fixture(scope="session") def install_into_virtualenv(): - def install_into_virtualenv(location: Path, contents: List[str]): + def install_into_virtualenv(location: Path, contents: list[str]): venv = Virtualenv(location) Popen( (venv.resolve_executable("pip"), "install", *contents), @@ -308,7 +309,7 @@ def use_venv(install_into_virtualenv): @contextmanager def use_venv( location: Path, - contents: Optional[List[str]] = None, + contents: Optional[list[str]] = None, require_empty: bool = False, ): did_exist = location.is_dir() @@ -340,7 +341,7 @@ def use_virtualenv(install_into_virtualenv): @contextmanager def use_virtualenv( location: Path, - contents: Optional[List[str]] = None, + contents: Optional[list[str]] = None, require_empty: bool = False, ): did_exist = location.is_dir() @@ -385,7 +386,7 @@ def try_rm_dir(location: Path): def with_virtualenv_and_venv(use_venv, use_virtualenv): def with_virtualenv_and_venv( location: Path, - contents: Optional[List[str]] = None, + contents: Optional[list[str]] = None, ): with use_venv(location, contents, require_empty=True): yield @@ -396,7 +397,7 @@ def with_virtualenv_and_venv( return with_virtualenv_and_venv -@pytest.fixture() +@pytest.fixture def temp_pyproject(tmp_path): """Return function which generates pyproject.toml with the given content""" diff --git a/tests/test_executors.py b/tests/test_executors.py index 4f50643cf..0fe1d0e8d 100644 --- a/tests/test_executors.py +++ b/tests/test_executors.py @@ -19,7 +19,7 @@ def test_virtualenv_executor_fails_without_venv_dir(run_poe_subproc, projects): assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow def test_virtualenv_executor_activates_venv( run_poe_subproc, with_virtualenv_and_venv, projects ): @@ -33,7 +33,7 @@ def test_virtualenv_executor_activates_venv( assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow def test_virtualenv_executor_provides_access_to_venv_content( run_poe_subproc, with_virtualenv_and_venv, projects ): @@ -62,7 +62,7 @@ def test_virtualenv_executor_provides_access_to_venv_content( assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow def test_detect_venv( projects, run_poe_subproc, diff --git a/tests/test_ignore_fail.py b/tests/test_ignore_fail.py index 7c36d97d9..3b5405e0e 100644 --- a/tests/test_ignore_fail.py +++ b/tests/test_ignore_fail.py @@ -1,7 +1,7 @@ import pytest -@pytest.fixture() +@pytest.fixture def generate_pyproject(temp_pyproject): def generator(lvl1_ignore_fail=False, lvl2_ignore_fail=False): def fmt_ignore_fail(value): diff --git a/tests/test_poetry_plugin.py b/tests/test_poetry_plugin.py index 48a9d3f8d..2c6fd9c8a 100644 --- a/tests/test_poetry_plugin.py +++ b/tests/test_poetry_plugin.py @@ -19,7 +19,7 @@ def _setup_poetry_project_with_prefix(run_poetry, projects): run_poetry(["install"], cwd=projects["poetry_plugin/with_prefix"].parent) -@pytest.mark.slow() +@pytest.mark.slow def test_poetry_help(run_poetry, projects): result = run_poetry([], cwd=projects["poetry_plugin"]) assert result.stdout.startswith("Poetry (version ") @@ -33,7 +33,7 @@ def test_poetry_help(run_poetry, projects): os.environ.get("GITHUB_ACTIONS", "false") == "true", reason="Skipping test the doesn't seem to work in GitHub Actions lately", ) -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_task_with_cli_dependency(run_poetry, projects, is_windows): result = run_poetry( @@ -58,7 +58,7 @@ def test_task_with_cli_dependency(run_poetry, projects, is_windows): os.environ.get("GITHUB_ACTIONS", "false") == "true", reason="Skipping test the doesn't seem to work in GitHub Actions lately", ) -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_task_with_lib_dependency(run_poetry, projects): result = run_poetry(["poe", "cow-cheese"], cwd=projects["poetry_plugin"]) @@ -68,7 +68,7 @@ def test_task_with_lib_dependency(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_task_accepts_any_args(run_poetry, projects): result = run_poetry( @@ -81,7 +81,7 @@ def test_task_accepts_any_args(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_empty_prefix") def test_poetry_help_without_poe_command_prefix(run_poetry, projects): result = run_poetry([], cwd=projects["poetry_plugin/empty_prefix"].parent) @@ -91,7 +91,7 @@ def test_poetry_help_without_poe_command_prefix(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_empty_prefix") def test_running_tasks_without_poe_command_prefix(run_poetry, projects): result = run_poetry( @@ -104,7 +104,7 @@ def test_running_tasks_without_poe_command_prefix(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_empty_prefix") def test_poetry_command_from_included_file_with_empty_prefix(run_poetry, projects): result = run_poetry( @@ -115,7 +115,7 @@ def test_poetry_command_from_included_file_with_empty_prefix(run_poetry, project # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_empty_prefix") def test_poetry_help_with_poe_command_prefix(run_poetry, projects): result = run_poetry([], cwd=projects["poetry_plugin/with_prefix"].parent) @@ -125,7 +125,7 @@ def test_poetry_help_with_poe_command_prefix(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_with_prefix") def test_running_tasks_with_poe_command_prefix(run_poetry, projects): result = run_poetry( @@ -138,7 +138,7 @@ def test_running_tasks_with_poe_command_prefix(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project_with_prefix") def test_running_tasks_with_poe_command_prefix_missing_args(run_poetry, projects): result = run_poetry( @@ -149,7 +149,7 @@ def test_running_tasks_with_poe_command_prefix_missing_args(run_poetry, projects # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_running_poetry_command_with_hooks(run_poetry, projects): result = run_poetry(["env", "info"], cwd=projects["poetry_plugin"]) @@ -158,7 +158,7 @@ def test_running_poetry_command_with_hooks(run_poetry, projects): # assert result.stderr == "" -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_running_poetry_command_with_hooks_with_directory(run_poetry, projects): result = run_poetry( @@ -175,7 +175,7 @@ def test_running_poetry_command_with_hooks_with_directory(run_poetry, projects): os.environ.get("GITHUB_ACTIONS", "false") == "true", reason="Skipping test the doesn't seem to work in GitHub Actions lately", ) -@pytest.mark.slow() +@pytest.mark.slow @pytest.mark.usefixtures("_setup_poetry_project") def test_task_with_cli_dependency_with_directory(run_poetry, projects, is_windows): result = run_poetry( diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 00cd8b616..46bfb0b80 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -23,7 +23,7 @@ } -@pytest.fixture() +@pytest.fixture def test_file_tree_dir(poe_project_path): path = poe_project_path / "tests" / "temp" / "rm_test" path.mkdir(parents=True, exist_ok=True) @@ -31,7 +31,7 @@ def test_file_tree_dir(poe_project_path): rmtree(path) -@pytest.fixture() +@pytest.fixture def test_file_tree_nodes(test_file_tree_dir): def _iter_dir(work_dir: Path, items: dict): for node_name, content in items.items(): @@ -45,7 +45,7 @@ def _iter_dir(work_dir: Path, items: dict): return tuple(_iter_dir(test_file_tree_dir, _test_file_tree)) -@pytest.fixture() +@pytest.fixture def test_dir_structure(test_file_tree_dir, test_file_tree_nodes): """ Stage a temporary directory structure full of files so we can delete some of them