diff --git a/.gitignore b/.gitignore
index 1ed9f78a6..356dee53c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
notes/
tests/temp
+out.txt
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 0b2d381bd..000000000
--- a/.pylintrc
+++ /dev/null
@@ -1,364 +0,0 @@
-[MASTER]
-
-# Pickle collected data for later comparisons
-persistent=no
-
-[MESSAGES CONTROL]
-
-reports=no
-
-disable=all
-
-output-format=parseable
-
-enable=invalid-name,
- import-error,
- import-self,
- reimported,
- wildcard-import,
- misplaced-future,
- deprecated-module,
- unpacking-non-sequence,
- invalid-all-object,
- undefined-all-variable,
- used-before-assignment,
- cell-var-from-loop,
- global-variable-undefined,
- redefine-in-handler,
- unused-import,
- unused-wildcard-import,
- global-variable-not-assigned,
- undefined-loop-variable,
- global-statement,
- global-at-module-level,
- bad-open-mode,
- redundant-unittest-assert,
- boolean-datetime
- deprecated-method,
- anomalous-unicode-escape-in-string,
- anomalous-backslash-in-string,
- not-in-loop,
- continue-in-finally,
- abstract-class-instantiated,
- star-needs-assignment-target,
- duplicate-argument-name,
- return-in-init,
- too-many-star-expressions,
- nonlocal-and-global,
- return-outside-function,
- return-arg-in-generator,
- invalid-star-assignment-target,
- bad-reversed-sequence,
- nonexistent-operator,
- yield-outside-function,
- init-is-generator,
- nonlocal-without-binding,
- lost-exception,
- assert-on-tuple,
- dangerous-default-value,
- duplicate-key,
- useless-else-on-loop
- expression-not-assigned,
- confusing-with-statement,
- unnecessary-lambda,
- pointless-statement,
- unnecessary-pass,
- unreachable,
- eval-used,
- exec-used,
- using-constant-test,
- bad-super-call,
- missing-super-argument,
- slots-on-old-class,
- super-on-old-class,
- property-on-old-class,
- not-an-iterable,
- not-a-mapping,
- format-needs-mapping,
- truncated-format-string,
- missing-format-string-key,
- mixed-format-string,
- too-few-format-args,
- bad-str-strip-call,
- too-many-format-args,
- bad-format-character,
- format-combined-specification,
- bad-format-string-key,
- bad-format-string,
- missing-format-attribute,
- missing-format-argument-key,
- unused-format-string-argument
- unused-format-string-key,
- invalid-format-index,
- lowercase-l-suffix,
- invalid-encoded-data,
- unpacking-in-except,
- import-star-module-level,
- long-suffix,
- old-octal-literal,
- old-ne-operator,
- backtick,
- old-raise-syntax,
- metaclass-assignment,
- next-method-called,
- dict-iter-method,
- dict-view-method,
- indexing-exception,
- raising-string,
- using-cmp-argument,
- cmp-method,
- coerce-method,
- delslice-method,
- getslice-method,
- hex-method,
- nonzero-method,
- t-method,
- setslice-method,
- old-division,
- logging-format-truncated,
- logging-too-few-args,
- logging-too-many-args,
- logging-unsupported-format,
- logging-format-interpolation,
- invalid-unary-operand-type,
- unsupported-binary-operation,
- not-callable,
- redundant-keyword-arg,
- assignment-from-no-return,
- assignment-from-none,
- not-context-manager,
- repeated-keyword,
- missing-kwoa,
- no-value-for-parameter,
- invalid-sequence-index,
- invalid-slice-index,
- unsupported-membership-test,
- access-member-before-definition,
- method-hidden,
- assigning-non-slot,
- duplicate-bases,
- inconsistent-mro,
- inherit-non-class,
- invalid-slots,
- invalid-slots-object,
- no-method-argument,
- no-self-argument,
- unexpected-special-method-signature,
- non-iterator-returned,
- arguments-differ,
- signature-differs,
- bad-staticmethod-argument,
- non-parent-init-called,
- bad-except-order,
- catching-non-exception,
- bad-exception-context,
- notimplemented-raised,
- raising-bad-type,
- raising-non-exception,
- misplaced-bare-raise,
- duplicate-except,
- broad-except,
- nonstandard-exception,
- binary-op-exception,
- bare-except,
- not-async-context-manager,
- yield-inside-async-function
-
-
-[LOGGING]
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format logging-modules=logging
-
-
-
-[TYPECHECK]
-
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis. It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# List of classes names for which member attributes should not be checked
-# (useful for classes with attributes dynamically set). This supports can work
-# with qualified names.
-ignored-classes=
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-
-[VARIABLES]
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=_$|dummy
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
-
-
-[SIMILARITIES]
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-# Ignore comments when computing similarities.
-ignore-comments=yes
-
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
-
-# Ignore imports when computing similarities.
-ignore-imports=no
-
-
-[SPELLING]
-
-# Spelling dictionary name. Available dictionaries: none. To make it working
-# install python-enchant package.
-spelling-dict=
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to indicated private dictionary in
-# --spelling-private-dict-file option instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,TODO
-
-
-[BASIC]
-
-good-names=T,_,NoValue
-
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
-
-# Regular expression matching correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for function names
-function-name-hint=[a-z_][a-z0-9_]{1,30}$
-
-# Regular expression matching correct variable names
-variable-rgx=[a-z_][a-z0-9_]{1,30}$
-
-# Naming hint for variable names
-variable-name-hint=[a-z_][a-z0-9_]{1,30}$
-
-# Regular expression matching correct constant names
-const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
-
-# Regular expression matching correct attribute names
-attr-rgx=[a-z_][A-Za-z0-9_]{1,30}$
-
-# Naming hint for attribute names
-attr-name-hint=[a-z_][a-z0-9_]{1,30}$
-
-# Regular expression matching correct argument names
-argument-rgx=[a-z_][a-z0-9_]{1,30}$
-
-# Naming hint for argument names
-argument-name-hint=[a-z_][a-z0-9_]{1,30}$
-
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
-
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
-
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
-
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
-
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
-
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
-
-# Regular expression matching correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
-
-# Regular expression matching correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
-
-# Naming hint for method names
-method-name-hint=[a-z_][a-z0-9_]{2,30}$
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-
-[IMPORTS]
-
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=
-
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
-
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
-
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
-
-
-[CLASSES]
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
diff --git a/docs/guides/include_guide.rst b/docs/guides/include_guide.rst
index 922cf2d4d..bb15314b2 100644
--- a/docs/guides/include_guide.rst
+++ b/docs/guides/include_guide.rst
@@ -45,7 +45,7 @@ If an included task file itself includes other files, these second order include
Setting a working directory for included tasks
----------------------------------------------
-When including files from another location, you can also specify that tasks from that other file should be run from within a specific directory. For example with the following configuration, when tasks imported from my_subproject are run from the root, the task will actually execute as if it had been run from the *my_subproject* subdirectory.
+When including files from another location, you can also specify that tasks from that other file should be run from within a specific directory. For example with the following configuration, when tasks imported from *my_subproject* are run from the root, the task will actually execute as if it had been run from the *my_subproject* subdirectory.
.. code-block:: toml
@@ -53,4 +53,13 @@ When including files from another location, you can also specify that tasks from
path = "my_subproject/pyproject.toml"
cwd = "my_subproject"
-The cwd option still has the limitation that it cannot be used to specify a directory outside of parent directory of the pyproject.toml file that poe is running with.
+The directory indicated by the ``cwd`` option will also be used as the base directory for global or task level ``envfile`` imports for tasks defined within an included file.
+
+Tasks and config in an included file can access the ``cwd`` value via the ``POE_CONF_DIR`` environment variable. When no ``cwd`` is set on the include then ``POE_CONF_DIR`` refers the to the parent directory of the config file where a task is defined.
+
+You can still specify that an envfile referenced within an included file should be imported relative to the main project root, using the ``POE_ROOT`` environment variable like so:
+
+.. code-block:: toml
+
+ [tool.poe]
+ envfile = "${POE_ROOT}/.env"
diff --git a/poethepoet/__init__.py b/poethepoet/__init__.py
index 48e0efb0c..029df2a86 100644
--- a/poethepoet/__init__.py
+++ b/poethepoet/__init__.py
@@ -48,9 +48,9 @@ def _list_tasks():
from .config import PoeConfig
config = PoeConfig()
- config.load()
- task_names = (task for task in config.tasks.keys() if task and task[0] != "_")
+ config.load(strict=False)
+ task_names = (task for task in config.task_names if task and task[0] != "_")
print(" ".join(task_names))
- except Exception: # pylint: disable=broad-except
+ except Exception:
# this happens if there's no pyproject.toml present
pass
diff --git a/poethepoet/app.py b/poethepoet/app.py
index fed4ae145..8a0d44c9a 100644
--- a/poethepoet/app.py
+++ b/poethepoet/app.py
@@ -18,7 +18,7 @@
if TYPE_CHECKING:
from .config import PoeConfig
from .context import RunContext
- from .task import PoeTask
+ from .task.base import PoeTask
from .ui import PoeUi
@@ -27,7 +27,7 @@ class PoeThePoet:
:param cwd:
The directory that poe should take as the current working directory,
this determines where to look for a pyproject.toml file, defaults to
- ``Path(".").resolve()``
+ ``Path().resolve()``
:type cwd: Path, optional
:param config:
Either a dictionary with the same schema as a pyproject.toml file, or a
@@ -56,6 +56,8 @@ class PoeThePoet:
ui: "PoeUi"
config: "PoeConfig"
+ _task_specs: Optional[Dict[str, "PoeTask.TaskSpec"]] = None
+
def __init__(
self,
cwd: Optional[Union[Path, str]] = None,
@@ -98,8 +100,9 @@ def __call__(self, cli_args: Sequence[str], internal: bool = False) -> int:
return 0
try:
- self.config.load(self.ui["project_root"])
- self.config.validate()
+ self.config.load(target_path=self.ui["project_root"])
+ for task_spec in self.task_specs.load_all():
+ task_spec.validate(self.config, self.task_specs)
except PoeException as error:
if self.ui["help"]:
self.print_help()
@@ -122,8 +125,16 @@ def __call__(self, cli_args: Sequence[str], internal: bool = False) -> int:
else:
return self.run_task(task) or 0
+ @property
+ def task_specs(self):
+ if not self._task_specs:
+ from .task.base import TaskSpecFactory
+
+ self._task_specs = TaskSpecFactory(self.config)
+ return self._task_specs
+
def resolve_task(self, allow_hidden: bool = False) -> Optional["PoeTask"]:
- from .task import PoeTask
+ from .task.base import TaskContext
task = tuple(self.ui["task"])
if not task:
@@ -131,7 +142,7 @@ def resolve_task(self, allow_hidden: bool = False) -> Optional["PoeTask"]:
return None
task_name = task[0]
- if task_name not in self.config.tasks:
+ if task_name not in self.config.task_names:
self.print_help(error=PoeException(f"Unrecognised task {task_name!r}"))
return None
@@ -143,8 +154,15 @@ def resolve_task(self, allow_hidden: bool = False) -> Optional["PoeTask"]:
)
return None
- return PoeTask.from_config(
- task_name, config=self.config, ui=self.ui, invocation=task
+ task_spec = self.task_specs.get(task_name)
+ return task_spec.create_task(
+ invocation=task,
+ ctx=TaskContext(
+ config=self.config,
+ cwd=str(task_spec.source.cwd),
+ specs=self.task_specs,
+ ui=self.ui,
+ ),
)
def run_task(
@@ -154,12 +172,12 @@ def run_task(
context = self.get_run_context()
try:
return task.run(context=context)
- except PoeException as error:
- self.print_help(error=error)
- return 1
except ExecutionError as error:
self.ui.print_error(error=error)
return 1
+ except PoeException as error:
+ self.print_help(error=error)
+ return 1
def run_task_graph(self, task: "PoeTask") -> Optional[int]:
from .task.graph import TaskExecutionGraph
diff --git a/poethepoet/config.py b/poethepoet/config.py
index 1ed996da1..c859a5942 100644
--- a/poethepoet/config.py
+++ b/poethepoet/config.py
@@ -1,17 +1,250 @@
import json
from pathlib import Path
+from types import MappingProxyType
try:
import tomllib as tomli
except ImportError:
import tomli # type: ignore[no-redef]
-from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union
+from typing import (
+ Any,
+ Iterator,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+)
+
+from .exceptions import ConfigValidationError, PoeException
+from .options import NoValue, PoeOptions
+
+
+class ConfigPartition:
+ options: PoeOptions
+ full_config: Mapping[str, Any]
+ poe_options: Mapping[str, Any]
+ path: Path
+ project_dir: Path
+ _cwd: Optional[Path]
+
+ ConfigOptions: Type[PoeOptions]
+ is_primary: bool = False
-from .exceptions import PoeException
+ def __init__(
+ self,
+ full_config: Mapping[str, Any],
+ path: Path,
+ project_dir: Optional[Path] = None,
+ cwd: Optional[Path] = None,
+ strict: bool = True,
+ ):
+ self.poe_options: Mapping[str, Any] = (
+ full_config["tool"].get("poe", {})
+ if "tool" in full_config
+ else full_config.get("tool.poe", {})
+ )
+ self.options = next(
+ self.ConfigOptions.parse(
+ self.poe_options,
+ strict=strict,
+ # Allow and standard config keys, even if not declared
+ # This avoids misguided validation errors on included config
+ extra_keys=tuple(ProjectConfig.ConfigOptions.get_fields()),
+ )
+ )
+ self.full_config = full_config
+ self.path = path
+ self._cwd = cwd
+ self.project_dir = project_dir or self.path.parent
+
+ @property
+ def cwd(self):
+ return self._cwd or self.project_dir
+
+ @property
+ def config_dir(self):
+ return self._cwd or self.path.parent
+
+ def get(self, key: str, default: Any = NoValue):
+ return self.options.get(key, default)
+
+
+EmptyDict: Mapping = MappingProxyType({})
+
+
+class ProjectConfig(ConfigPartition):
+ is_primary = True
+
+ class ConfigOptions(PoeOptions):
+ """
+ Options supported directly under tool.poe in the main config i.e. pyproject.toml
+ """
+
+ default_task_type: str = "cmd"
+ default_array_task_type: str = "sequence"
+ default_array_item_task_type: str = "ref"
+ env: Mapping[str, str] = EmptyDict
+ envfile: Union[str, Sequence[str]] = tuple()
+ executor: Mapping[str, str] = MappingProxyType({"type": "auto"})
+ include: Sequence[str] = tuple()
+ poetry_command: str = "poe"
+ poetry_hooks: Mapping[str, str] = EmptyDict
+ shell_interpreter: Union[str, Sequence[str]] = "posix"
+ verbosity: int = 0
+ tasks: Mapping[str, Any] = EmptyDict
+
+ @classmethod
+ def normalize(
+ cls,
+ config: Any,
+ strict: bool = True,
+ ):
+ if isinstance(config, (list, tuple)):
+ raise ConfigValidationError("Expected ")
+
+ # Normalize include option:
+ # > Union[str, Sequence[str], Mapping[str, str]] => List[dict]
+ if "include" in config:
+ includes: Any = []
+ include_option = config.get("include", None)
+
+ if isinstance(include_option, (dict, str)):
+ include_option = [include_option]
+
+ if isinstance(include_option, list):
+ valid_keys = {"path", "cwd"}
+ for include in include_option:
+ if isinstance(include, str):
+ includes.append({"path": include})
+ elif (
+ isinstance(include, dict)
+ and include.get("path")
+ and set(include.keys()) <= valid_keys
+ ):
+ includes.append(include)
+ else:
+ raise ConfigValidationError(
+ f"Invalid item for the include option {include!r}",
+ global_option="include",
+ )
+ else:
+ # Something is wrong, let option validation handle it
+ includes = include_option
+
+ config = {**config, "include": includes}
+
+ yield config
+
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ super().validate()
+
+ from .executor import PoeExecutor
+ from .task.base import PoeTask
+
+ # Validate default_task_type value
+ if not PoeTask.is_task_type(self.default_task_type, content_type=str):
+ raise ConfigValidationError(
+ "Invalid value for option 'default_task_type': "
+ f"{self.default_task_type!r}\n"
+ f"Expected one of {PoeTask.get_task_types(str)!r}"
+ )
+
+ # Validate default_array_task_type value
+ if not PoeTask.is_task_type(
+ self.default_array_task_type, content_type=list
+ ):
+ raise ConfigValidationError(
+ "Invalid value for option 'default_array_task_type': "
+ f"{self.default_array_task_type!r}\n"
+ f"Expected one of {PoeTask.get_task_types(list)!r}"
+ )
+
+ # Validate default_array_item_task_type value
+ if not PoeTask.is_task_type(
+ self.default_array_item_task_type, content_type=str
+ ):
+ raise ConfigValidationError(
+ "Invalid value for option 'default_array_item_task_type': "
+ f"{self.default_array_item_task_type!r}\n"
+ f"Expected one of {PoeTask.get_task_types(str)!r}"
+ )
+
+ # Validate shell_interpreter type
+ if self.shell_interpreter:
+ shell_interpreter = (
+ (self.shell_interpreter,)
+ if isinstance(self.shell_interpreter, str)
+ else self.shell_interpreter
+ )
+ for interpreter in shell_interpreter:
+ if interpreter not in PoeConfig.KNOWN_SHELL_INTERPRETERS:
+ raise ConfigValidationError(
+ f"Unsupported value {interpreter!r} for option "
+ "'shell_interpreter'\n"
+ f"Expected one of {PoeConfig.KNOWN_SHELL_INTERPRETERS!r}"
+ )
+
+ # Validate default verbosity.
+ if self.verbosity < -1 or self.verbosity > 2:
+ raise ConfigValidationError(
+ f"Invalid value for option 'verbosity': {self.verbosity!r},\n"
+ "Expected value be between -1 and 2."
+ )
+
+ self.validate_env(self.env)
+
+ # Validate executor config
+ PoeExecutor.validate_config(self.executor)
+
+ @classmethod
+ def validate_env(cls, env: Mapping[str, str]):
+ # Validate env value
+ for key, value in env.items():
+ if isinstance(value, dict):
+ if tuple(value.keys()) != ("default",) or not isinstance(
+ value["default"], str
+ ):
+ raise ConfigValidationError(
+ f"Invalid declaration at {key!r} in option 'env': {value!r}"
+ )
+ elif not isinstance(value, str):
+ raise ConfigValidationError(
+ f"Value of {key!r} in option 'env' should be a string, "
+ f"but found {type(value).__name__!r}"
+ )
+
+
+class IncludedConfig(ConfigPartition):
+ class ConfigOptions(PoeOptions):
+ """
+ Options supported directly under tool.poe in included config files
+ """
+
+ env: Mapping[str, str] = EmptyDict
+ envfile: Union[str, Sequence[str]] = tuple()
+ tasks: Mapping[str, Any] = EmptyDict
+
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ super().validate()
+
+ # Apply same validation to env option as for the main config
+ ProjectConfig.ConfigOptions.validate_env(self.env)
class PoeConfig:
+ _project_config: ProjectConfig
+ _included_config: List[IncludedConfig]
+
KNOWN_SHELL_INTERPRETERS = (
"posix",
"sh",
@@ -24,22 +257,13 @@ class PoeConfig:
)
"""
- Options allowed directly under tool.poe in pyproject.toml
+ The filename to look for when loading config
"""
- __options__ = {
- "default_task_type": str,
- "default_array_task_type": str,
- "default_array_item_task_type": str,
- "env": dict,
- "envfile": (str, list),
- "executor": dict,
- "include": (str, list, dict),
- "poetry_command": str,
- "poetry_hooks": dict,
- "shell_interpreter": (str, list),
- "verbosity": int,
- }
-
+ _config_name: str = "pyproject.toml"
+ """
+ The parent directory of the project config file
+ """
+ _project_dir: Path
"""
This can be overridden, for example to align with poetry
"""
@@ -51,264 +275,214 @@ def __init__(
table: Optional[Mapping[str, Any]] = None,
config_name: str = "pyproject.toml",
):
- self.cwd = Path().resolve() if cwd is None else Path(cwd)
- self._poe = {} if table is None else dict(table)
self._config_name = config_name
- self._project_dir: Optional[Path] = None
+ self._project_dir = self._resolve_project_dir(
+ Path().resolve() if cwd is None else Path(cwd)
+ )
+ self._project_config = ProjectConfig(
+ {"tool.poe": table or {}},
+ path=self._project_dir.joinpath(config_name),
+ strict=False,
+ )
+ self._included_config = []
+
+ def lookup_task(
+ self, name: str
+ ) -> 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
+
+ for include in reversed(self._included_config):
+ task = include.get("tasks", {}).get(name, None)
+ if task is not None:
+ return task, include
+
+ return None, None
+
+ def partitions(self, included_first=True) -> Iterator[ConfigPartition]:
+ if not included_first:
+ yield self._project_config
+ yield from self._included_config
+ if included_first:
+ yield self._project_config
@property
def executor(self) -> Mapping[str, Any]:
- return self._poe.get("executor", {"type": "auto"})
+ return self._project_config.options.executor
+
+ @property
+ def task_names(self) -> Iterator[str]:
+ result = list(self._project_config.get("tasks", {}).keys())
+ for config_part in self._included_config:
+ for task_name in config_part.get("tasks", {}).keys():
+ # Don't use a set to dedup because we want to preserve task order
+ if task_name not in result:
+ result.append(task_name)
+ yield from result
@property
def tasks(self) -> Mapping[str, Any]:
- return self._poe.get("tasks", {})
+ result = dict(self._project_config.get("tasks", {}))
+ for config in self._included_config:
+ for task_name, task_def in config.get("tasks", {}).items():
+ if task_name in result:
+ continue
+ result[task_name] = task_def
+ return result
@property
def default_task_type(self) -> str:
- return self._poe.get("default_task_type", "cmd")
+ return self._project_config.options.default_task_type
@property
def default_array_task_type(self) -> str:
- return self._poe.get("default_array_task_type", "sequence")
+ return self._project_config.options.default_array_task_type
@property
def default_array_item_task_type(self) -> str:
- return self._poe.get("default_array_item_task_type", "ref")
-
- @property
- def global_env(self) -> Dict[str, Union[str, Dict[str, str]]]:
- return self._poe.get("env", {})
-
- @property
- def global_envfile(self) -> Optional[str]:
- return self._poe.get("envfile")
+ return self._project_config.options.default_array_item_task_type
@property
def shell_interpreter(self) -> Tuple[str, ...]:
- raw_value = self._poe.get("shell_interpreter", "posix")
+ raw_value = self._project_config.options.shell_interpreter
if isinstance(raw_value, list):
return tuple(raw_value)
return (raw_value,)
@property
def verbosity(self) -> int:
- return self._poe.get("verbosity", self._baseline_verbosity)
+ return self._project_config.get("verbosity", self._baseline_verbosity)
@property
- def project(self) -> Any:
- return self._project
+ def is_poetry_project(self) -> bool:
+ return "poetry" in self._project_config.full_config
@property
- def project_dir(self) -> str:
- return str(self._project_dir or self.cwd)
+ def project_dir(self) -> Path:
+ return self._project_dir
+
+ def load(self, target_path: Optional[Union[Path, str]] = None, strict: bool = True):
+ """
+ target_path is the path to a file or directory for loading config
+ If strict is false then some errors in the config structure are tolerated
+ """
- def load(self, target_dir: Optional[str] = None):
- if self._poe:
- return
+ config_path = self.find_config_file(
+ target_path=Path(target_path) if target_path else None,
+ search_parent=target_path is None,
+ )
+ self._project_dir = config_path.parent
- config_path = self.find_config_file(target_dir)
try:
- self._project = self._read_config_file(config_path)
- self._poe = self._project["tool"]["poe"]
+ self._project_config = ProjectConfig(
+ self._read_config_file(config_path),
+ path=config_path,
+ project_dir=self._project_dir,
+ strict=strict,
+ )
except KeyError:
raise PoeException(
f"No poe configuration found in file at {self._config_name}"
)
- self._project_dir = config_path.parent
- self._load_includes(self._project_dir)
-
- def validate(self):
- from .executor import PoeExecutor
- from .task import PoeTask
-
- # Validate keys
- supported_keys = {"tasks", *self.__options__}
- unsupported_keys = set(self._poe) - supported_keys
- if unsupported_keys:
- raise PoeException(f"Unsupported keys in poe config: {unsupported_keys!r}")
-
- # Validate types of option values
- for key, option_type in self.__options__.items():
- if key in self._poe and not isinstance(self._poe[key], option_type):
- raise PoeException(
- f"Unsupported value for option {key!r}, expected type to be "
- f"{option_type.__name__}."
- )
-
- # Validate executor config
- error = PoeExecutor.validate_config(self.executor)
- if error:
- raise PoeException(error)
-
- # Validate default_task_type value
- if not PoeTask.is_task_type(self.default_task_type, content_type=str):
- raise PoeException(
- "Unsupported value for option `default_task_type` "
- f"{self.default_task_type!r}"
+ except ConfigValidationError:
+ # Try again to load Config with minimal validation so we can still display
+ # the task list alongside the error
+ self._project_config = ProjectConfig(
+ self._read_config_file(config_path),
+ path=config_path,
+ project_dir=self._project_dir,
+ strict=False,
)
+ raise
- # Validate default_array_task_type value
- if not PoeTask.is_task_type(self.default_array_task_type, content_type=list):
- raise PoeException(
- "Unsupported value for option `default_array_task_type` "
- f"{self.default_array_task_type!r}"
- )
+ self._load_includes(strict=strict)
- # Validate default_array_item_task_type value
- if not PoeTask.is_task_type(self.default_array_item_task_type):
- raise PoeException(
- "Unsupported value for option `default_array_item_task_type` "
- f"{self.default_array_item_task_type!r}"
- )
+ def find_config_file(
+ self, target_path: Optional[Path] = None, search_parent: bool = True
+ ) -> Path:
+ """
+ If search_parent is False then check if the target_path points to a config file
+ or a directory containing a config file.
- # Validate env value
- for key, value in self.global_env.items():
- if isinstance(value, dict):
- if tuple(value.keys()) != ("default",) or not isinstance(
- value["default"], str
- ):
- raise PoeException(
- f"Invalid declaration at {key!r} in option `env`: {value!r}"
- )
- elif not isinstance(value, str):
- raise PoeException(
- f"Value of {key!r} in option `env` should be a string, but found "
- f"{type(value)!r}"
- )
+ If search_parent is True then also search for the config file in parent
+ directories in ascending order.
- # Validate tasks
- for task_name, task_def in self.tasks.items():
- error = PoeTask.validate_def(task_name, task_def, self)
- if error is None:
- continue
- raise PoeException(error)
+ If no target_path is provided then start with self._project_dir
- # Validate shell_interpreter type
- for interpreter in self.shell_interpreter:
- if interpreter not in self.KNOWN_SHELL_INTERPRETERS:
- raise PoeException(
- f"Unsupported value {interpreter!r} for option `shell_interpreter`."
- )
+ If the given target_path is a file, then it may be named as any toml or json
+ file, otherwise the config file name must match `self._config_name`.
- # Validate default verbosity.
- if self.verbosity < -1 or self.verbosity > 2:
- raise PoeException(
- f"Invalid value for option `verbosity`: {self.verbosity!r}. "
- "Should be between -1 and 2."
- )
-
- def find_config_file(self, target_dir: Optional[str] = None) -> Path:
+ If no config file can be found then raise a PoeException
"""
- Resolve a path to a self._config_name using one of two strategies:
- 1. If target_dir is provided then only look there, (accept path to config file
- or to a directory).
- 2. Otherwise look for the self._config_name in the current working directory,
- following by all parent directories in ascending order.
+ if target_path is None:
+ target_path = self._project_dir
+ else:
+ target_path = target_path.resolve()
- Both strategies result in an Exception on failure.
- """
- if target_dir:
- target_path = Path(target_dir).resolve()
+ if not search_parent:
if not (
target_path.name.endswith(".toml") or target_path.name.endswith(".json")
):
target_path = target_path.joinpath(self._config_name)
if not target_path.exists():
raise PoeException(
- f"Poe could not find a {self._config_name} file at the given "
- f"location: {target_dir}"
+ f"Poe could not find a {self._config_name!r} file at the given "
+ f"location: {target_path!r}"
)
return target_path
- maybe_result = self.cwd.joinpath(self._config_name)
+ return self._resolve_project_dir(target_path, raise_on_fail=True)
+
+ def _resolve_project_dir(self, target_dir: Path, raise_on_fail: bool = False):
+ """
+ Look for the self._config_name in the current working directory,
+ followed by all parent directories in ascending order.
+ Return the path of the parent directory of the first config file found.
+ """
+ maybe_result = target_dir.joinpath(self._config_name)
while not maybe_result.exists():
if len(maybe_result.parents) == 1:
- raise PoeException(
- f"Poe could not find a {self._config_name} file in {self.cwd} or"
- " its parents"
- )
- maybe_result = maybe_result.parents[1].joinpath(self._config_name).resolve()
- return maybe_result
-
- def _load_includes(self, project_dir: Path):
- include_option: Union[str, Sequence[str]] = self._poe.get("include", tuple())
- includes: List[Dict[str, str]] = []
-
- if isinstance(include_option, str):
- includes.append({"path": include_option})
- elif isinstance(include_option, dict):
- includes.append(include_option)
- elif isinstance(include_option, list):
- valid_keys = {"path", "cwd"}
- for include in include_option:
- if isinstance(include, str):
- includes.append({"path": include})
- elif (
- isinstance(include, dict)
- and include.get("path")
- and set(include.keys()) <= valid_keys
- ):
- includes.append(include)
- else:
+ if raise_on_fail:
raise PoeException(
- f"Invalid item for the include option {include!r}"
+ f"Poe could not find a {self._config_name!r} file in "
+ f"{target_dir} or any parent directory."
)
+ else:
+ return target_dir
+ maybe_result = maybe_result.parents[1].joinpath(self._config_name).resolve()
+ return maybe_result
- for include in includes:
- include_path = project_dir.joinpath(include["path"]).resolve()
+ def _load_includes(self: "PoeConfig", strict: bool = True):
+ # Attempt to load each of the included configs
+ for include in self._project_config.options.include:
+ include_path = self._project_dir.joinpath(include["path"]).resolve()
if not include_path.exists():
# TODO: print warning in verbose mode, requires access to ui somehow
+ # Maybe there should be something like a WarningService?
continue
try:
- include_config = PoeConfig(
- cwd=include.get("cwd", self.project_dir),
- table=self._read_config_file(include_path)["tool"]["poe"],
+ self._included_config.append(
+ IncludedConfig(
+ self._read_config_file(include_path),
+ path=include_path,
+ project_dir=self._project_dir,
+ cwd=(
+ self.project_dir.joinpath(include["cwd"]).resolve()
+ if include.get("cwd")
+ else None
+ ),
+ strict=strict,
+ )
)
- include_config._project_dir = self._project_dir
except (PoeException, KeyError) as error:
- raise PoeException(
- f"Invalid content in included file from {include_path}", error
+ raise ConfigValidationError(
+ f"Invalid content in included file from {include_path}",
+ filename=str(include_path),
) from error
- self._merge_config(include_config)
-
- def _merge_config(self, include_config: "PoeConfig"):
- from .task import PoeTask
-
- # Env is special because it can be extended rather than just overwritten
- if include_config.global_env:
- self._poe["env"] = {**include_config.global_env, **self._poe.get("env", {})}
-
- if include_config.global_envfile and "envfile" not in self._poe:
- self._poe["envfile"] = include_config.global_envfile
-
- # Includes additional tasks with preserved ordering
- self._poe["tasks"] = own_tasks = self._poe.get("tasks", {})
- for task_name, task_def in include_config.tasks.items():
- if task_name in own_tasks:
- # don't override tasks from the base config
- continue
-
- task_def = PoeTask.normalize_task_def(task_def, include_config)
- if include_config.cwd:
- # Override the config of each task to use the include level cwd as a
- # base for the task level cwd
- if "cwd" in task_def:
- # rebase the configured cwd onto the include level cwd
- task_def["cwd"] = str(
- Path(include_config.cwd)
- .resolve()
- .joinpath(task_def["cwd"])
- .relative_to(self.project_dir)
- )
- else:
- task_def["cwd"] = str(include_config.cwd)
-
- own_tasks[task_name] = task_def
-
@staticmethod
def _read_config_file(path: Path) -> Mapping[str, Any]:
try:
diff --git a/poethepoet/context.py b/poethepoet/context.py
index 685ad9fac..990446229 100644
--- a/poethepoet/context.py
+++ b/poethepoet/context.py
@@ -40,30 +40,21 @@ def __init__(
self.multistage = multistage
self.exec_cache = {}
self.captured_stdout = {}
+
+ # Init root EnvVarsManager
self.env = EnvVarsManager(self.config, self.ui, base_env=env, cwd=cwd)
+ for config_part in self.config.partitions():
+ self.env.apply_env_config(
+ envfile=config_part.get("envfile", None),
+ config_env=config_part.get("env", None),
+ config_dir=config_part.config_dir,
+ config_working_dir=config_part.cwd,
+ )
@property
def executor_type(self) -> Optional[str]:
return self.config.executor["type"]
- def get_task_env(
- self,
- parent_env: Optional["EnvVarsManager"],
- task_envfile: Optional[str],
- task_env: Optional[Mapping[str, str]],
- task_uses: Optional[Mapping[str, Tuple[str, ...]]] = None,
- ) -> "EnvVarsManager":
- if parent_env is None:
- parent_env = self.env
-
- result = parent_env.for_task(task_envfile, task_env)
-
- # Include env vars from dependencies
- if task_uses is not None:
- result.update(self._get_dep_values(task_uses))
-
- return result
-
def _get_dep_values(
self, used_task_invocations: Mapping[str, Tuple[str, ...]]
) -> Dict[str, str]:
@@ -104,7 +95,7 @@ def get_executor(
env: "EnvVarsManager",
working_dir: Path,
executor_config: Optional[Mapping[str, str]] = None,
- capture_stdout: bool = False,
+ capture_stdout: Union[str, bool] = False,
) -> "PoeExecutor":
from .executor import PoeExecutor
@@ -113,7 +104,7 @@ def get_executor(
context=self,
env=env,
working_dir=working_dir,
- dry=self.dry,
executor_config=executor_config,
capture_stdout=capture_stdout,
+ dry=self.dry,
)
diff --git a/poethepoet/env/cache.py b/poethepoet/env/cache.py
index 158160484..ca554674e 100644
--- a/poethepoet/env/cache.py
+++ b/poethepoet/env/cache.py
@@ -1,11 +1,14 @@
+from os import environ
from pathlib import Path
-from typing import TYPE_CHECKING, Dict, Optional
+from typing import TYPE_CHECKING, Dict, Optional, Union
from ..exceptions import ExecutionError
if TYPE_CHECKING:
from .ui import PoeUi
+POE_DEBUG = environ.get("POE_DEBUG")
+
class EnvFileCache:
_cache: Dict[str, Dict[str, str]] = {}
@@ -16,19 +19,23 @@ def __init__(self, project_dir: Path, ui: Optional["PoeUi"]):
self._project_dir = project_dir
self._ui = ui
- def get(self, envfile_path_str: str) -> Dict[str, str]:
+ def get(self, envfile: Union[str, Path]) -> Dict[str, str]:
from .parse import parse_env_file
+ envfile_path_str = str(envfile)
+
if envfile_path_str in self._cache:
return self._cache[envfile_path_str]
result = {}
- envfile_path = self._project_dir.joinpath(Path(envfile_path_str).expanduser())
+ envfile_path = self._project_dir.joinpath(Path(envfile).expanduser())
if envfile_path.is_file():
try:
- with envfile_path.open(encoding="utf-8") as envfile:
- result = parse_env_file(envfile.readlines())
+ with envfile_path.open(encoding="utf-8") as envfile_file:
+ result = parse_env_file(envfile_file.readlines())
+ if POE_DEBUG:
+ print(f" - Loaded Envfile from {envfile_path}")
except ValueError as error:
message = error.args[0]
raise ExecutionError(
@@ -36,11 +43,15 @@ def get(self, envfile_path_str: str) -> Dict[str, str]:
f" {message}"
) from error
- elif self._ui is not None:
- self._ui.print_msg(
- f"Warning: Poe failed to locate envfile at {envfile_path_str!r}",
- verbosity=1,
- )
+ else:
+ if POE_DEBUG:
+ print(f" ! Envfile not found at {envfile_path}")
+
+ if self._ui is not None:
+ self._ui.print_msg(
+ f"Warning: Poe failed to locate envfile at {envfile_path_str!r}",
+ verbosity=1,
+ )
self._cache[envfile_path_str] = result
return result
diff --git a/poethepoet/env/manager.py b/poethepoet/env/manager.py
index 04b4b4db3..f692876ee 100644
--- a/poethepoet/env/manager.py
+++ b/poethepoet/env/manager.py
@@ -1,6 +1,6 @@
import os
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union
from .template import apply_envvars_to_template
@@ -16,7 +16,7 @@ class EnvVarsManager:
_vars: Dict[str, str]
envfiles: "EnvFileCache"
- def __init__(
+ def __init__( # TODO: check if we still need all these args!
self,
config: "PoeConfig",
ui: Optional["PoeUi"],
@@ -39,74 +39,60 @@ def __init__(
**(base_env or {}),
}
- if parent_env is None:
- # Get env vars from envfile(s) referenced in global options
- global_envfile = self._config.global_envfile
- if isinstance(global_envfile, str):
- self._vars.update(self.envfiles.get(global_envfile))
- elif isinstance(global_envfile, list):
- for task_envfile_path in global_envfile:
- self._vars.update(self.envfiles.get(task_envfile_path))
-
- # Get env vars from global options
- self._apply_env_config(self._config.global_env)
-
self._vars["POE_ROOT"] = str(self._config.project_dir)
self.cwd = str(cwd or os.getcwd())
- if "POE_PWD" not in self._vars:
- self._vars["POE_PWD"] = self.cwd
+ if "POE_CWD" not in self._vars:
+ self._vars["POE_CWD"] = self.cwd
+ self._vars["POE_PWD"] = self.cwd # Deprecated
def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
return self._vars.get(key, default)
- def _apply_env_config(
+ def set(self, key: str, value: str):
+ self._vars[key] = value
+
+ def apply_env_config(
self,
- config_env: Mapping[str, Union[str, Mapping[str, str]]],
+ envfile: Optional[Union[str, List[str]]],
+ config_env: Optional[Mapping[str, Union[str, Mapping[str, str]]]],
+ config_dir: Path,
+ config_working_dir: Path,
):
"""
Used for including env vars from global or task config.
If a value is provided as a mapping from `"default"` to `str` then it is only
used if the associated key doesn't already have a value.
"""
- for key, value in config_env.items():
+
+ vars_scope = dict(self._vars, POE_CONF_DIR=str(config_dir))
+ if envfile:
+ if isinstance(envfile, str):
+ envfile = [envfile]
+ for envfile_path in envfile:
+ self.update(
+ self.envfiles.get(
+ config_working_dir.joinpath(
+ apply_envvars_to_template(
+ envfile_path, vars_scope, require_braces=True
+ )
+ )
+ )
+ )
+
+ vars_scope = dict(self._vars, POE_CONF_DIR=str(config_dir))
+ for key, value in (config_env or {}).items():
if isinstance(value, str):
value_str = value
- elif key not in self._vars:
+ elif key not in vars_scope:
value_str = value["default"]
else:
continue
self._vars[key] = apply_envvars_to_template(
- value_str, self._vars, require_braces=True
+ value_str, vars_scope, require_braces=True
)
- def for_task(
- self, task_envfile: Optional[str], task_env: Optional[Mapping[str, str]]
- ) -> "EnvVarsManager":
- """
- Create a copy of self and extend it to include vars for the task.
- """
- result = EnvVarsManager(
- self._config,
- self._ui,
- parent_env=self,
- cwd=self.cwd,
- )
-
- # Include env vars from envfile(s) referenced in task options
- if isinstance(task_envfile, str):
- result.update(self.envfiles.get(task_envfile))
- elif isinstance(task_envfile, list):
- for task_envfile_path in task_envfile:
- result.update(self.envfiles.get(task_envfile_path))
-
- # Include env vars from task options
- if task_env is not None:
- result._apply_env_config(task_env)
-
- return result
-
def update(self, env_vars: Mapping[str, Any]):
# ensure all values are strings
str_vars: Dict[str, str] = {}
@@ -120,6 +106,14 @@ def update(self, env_vars: Mapping[str, Any]):
return self
+ def clone(self):
+ return EnvVarsManager(
+ config=self._config,
+ ui=self._ui,
+ parent_env=self,
+ cwd=self.cwd,
+ )
+
def to_dict(self):
return dict(self._vars)
diff --git a/poethepoet/exceptions.py b/poethepoet/exceptions.py
index a7011c928..9cbcc8a5a 100644
--- a/poethepoet/exceptions.py
+++ b/poethepoet/exceptions.py
@@ -19,6 +19,31 @@ class ExpressionParseError(PoeException):
pass
+class ConfigValidationError(PoeException):
+ context: Optional[str]
+ task_name: Optional[str]
+ index: Optional[int]
+ global_option: Optional[str]
+ filename: Optional[str]
+
+ def __init__(
+ self,
+ msg,
+ *args,
+ context: Optional[str] = None,
+ task_name: Optional[str] = None,
+ index: Optional[int] = None,
+ global_option: Optional[str] = None,
+ filename: Optional[str] = None
+ ):
+ super().__init__(msg, *args)
+ self.context = context
+ self.task_name = task_name
+ self.index = index
+ self.global_option = global_option
+ self.filename = filename
+
+
class ExecutionError(RuntimeError):
cause: Optional[str]
diff --git a/poethepoet/executor/base.py b/poethepoet/executor/base.py
index 767361077..86a04b433 100644
--- a/poethepoet/executor/base.py
+++ b/poethepoet/executor/base.py
@@ -5,6 +5,7 @@
from typing import (
TYPE_CHECKING,
Any,
+ ClassVar,
Dict,
Mapping,
MutableMapping,
@@ -15,7 +16,7 @@
Union,
)
-from ..exceptions import ExecutionError, PoeException
+from ..exceptions import ConfigValidationError, ExecutionError, PoeException
if TYPE_CHECKING:
from ..context import RunContext
@@ -47,8 +48,8 @@ class PoeExecutor(metaclass=MetaPoeExecutor):
working_dir: Optional[Path]
- __executor_types: Dict[str, Type["PoeExecutor"]] = {}
- __key__: Optional[str] = None
+ __executor_types: ClassVar[Dict[str, Type["PoeExecutor"]]] = {}
+ __key__: ClassVar[Optional[str]] = None
def __init__(
self,
@@ -57,22 +58,22 @@ def __init__(
options: Mapping[str, str],
env: "EnvVarsManager",
working_dir: Optional[Path] = None,
- dry: bool = False,
capture_stdout: Union[str, bool] = False,
+ dry: bool = False,
):
self.invocation = invocation
self.context = context
self.options = options
self.working_dir = working_dir
self.env = env
- self.dry = dry
self.capture_stdout = (
Path(self.context.config.project_dir).joinpath(
self.env.fill_template(capture_stdout)
)
- if isinstance(capture_stdout, str)
- else capture_stdout
+ if capture_stdout and isinstance(capture_stdout, str)
+ else bool(capture_stdout)
)
+ self.dry = dry
self._is_windows = sys.platform == "win32"
@classmethod
@@ -86,15 +87,15 @@ def get(
context: "RunContext",
env: "EnvVarsManager",
working_dir: Optional[Path] = None,
- dry: bool = False,
executor_config: Optional[Mapping[str, str]] = None,
capture_stdout: Union[str, bool] = False,
+ dry: bool = False,
) -> "PoeExecutor":
""""""
# use task specific executor config or fallback to global
options = executor_config or context.config.executor
return cls._resolve_implementation(context, executor_config)(
- invocation, context, options, env, working_dir, dry, capture_stdout
+ invocation, context, options, env, working_dir, capture_stdout, dry
)
@classmethod
@@ -109,11 +110,12 @@ def _resolve_implementation(
config_executor_type = context.executor_type
if executor_config:
- if executor_config["type"] not in cls.__executor_types:
+ executor_type = executor_config["type"]
+ if executor_type not in cls.__executor_types:
raise PoeException(
- f"Cannot instantiate unknown executor {executor_config['type']!r}"
+ f"Cannot instantiate unknown executor {executor_type!r}"
)
- return cls.__executor_types[executor_config["type"]]
+ return cls.__executor_types[executor_type]
elif config_executor_type == "auto":
for impl in [
cls.__executor_types["poetry"],
@@ -180,7 +182,7 @@ def _execute_cmd(
return self._handle_file_not_found(cmd, error)
if error.filename == self.working_dir:
raise PoeException(
- "The specified working directory does not exists "
+ "The specified working directory does not exist "
f"'{self.working_dir}'"
)
raise
@@ -270,25 +272,40 @@ def handle_sigint(signum, _frame):
return proc.returncode
@classmethod
- def validate_config(cls, config: Dict[str, Any]) -> Optional[str]:
+ def validate_config(cls, config: Dict[str, Any]):
+ if "type" not in config:
+ raise ConfigValidationError(
+ "Missing required key 'type' from executor option",
+ global_option="executor",
+ )
+
executor_type = config["type"]
if executor_type == "auto":
extra_options = set(config.keys()) - {"type"}
if extra_options:
- return f"Unexpected keys for executor config: {extra_options!r}"
+ raise ConfigValidationError(
+ f"Unexpected keys for executor config: {extra_options!r}",
+ global_option="executor",
+ )
+
elif executor_type not in cls.__executor_types:
- return f"Unknown executor type: {executor_type!r}"
+ raise ConfigValidationError(
+ f"Unknown executor type: {executor_type!r}",
+ global_option="executor.type",
+ )
+
else:
- return cls.__executor_types[executor_type].validate_executor_config(config)
- return None
+ cls.__executor_types[executor_type].validate_executor_config(config)
@classmethod
- def validate_executor_config(cls, config: Dict[str, Any]) -> Optional[str]:
+ 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:
- return f"Unexpected keys for executor config: {extra_options!r}"
- return None
+ raise ConfigValidationError(
+ f"Unexpected keys for executor config: {extra_options!r}",
+ global_option="executor",
+ )
def _stop_coverage():
diff --git a/poethepoet/executor/poetry.py b/poethepoet/executor/poetry.py
index c282a702d..2e496639a 100644
--- a/poethepoet/executor/poetry.py
+++ b/poethepoet/executor/poetry.py
@@ -2,7 +2,7 @@
from pathlib import Path
from typing import TYPE_CHECKING, Dict, Optional, Sequence, Type
-from ..exceptions import PoeException
+from ..exceptions import ExecutionError
from .base import PoeExecutor
if TYPE_CHECKING:
@@ -20,7 +20,7 @@ class PoetryExecutor(PoeExecutor):
@classmethod
def works_with_context(cls, context: "RunContext") -> bool:
- if "poetry" not in context.config.project["tool"]:
+ if not context.config.is_poetry_project:
return False
return bool(cls._poetry_cmd_from_path())
@@ -61,7 +61,7 @@ def _handle_file_not_found(
) -> int:
poetry_env = self._get_poetry_virtualenv()
error_context = f" using virtualenv {poetry_env!r}" if poetry_env else ""
- raise PoeException(
+ raise ExecutionError(
f"executable {cmd[0]!r} could not be found{error_context}"
) from error
diff --git a/poethepoet/executor/virtualenv.py b/poethepoet/executor/virtualenv.py
index b80c60836..9f6587723 100644
--- a/poethepoet/executor/virtualenv.py
+++ b/poethepoet/executor/virtualenv.py
@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Type
-from ..exceptions import PoeException
+from ..exceptions import ConfigValidationError, ExecutionError
from .base import PoeExecutor
if TYPE_CHECKING:
@@ -42,7 +42,7 @@ def _handle_file_not_found(
) -> int:
venv = self._resolve_virtualenv()
error_context = f" using virtualenv {str(venv.path)!r}" if venv else ""
- raise PoeException(
+ raise ExecutionError(
f"executable {cmd[0]!r} could not be found{error_context}"
) from error
@@ -55,7 +55,7 @@ def _resolve_virtualenv(self) -> "Virtualenv":
)
if venv.valid():
return venv
- raise PoeException(
+ raise ExecutionError(
f"Could not find valid virtualenv at configured location: {venv.path}"
)
@@ -67,22 +67,26 @@ def _resolve_virtualenv(self) -> "Virtualenv":
if hidden_venv.valid():
return hidden_venv
- raise PoeException(
+ raise ExecutionError(
f"Could not find valid virtualenv at either of: {venv.path} or "
- f"{hidden_venv.path}"
+ f"{hidden_venv.path}.\n"
+ "You can configure another location as tool.poe.executor.location"
)
@classmethod
- def validate_executor_config(cls, config: Dict[str, Any]) -> Optional[str]:
+ def validate_executor_config(cls, config: Dict[str, Any]):
"""
Validate that location is a string if given and no other options are given.
"""
if "location" in config and not isinstance(config["location"], str):
- return (
+ raise ConfigValidationError(
"The location option virtualenv executor must be a string not: "
- f"{config['location']!r}"
+ f"{config['location']!r}",
+ global_option="executor",
)
extra_options = set(config.keys()) - {"type", "location"}
if extra_options:
- return f"Unexpected keys for executor config: {extra_options!r}"
- return None
+ raise ConfigValidationError(
+ f"Unexpected keys for executor config: {extra_options!r}",
+ global_option="executor",
+ )
diff --git a/poethepoet/helpers/command/ast.py b/poethepoet/helpers/command/ast.py
index 02ebc715d..9eacc872f 100644
--- a/poethepoet/helpers/command/ast.py
+++ b/poethepoet/helpers/command/ast.py
@@ -297,14 +297,11 @@ def _parse(self, chars: ParseCursor):
return self.__consume_unquoted(chars)
def __consume_single_quoted(self, chars):
- # pylint: disable=invalid-name
SingleQuotedTextCls = self.get_child_node_cls(SingleQuotedText)
self._children.append(SingleQuotedTextCls(chars, self.config))
def __consume_double_quoted(self, chars):
- # pylint: disable=invalid-name
DoubleQuotedTextCls = self.get_child_node_cls(DoubleQuotedText)
- # pylint: disable=invalid-name
ParamExpansionCls = self.get_child_node_cls(ParamExpansion)
while next_char := chars.peek():
@@ -329,11 +326,8 @@ def __consume_double_quoted(self, chars):
raise ParseError("Unexpected end of input with unmatched double quote")
def __consume_unquoted(self, chars):
- # pylint: disable=invalid-name
UnquotedTextCls = self.get_child_node_cls(UnquotedText)
- # pylint: disable=invalid-name
GlobCls = self.get_child_node_cls(Glob)
- # pylint: disable=invalid-name
ParamExpansionCls = self.get_child_node_cls(ParamExpansion)
while next_char := chars.peek():
@@ -366,7 +360,6 @@ def segments(self) -> Tuple[Segment, ...]:
return tuple(self._children)
def _parse(self, chars: ParseCursor):
- # pylint: disable=invalid-name
SegmentCls = self.get_child_node_cls(Segment)
self._children = []
@@ -395,9 +388,7 @@ def comment(self) -> str:
return ""
def _parse(self, chars: ParseCursor):
- # pylint: disable=invalid-name
WordCls = self.get_child_node_cls(Word)
- # pylint: disable=invalid-name
CommentCls = self.get_child_node_cls(Comment)
self._children = []
@@ -439,7 +430,6 @@ def command_lines(self):
return tuple(line for line in self._children if line.words)
def _parse(self, chars: ParseCursor):
- # pylint: disable=invalid-name
LineCls = self.get_child_node_cls(Line)
self._children = []
diff --git a/poethepoet/options.py b/poethepoet/options.py
new file mode 100644
index 000000000..489de7e4d
--- /dev/null
+++ b/poethepoet/options.py
@@ -0,0 +1,270 @@
+import collections
+from keyword import iskeyword
+from typing import (
+ Any,
+ Dict,
+ List,
+ Literal,
+ Mapping,
+ MutableMapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+ get_args,
+ get_origin,
+)
+
+from .exceptions import ConfigValidationError
+
+NoValue = object()
+
+
+class PoeOptions:
+ """
+ A special kind of config object that parses options ...
+ """
+
+ __annotations: Dict[str, Type]
+
+ def __init__(self, **options: Any):
+ for key in self.get_fields():
+ unsanitized_key = key.rstrip("_")
+ if unsanitized_key in options:
+ setattr(self, key, options[unsanitized_key])
+
+ def __getattribute__(self, name: str):
+ if name.endswith("_") and iskeyword(name[:-1]):
+ # keyword attributes are accessed with a "_" suffix
+ name = name[:-1]
+
+ return object.__getattribute__(self, name)
+
+ def __getattr__(self, name: str):
+ if name not in self.get_fields():
+ raise AttributeError(
+ f"{self.__class__.__name__} has no such attribute {name!r}"
+ )
+
+ if name in self.__dict__:
+ return self.__dict__[name]
+ if hasattr(self.__class__, name):
+ return getattr(self.__class__, name)
+ if self.__is_optional(name):
+ return None
+
+ raise AttributeError(
+ f"{self.__class__.__name__} has no value for option {name!r}"
+ )
+
+ @classmethod
+ def parse(
+ cls,
+ source: Union[Mapping[str, Any], list],
+ strict: bool = True,
+ extra_keys: Sequence[str] = tuple(),
+ ):
+ config_keys = {
+ key[:-1] if key.endswith("_") and iskeyword(key[:-1]) else key: vtype
+ for key, vtype in cls.get_fields().items()
+ }
+ if strict:
+ for index, item in enumerate(cls.normalize(source, strict)):
+ options = {}
+ for key, value_type in config_keys.items():
+ if key in item:
+ options[key] = cls._parse_value(
+ index, key, item[key], value_type, strict
+ )
+ elif not hasattr(cls, cls._resolve_key(key)):
+ raise ConfigValidationError(
+ f"Missing required option {key!r}", index=index
+ )
+
+ for key in item:
+ if key not in config_keys and key not in extra_keys:
+ raise ConfigValidationError(
+ f"Unrecognised option {key!r}", index=index
+ )
+
+ result = cls(**options)
+ result.validate()
+ yield result
+
+ else:
+ for index, item in enumerate(cls.normalize(source, strict)):
+ yield cls(
+ **{
+ key: cls._parse_value(index, key, item[key], value_type, strict)
+ for key, value_type in config_keys.items()
+ if key in item
+ }
+ )
+
+ @classmethod
+ def _parse_value(
+ cls, index: int, key: str, value: Any, value_type: Any, strict: bool
+ ):
+ if isinstance(value_type, type) and issubclass(value_type, PoeOptions):
+ return value_type.parse(value, strict=strict)
+
+ if strict:
+ expected_type: Union[Type, Tuple[Type, ...]] = cls._type_of(value_type)
+ if not isinstance(value, expected_type):
+ # Try format expected_type nicely in the error message
+ if not isinstance(expected_type, tuple):
+ expected_type = (expected_type,)
+ formated_type = " | ".join(
+ type_.__name__ for type_ in expected_type if type_ is not type(None)
+ )
+ raise ConfigValidationError(
+ f"Option {key!r} should have a value of type: {formated_type}",
+ index=index,
+ )
+
+ annotation = cls.get_annotation(key)
+ if get_origin(annotation) is Literal:
+ allowed_values = get_args(annotation)
+ if value not in allowed_values:
+ raise ConfigValidationError(
+ f"Option {key!r} must be one of {allowed_values!r}",
+ index=index,
+ )
+
+ # TODO: validate list/dict contents
+
+ return value
+
+ @classmethod
+ def normalize(
+ cls,
+ config: Any,
+ strict: bool = True,
+ ):
+ if isinstance(config, (list, tuple)):
+ yield from config
+ else:
+ yield config
+
+ def validate(self):
+ pass
+
+ def get(self, key: str, default: Any = NoValue) -> Any:
+ """
+ This is the most tolerant way to fetch a config value using the following
+ strategies in priority order:
+
+ 1. Get the config value
+ 2. Return the default value provided as an argument
+ 3. Return the default value declared for this field
+ 4. Return the zero value for the type of this field
+ """
+
+ key = self._resolve_key(key)
+
+ if key in self.__dict__:
+ return self.__dict__[key]
+
+ if default is NoValue:
+ default = getattr(self.__class__, key, default)
+ if default is NoValue:
+ # Fallback to getting getting the zero value for the type of this attribute
+ # e.g. 0, False, empty list, empty dict, etc
+ return self.__get_zero_value(key)
+
+ return default
+
+ def __get_zero_value(self, key: str):
+ type_of_attr = self.type_of(key)
+ if isinstance(type_of_attr, tuple):
+ if type(None) in type_of_attr:
+ # Optional types default to None
+ return None
+ type_of_attr = type_of_attr[0]
+ assert type_of_attr
+ return type_of_attr()
+
+ def __is_optional(self, key: str):
+ # TODO: precache optional options keys?
+ type_of_attr = self.type_of(key)
+ if isinstance(type_of_attr, tuple):
+ return type(None) in type_of_attr
+ return False
+
+ def update(self, options_dict: Dict[str, Any]):
+ new_options_dict = {}
+ for key in self.get_fields().keys():
+ if key in options_dict:
+ new_options_dict[key] = options_dict[key]
+ elif hasattr(self, key):
+ new_options_dict[key] = getattr(self, key)
+
+ @classmethod
+ def type_of(cls, key: str) -> Optional[Union[Type, Tuple[Type, ...]]]:
+ return cls._type_of(cls.get_annotation(key))
+
+ @classmethod
+ def get_annotation(cls, key: str) -> Optional[Type]:
+ return cls.get_fields().get(cls._resolve_key(key))
+
+ @classmethod
+ def _resolve_key(cls, key: str) -> str:
+ """
+ Map from a config key to the config object attribute, which must not but a
+ python keyword.
+ """
+ if iskeyword(key):
+ return f"{key}_"
+ return key
+
+ @classmethod
+ def _type_of(cls, annotation: Any) -> Union[Type, Tuple[Type, ...]]:
+ if get_origin(annotation) is Union:
+ result: List[Type] = []
+ for component in get_args(annotation):
+ component_type = cls._type_of(component)
+ if isinstance(component_type, tuple):
+ result.extend(component_type)
+ else:
+ result.append(component_type)
+ return tuple(result)
+
+ if get_origin(annotation) in (
+ dict,
+ Mapping,
+ MutableMapping,
+ collections.abc.Mapping,
+ collections.abc.MutableMapping,
+ ):
+ return dict
+
+ if get_origin(annotation) in (
+ list,
+ Sequence,
+ collections.abc.Sequence,
+ ):
+ return list
+
+ if get_origin(annotation) is Literal:
+ return tuple({type(arg) for arg in get_args(annotation)})
+
+ return annotation
+
+ @classmethod
+ def get_fields(cls) -> Dict[str, Any]:
+ """
+ Recent python versions removed inheritance for __annotations__
+ so we have to implement it explicitly
+ """
+ if not hasattr(cls, "__annotations"):
+ annotations = {}
+ for base_cls in cls.__bases__:
+ annotations.update(base_cls.__annotations__)
+ annotations.update(cls.__annotations__)
+ cls.__annotations = {
+ key: type_
+ for key, type_ in annotations.items()
+ if not key.startswith("_")
+ }
+ return cls.__annotations
diff --git a/poethepoet/plugin.py b/poethepoet/plugin.py
index f17614af1..18d5a7bcd 100644
--- a/poethepoet/plugin.py
+++ b/poethepoet/plugin.py
@@ -1,5 +1,3 @@
-# pylint: disable=import-error
-
from pathlib import Path
from typing import Any, Dict, List
@@ -52,7 +50,6 @@ def get_poe(cls, application: Application, io: IO):
from poetry.utils.env import EnvManager
poetry_env_path = EnvManager(application.poetry).get().path
- # pylint: disable=bare-except
except: # noqa: E722
poetry_env_path = None
@@ -84,7 +81,6 @@ def activate(self, application: Application) -> None:
try:
return self._activate(application)
- # pylint: disable=bare-except
except: # noqa: E722
import os
import sys
@@ -152,8 +148,6 @@ def _activate(self, application: Application) -> None:
def _get_config(cls, application: Application) -> Dict[str, Any]:
try:
pyproject = application.poetry.pyproject.data
-
- # pylint: disable=bare-except
except: # noqa: E722
# Fallback to loading the config again in case of future failure of the
# above undocumented API
@@ -161,7 +155,17 @@ def _get_config(cls, application: Application) -> Dict[str, Any]:
from .config import PoeConfig
- pyproject = tomlkit.loads(Path(PoeConfig().find_config_file()).read_text())
+ # Try respect poetry's '--directory' if set
+ try:
+ pyproject_path = Path(application.poetry.pyproject_path)
+ except AttributeError:
+ pyproject_path = None
+
+ pyproject = tomlkit.loads(
+ Path(
+ PoeConfig().find_config_file(target_path=pyproject_path)
+ ).read_text()
+ )
return pyproject.get("tool", {}).get("poe", {})
diff --git a/poethepoet/task/__init__.py b/poethepoet/task/__init__.py
index 1a851d1ce..da462a38f 100644
--- a/poethepoet/task/__init__.py
+++ b/poethepoet/task/__init__.py
@@ -1,4 +1,3 @@
-from .base import PoeTask
from .cmd import CmdTask
from .expr import ExprTask
from .ref import RefTask
@@ -8,7 +7,6 @@
from .switch import SwitchTask
__all__ = [
- "PoeTask",
"CmdTask",
"ExprTask",
"RefTask",
diff --git a/poethepoet/task/args.py b/poethepoet/task/args.py
index ec32e91e2..bd86ac700 100644
--- a/poethepoet/task/args.py
+++ b/poethepoet/task/args.py
@@ -3,9 +3,10 @@
Any,
Dict,
List,
+ Literal,
+ Mapping,
Optional,
Sequence,
- Set,
Tuple,
Type,
Union,
@@ -16,6 +17,9 @@
from ..env.manager import EnvVarsManager
+from ..exceptions import ConfigValidationError
+from ..options import PoeOptions
+
ArgParams = Dict[str, Any]
ArgsDef = Union[List[str], List[ArgParams], Dict[str, ArgParams]]
@@ -37,61 +41,170 @@
}
-class PoeTaskArgs:
- _args: Tuple[ArgParams, ...]
-
- def __init__(
- self,
- args_def: ArgsDef,
- task_name: str,
- program_name: str,
- env: "EnvVarsManager",
- ):
- self._args = self._normalize_args_def(args_def)
- self._program_name = program_name
- self._task_name = task_name
- self._env = env
+class ArgSpec(PoeOptions):
+ default: Optional[Union[str, int, float, bool]] = None
+ help: str = ""
+ name: str
+ options: Union[list, tuple]
+ positional: Union[bool, str] = False
+ required: bool = False
+ type: Literal["string", "float", "integer", "boolean"] = "string"
+ multiple: Union[bool, int] = False
@classmethod
- def _normalize_args_def(cls, args_def: ArgsDef) -> Tuple[ArgParams, ...]:
+ def normalize(cls, args_def: ArgsDef, strict: bool = True):
"""
- args_def can be defined as a dictionary of ArgParams, or a list of strings, or
- ArgParams. Here we normalize it to a list of ArgParams, assuming that it has
- already been validated.
+ Becuase arguments can be declared with different structures
+ (i.e. dict or list), this function normalizes the input into a list of
+ dictionaries with necessary keys.
+
+ This is also where we do any validation that requires access to the raw
+ config.
"""
- result = []
if isinstance(args_def, list):
for item in args_def:
if isinstance(item, str):
- result.append({"name": item, "options": (f"--{item}",)})
- else:
- result.append(
- dict(
- item,
- options=cls._get_arg_options_list(item),
- )
+ yield {"name": item, "options": (f"--{item}",)}
+ elif isinstance(item, dict):
+ yield dict(
+ item,
+ options=cls._get_arg_options_list(item, strict=strict),
)
- else:
+ elif strict:
+ raise ConfigValidationError(
+ f"Argument {item!r} has invlaid type, a string or dict is "
+ "expected"
+ )
+
+ elif isinstance(args_def, dict):
for name, params in args_def.items():
- result.append(
- dict(
- params,
- name=name,
- options=cls._get_arg_options_list(params, name),
+ if strict and "name" in params:
+ raise ConfigValidationError(
+ f"Unexpected 'name' option for argument {name!r}"
)
+ yield dict(
+ params,
+ name=name,
+ options=cls._get_arg_options_list(params, name, strict),
)
- return tuple(result)
+
+ @classmethod
+ def parse(
+ cls,
+ source: Union[Mapping[str, Any], list],
+ strict: bool = True,
+ extra_keys: Sequence[str] = tuple(),
+ ):
+ """
+ Override parse function to perform validations that require considering all
+ argument declarations at once.
+ """
+ result = tuple(super().parse(source, strict, extra_keys))
+
+ if strict:
+ arg_names = set()
+ positional_multiple = None
+ for arg in result:
+ if arg.name in arg_names:
+ raise ConfigValidationError(f"Duplicate argument name {arg.name!r}")
+ arg_names.add(arg.name)
+
+ if arg.positional:
+ if positional_multiple:
+ raise ConfigValidationError(
+ f"Only the last positional arg of task may accept"
+ f" multiple values (not {positional_multiple!r})."
+ )
+ if arg.multiple:
+ positional_multiple = arg.name
+
+ yield from result
@staticmethod
- def _get_arg_options_list(arg: ArgParams, name: Optional[str] = None):
+ def _get_arg_options_list(
+ arg: ArgParams, name: Optional[str] = None, strict: bool = True
+ ):
position = arg.get("positional", False)
- name = name or arg["name"]
+ name = name or arg.get("name")
if position:
+ if strict and arg.get("options"):
+ raise ConfigValidationError(
+ f"Positional argument {name!r} may not declare options"
+ )
+ # Fill in the options param in a way that makes sesne for argparse
if isinstance(position, str):
return [position]
return [name]
return tuple(arg.get("options", (f"--{name}",)))
+ def validate(self):
+ try:
+ return self._validate()
+ except ConfigValidationError as error:
+ error.context = f"Invalid argument {self.name!r} declared"
+ raise
+
+ def _validate(self):
+ if not self.name.replace("-", "_").isidentifier():
+ raise ConfigValidationError(
+ f"Argument name {self.name!r} is not a valid 'identifier',\n"
+ f"see the following documentation for details "
+ f"https://docs.python.org/3/reference/lexical_analysis.html#identifiers"
+ )
+
+ if self.positional:
+ if self.type == "boolean":
+ raise ConfigValidationError(
+ f"Positional argument {self.name!r} may not have type 'boolean'"
+ )
+
+ if isinstance(self.positional, str) and not self.positional.isidentifier():
+ raise ConfigValidationError(
+ f"positional name {self.positional!r} for arg {self.name!r} is "
+ "not a valid 'identifier'\n"
+ "see the following documentation for details "
+ "https://docs.python.org/3/reference/lexical_analysis.html#identifiers"
+ )
+
+ if (
+ not isinstance(self.multiple, bool)
+ and isinstance(self.multiple, int)
+ and self.multiple < 2
+ ):
+ raise ConfigValidationError(
+ "The 'multiple' option accepts a boolean or integer >= 2"
+ )
+
+ if self.multiple is not False and self.type == "boolean":
+ raise ConfigValidationError(
+ "Argument with type 'boolean' may not delcare option 'multiple'"
+ )
+
+
+class PoeTaskArgs:
+ _args: Tuple[ArgSpec, ...]
+
+ def __init__(self, args_def: ArgsDef, task_name: str):
+ self._task_name = task_name
+ self._args = self._parse_args_def(args_def)
+
+ def _parse_args_def(self, args_def: ArgsDef):
+ try:
+ return tuple(ArgSpec.parse(args_def))
+ except ConfigValidationError as error:
+ if isinstance(error.index, int):
+ if isinstance(args_def, list):
+ item = args_def[error.index]
+ if arg_name := (isinstance(item, dict) and item.get("name")):
+ arg_ref = arg_name
+ elif arg_name := tuple(args_def.keys())[error.index]:
+ arg_ref = arg_name
+ else:
+ arg_ref = error.index
+ error.context = f"Invalid argument {arg_ref!r} declared"
+ error.task_name = self._task_name
+ raise
+
@classmethod
def get_help_content(
cls, args_def: Optional[ArgsDef]
@@ -107,165 +220,30 @@ def format_default(arg) -> str:
return [
(arg["options"], arg.get("help", ""), format_default(arg))
- for arg in cls._normalize_args_def(args_def)
+ for arg in ArgSpec.normalize(args_def, strict=False)
]
- @classmethod
- def validate_def(cls, task_name: str, args_def: ArgsDef) -> Optional[str]:
- arg_names: Set[str] = set()
- arg_params = []
-
- if isinstance(args_def, list):
- for item in args_def:
- # can be a list of strings (just arg name) or ArgConfig dictionaries
- if isinstance(item, str):
- arg_name = item
- elif isinstance(item, dict):
- arg_name = item.get("name", "")
- arg_params.append((item, arg_name, task_name))
- else:
- return f"Arg {item!r} of task {task_name!r} has invlaid type"
- error = cls._validate_name(arg_name, task_name, arg_names)
- if error:
- return error
-
- elif isinstance(args_def, dict):
- for arg_name, params in args_def.items():
- error = cls._validate_name(arg_name, task_name, arg_names)
- if error:
- return error
- if "name" in params:
- return (
- f"Unexpected 'name' option for arg {arg_name!r} of task "
- f"{task_name!r}"
- )
- arg_params.append((params, arg_name, task_name))
-
- positional_multiple = None
- for params, arg_name, task_name in arg_params:
- error = cls._validate_type(params, arg_name, task_name)
- if error:
- return error
-
- error = cls._validate_params(params, arg_name, task_name)
- if error:
- return error
-
- if params.get("positional", False):
- if positional_multiple:
- return (
- f"Only the last positional arg of task {task_name!r} may accept"
- f" multiple values ({positional_multiple!r})."
- )
- if params.get("multiple", False):
- positional_multiple = arg_name
-
- return None
-
- @classmethod
- def _validate_name(
- cls, name: Any, task_name: str, arg_names: Set[str]
- ) -> Optional[str]:
- if not isinstance(name, str):
- return f"Arg name {name!r} of task {task_name!r} should be a string"
- if not name.replace("-", "_").isidentifier():
- return (
- f"Arg name {name!r} of task {task_name!r} is not a valid 'identifier'"
- f"see the following documentation for details"
- f"https://docs.python.org/3/reference/lexical_analysis.html#identifiers"
- )
- if name in arg_names:
- return f"Duplicate arg name {name!r} for task {task_name!r}"
- arg_names.add(name)
- return None
-
- @classmethod
- def _validate_params(
- cls, params: ArgParams, arg_name: str, task_name: str
- ) -> Optional[str]:
- for param, value in params.items():
- if param not in arg_param_schema:
- return (
- f"Invalid option {param!r} for arg {arg_name!r} of task "
- f"{task_name!r}"
- )
- if not isinstance(value, arg_param_schema[param]):
- return (
- f"Invalid value for option {param!r} of arg {arg_name!r} of"
- f" task {task_name!r}"
- )
-
- positional = params.get("positional", False)
- if positional:
- if params.get("type") == "boolean":
- return (
- f"Positional argument {arg_name!r} of task {task_name!r} may not"
- "have type 'boolean'"
- )
- if params.get("options") is not None:
- return (
- f"Positional argument {arg_name!r} of task {task_name!r} may not"
- "have options defined"
- )
- if isinstance(positional, str) and not positional.isidentifier():
- return (
- f"positional name {positional!r} for arg {arg_name!r} of task "
- f"{task_name!r} is not a valid 'identifier' see the following "
- "documentation for details"
- "https://docs.python.org/3/reference/lexical_analysis.html#identifiers"
- )
-
- multiple = params.get("multiple", False)
- if (
- not isinstance(multiple, bool)
- and isinstance(multiple, int)
- and multiple < 2
- ):
- return (
- f"The multiple option for arg {arg_name!r} of {task_name!r}"
- " must be given a boolean or integer >= 2"
- )
- if multiple is not False and params.get("type") == "boolean":
- return (
- "Incompatible param 'multiple' for arg {arg_name!r} of {task_name!r} "
- "with type: 'boolean'"
- )
-
- return None
-
- @classmethod
- def _validate_type(
- cls, params: ArgParams, arg_name: str, task_name: str
- ) -> Optional[str]:
- if "type" in params and params["type"] not in arg_types:
- return (
- f"{params['type']!r} is not a valid type for arg {arg_name!r} of task "
- f"{task_name!r}. Choose one of "
- "{"
- f'{" ".join(sorted(str_type for str_type in arg_types.keys()))}'
- "}"
- )
- return None
-
- def build_parser(self) -> "ArgumentParser":
+ def build_parser(
+ self, env: "EnvVarsManager", program_name: str
+ ) -> "ArgumentParser":
import argparse
parser = argparse.ArgumentParser(
- prog=f"{self._program_name} {self._task_name}",
+ prog=f"{program_name} {self._task_name}",
add_help=False,
allow_abbrev=False,
)
for arg in self._args:
parser.add_argument(
- *arg["options"],
- **self._get_argument_params(arg),
+ *arg.options,
+ **self._get_argument_params(arg, env),
)
return parser
- def _get_argument_params(self, arg: ArgParams):
+ def _get_argument_params(self, arg: ArgSpec, env: "EnvVarsManager"):
default = arg.get("default")
if isinstance(default, str):
- default = self._env.fill_template(default)
+ default = env.fill_template(default)
result = {
"default": default,
@@ -288,7 +266,7 @@ def _get_argument_params(self, arg: ArgParams):
if not multiple and not required:
result["nargs"] = "?"
else:
- result["dest"] = arg["name"]
+ result["dest"] = arg.name
result["required"] = required
if arg_type == "boolean":
@@ -298,15 +276,13 @@ def _get_argument_params(self, arg: ArgParams):
return result
- def parse(self, args: Sequence[str]) -> Dict[str, str]:
- parsed_args = vars(self.build_parser().parse_args(args))
-
+ def parse(self, args: Sequence[str], env: "EnvVarsManager", program_name: str):
+ parsed_args = vars(self.build_parser(env, program_name).parse_args(args))
# Ensure positional args are still exposed by name even if they were parsed with
# alternate identifiers
for arg in self._args:
- if isinstance(arg.get("positional"), str):
- parsed_args[arg["name"]] = parsed_args[arg["positional"]]
- del parsed_args[arg["positional"]]
-
+ if isinstance(arg.positional, str):
+ parsed_args[arg.name] = parsed_args[arg.positional]
+ del parsed_args[arg.positional]
# args named with dash case are converted to snake case before being exposed
return {name.replace("-", "_"): value for name, value in parsed_args.items()}
diff --git a/poethepoet/task/base.py b/poethepoet/task/base.py
index 850fd5263..ec2b7c59a 100644
--- a/poethepoet/task/base.py
+++ b/poethepoet/task/base.py
@@ -5,28 +5,30 @@
from typing import (
TYPE_CHECKING,
Any,
- Collection,
+ ClassVar,
Dict,
Iterator,
List,
+ Mapping,
NamedTuple,
Optional,
+ Sequence,
Tuple,
Type,
Union,
)
-from ..exceptions import PoeException
+from ..exceptions import ConfigValidationError, PoeException
+from ..options import PoeOptions
if TYPE_CHECKING:
- from ..config import PoeConfig
+ from ..config import ConfigPartition, PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
from ..ui import PoeUi
+ from .args import PoeTaskArgs
-TaskDef = Union[str, Dict[str, Any], List[Union[str, Dict[str, Any]]]]
-
_TASK_NAME_PATTERN = re.compile(r"^\w[\w\d\-\_\+\:]*$")
@@ -40,154 +42,342 @@ def __init__(cls, *args):
super().__init__(*args)
if cls.__name__ == "PoeTask":
return
+
assert isinstance(getattr(cls, "__key__", None), str)
- assert isinstance(getattr(cls, "__options__", None), dict)
+ assert issubclass(getattr(cls, "TaskOptions", None), PoeOptions)
PoeTask._PoeTask__task_types[cls.__key__] = cls
+ # Give each TaskSpec a reference to its parent PoeTask
+ if "TaskSpec" in cls.__dict__:
+ cls.TaskSpec.task_type = cls
+
+
+TaskContent = Union[str, Sequence[Union[str, Mapping[str, Any]]]]
+
+TaskDef = Union[str, Mapping[str, Any], Sequence[Union[str, Mapping[str, Any]]]]
+
-TaskContent = Union[str, List[Union[str, Dict[str, Any]]]]
+class TaskSpecFactory:
+ __cache: Dict[str, "PoeTask.TaskSpec"]
+ config: "PoeConfig"
+
+ def __init__(self, config: "PoeConfig"):
+ self.__cache = {}
+ self.config = config
+
+ def __contains__(self, other) -> bool:
+ return other in self.__cache
+
+ def get(
+ self,
+ task_name: str,
+ task_def: Optional[TaskDef] = None,
+ task_type: Optional[str] = None,
+ parent: Optional["PoeTask.TaskSpec"] = None,
+ ) -> "PoeTask.TaskSpec":
+ if task_def and parent:
+ # This is probably a subtask and will be cached by the parent task_spec
+ if not task_type:
+ task_type = PoeTask.resolve_task_type(task_def, self.config)
+ assert task_type
+ return self.create(
+ task_name, task_type, task_def, source=parent.source, parent=parent
+ )
+
+ if task_name not in self.__cache:
+ self.load(task_name)
+
+ return self.__cache[task_name]
+
+ def create(
+ self,
+ task_name: str,
+ task_type: str,
+ task_def: TaskDef,
+ source: "ConfigPartition",
+ parent: Optional["PoeTask.TaskSpec"] = None,
+ ) -> "PoeTask.TaskSpec":
+ """
+ A parent task should be provided when this task is defined inline within another
+ task, for example as part of a sequence.
+ """
+ if not isinstance(task_def, dict):
+ task_def = {task_type: task_def}
+
+ return PoeTask.lookup_task_spec_cls(task_type)(
+ name=task_name,
+ task_def=task_def,
+ factory=self,
+ source=source,
+ parent=parent,
+ )
+ def load_all(self):
+ for task_name in self.config.task_names:
+ self.load(task_name)
-class TaskInheritance(NamedTuple):
+ return self
+
+ def load(self, task_name: str):
+ task_def, config_partition = self.config.lookup_task(task_name)
+
+ if task_def is None or config_partition is None:
+ raise PoeException(f"Cannot instantiate unknown task {task_name!r}")
+
+ task_type = PoeTask.resolve_task_type(task_def, self.config)
+ if not task_type:
+ raise ConfigValidationError(
+ "Task definition must be a string, a list, or a table including exactly"
+ " one task key\n"
+ f"Available task keys: {set(PoeTask.get_task_types())!r}",
+ task_name=task_name,
+ filename=(
+ None if config_partition.is_primary else str(config_partition.path)
+ ),
+ )
+
+ self.__cache[task_name] = self.create(
+ task_name, task_type, task_def, source=config_partition
+ )
+
+ def __iter__(self):
+ return iter(self.__cache.values())
+
+
+class TaskContext(NamedTuple):
"""
- Collection of inheritanced config from a parent task to a child task
+ Collection of contextual config inherited from a parent task to a child task
"""
+ config: "PoeConfig"
cwd: str
+ ui: "PoeUi"
+ specs: "TaskSpecFactory"
@classmethod
def from_task(cls, parent_task: "PoeTask"):
- return cls(cwd=str(parent_task.options.get("cwd", parent_task.inheritance.cwd)))
+ return cls(
+ config=parent_task.ctx.config,
+ cwd=str(parent_task.spec.options.get("cwd", parent_task.ctx.cwd)),
+ specs=parent_task.ctx.specs,
+ ui=parent_task.ctx.ui,
+ )
class PoeTask(metaclass=MetaPoeTask):
- name: str
- content: TaskContent
- options: Dict[str, Any]
- inheritance: TaskInheritance
- _parsed_args: Optional[Tuple[Dict[str, str], Tuple[str, ...]]] = None
+ __key__: ClassVar[str]
+ __content_type__: ClassVar[Type] = str
+
+ class TaskOptions(PoeOptions):
+ args: Optional[Union[dict, list]] = None
+ capture_stdout: Optional[str] = None
+ cwd: Optional[str] = None
+ deps: Optional[Sequence[str]] = None
+ env: Optional[dict] = None
+ envfile: Optional[Union[str, list]] = None
+ executor: Optional[dict] = None
+ help: Optional[str] = None
+ uses: Optional[dict] = None
+
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ if self.help and "\n" in self.help:
+ raise ConfigValidationError(
+ "Help messages must not contain line breaks"
+ )
+
+ class TaskSpec:
+ name: str
+ content: TaskContent
+ options: "PoeTask.TaskOptions"
+ task_type: Type["PoeTask"]
+ source: "ConfigPartition"
+ parent: Optional["PoeTask.TaskSpec"] = None
+
+ _args: Optional["PoeTaskArgs"] = None
+
+ def __init__(
+ self,
+ name: str,
+ task_def: Dict[str, Any],
+ factory: TaskSpecFactory,
+ source: "ConfigPartition",
+ parent: Optional["PoeTask.TaskSpec"] = None,
+ ):
+ self.name = name
+ self.content = task_def[self.task_type.__key__]
+ self.options = self._parse_options(task_def)
+ self.source = source
+ self.parent = parent
+
+ def _parse_options(self, task_def: Dict[str, Any]):
+ try:
+ return next(
+ self.task_type.TaskOptions.parse(
+ task_def, extra_keys=(self.task_type.__key__,)
+ )
+ )
+ except ConfigValidationError as error:
+ error.task_name = self.name
+ raise
+
+ def get_task_env(
+ self,
+ parent_env: "EnvVarsManager",
+ uses_values: Optional[Mapping[str, str]] = None,
+ ) -> "EnvVarsManager":
+ """
+ Resolve the EnvVarsManager for this task, relative to the given parent_env
+ """
+ task_envfile = self.options.get("envfile")
+ task_env = self.options.get("env")
+
+ result = parent_env.clone()
+
+ # Include env vars from outputs of upstream dependencies
+ if uses_values:
+ result.update(uses_values)
+
+ result.set("POE_CONF_DIR", str(self.source.config_dir))
+ result.apply_env_config(
+ task_envfile,
+ task_env,
+ config_dir=self.source.config_dir,
+ config_working_dir=self.source.cwd,
+ )
+
+ return result
+
+ @property
+ def args(self) -> Optional["PoeTaskArgs"]:
+ from .args import PoeTaskArgs
+
+ if not self._args and self.options.args:
+ self._args = PoeTaskArgs(self.options.args, self.name)
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {}
- __content_type__: Type = str
- __base_options: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "args": (dict, list),
- "capture_stdout": str,
- "cwd": str,
- "deps": list,
- "env": dict,
- "envfile": (str, list),
- "executor": dict,
- "help": str,
- "uses": dict,
- }
- __task_types: Dict[str, Type["PoeTask"]] = {}
+ return self._args
+ def create_task(
+ self,
+ invocation: Tuple[str, ...],
+ ctx: TaskContext,
+ capture_stdout: Union[str, bool] = False,
+ ) -> "PoeTask":
+ return self.task_type(
+ spec=self,
+ invocation=invocation,
+ capture_stdout=capture_stdout,
+ ctx=ctx,
+ )
+
+ def validate(self, config: "PoeConfig", task_specs: TaskSpecFactory):
+ try:
+ self._base_validations(config, task_specs)
+ self._task_validations(config, task_specs)
+ except ConfigValidationError as error:
+ error.task_name = self.name
+ raise
+
+ def _base_validations(self, config: "PoeConfig", task_specs: TaskSpecFactory):
+ """
+ Perform validations on this TaskSpec that apply to all task types
+ """
+ if not (self.name[0].isalpha() or self.name[0] == "_"):
+ raise ConfigValidationError(
+ "Task names must start with a letter or underscore."
+ )
+
+ if not self.parent and not _TASK_NAME_PATTERN.match(self.name):
+ raise ConfigValidationError(
+ "Task names characters must be alphanumeric, colon, underscore or "
+ "dash."
+ )
+
+ if not isinstance(self.content, self.task_type.__content_type__):
+ raise ConfigValidationError(
+ f"Content for {self.task_type.__name__} must be a "
+ f"{self.task_type.__content_type__.__name__}"
+ )
+
+ if self.options.deps:
+ for dep in self.options.deps:
+ dep_task_name = dep.split(" ", 1)[0]
+ if dep_task_name not in task_specs:
+ raise ConfigValidationError(
+ "'deps' option includes reference to unknown task: "
+ f"{dep_task_name!r}"
+ )
+
+ if task_specs.get(dep_task_name).options.get("use_exec", False):
+ raise ConfigValidationError(
+ f"'deps' option includes reference to task with "
+ f"'use_exec' set to true: {dep_task_name!r}"
+ )
+
+ if self.options.uses:
+ from ..helpers import is_valid_env_var
+
+ for key, dep in self.options.uses.items():
+ if not is_valid_env_var(key):
+ raise ConfigValidationError(
+ f"'uses' option includes invalid key: {key!r}"
+ )
+
+ dep_task_name = dep.split(" ", 1)[0]
+ if dep_task_name not in task_specs:
+ raise ConfigValidationError(
+ "'uses' options includes reference to unknown task: "
+ f"{dep_task_name!r}"
+ )
+
+ referenced_task = task_specs.get(dep_task_name)
+ if referenced_task.options.get("use_exec", False):
+ raise ConfigValidationError(
+ f"'uses' option references task with 'use_exec' set to "
+ f"true: {dep_task_name!r}"
+ )
+ if referenced_task.options.get("capture_stdout"):
+ raise ConfigValidationError(
+ f"'uses' option references task with 'capture_stdout' "
+ f"option set: {dep_task_name!r}"
+ )
+
+ def _task_validations(self, config: "PoeConfig", task_specs: TaskSpecFactory):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+
+ spec: TaskSpec
+ ctx: TaskContext
+ _parsed_args: Optional[Tuple[Dict[str, str], Tuple[str, ...]]] = None
+
+ __task_types: ClassVar[Dict[str, Type["PoeTask"]]] = {}
__upstream_invocations: Optional[
Dict[str, Union[List[Tuple[str, ...]], Dict[str, Tuple[str, ...]]]]
] = None
def __init__(
self,
- name: str,
- content: TaskContent,
- options: Dict[str, Any],
- ui: "PoeUi",
- config: "PoeConfig",
+ spec: TaskSpec,
invocation: Tuple[str, ...],
- capture_stdout: bool = False,
- inheritance: Optional[TaskInheritance] = None,
+ ctx: TaskContext,
+ capture_stdout: Union[str, bool] = False,
):
- self.name = name
- self.content = content
- self.options = dict(options, capture_stdout=True) if capture_stdout else options
- self._ui = ui
- self._config = config
- self._is_windows = sys.platform == "win32"
+ self.spec = spec
self.invocation = invocation
- self.inheritance = inheritance or TaskInheritance(cwd=str(config.project_dir))
-
- @classmethod
- def from_config(
- cls,
- task_name: str,
- config: "PoeConfig",
- ui: "PoeUi",
- invocation: Tuple[str, ...],
- capture_stdout: Optional[bool] = None,
- inheritance: Optional[TaskInheritance] = None,
- ) -> "PoeTask":
- task_def = config.tasks.get(task_name)
- if not task_def:
- raise PoeException(f"Cannot instantiate unknown task {task_name!r}")
- return cls.from_def(
- task_def,
- task_name,
- config,
- ui,
- invocation=invocation,
- capture_stdout=capture_stdout,
- inheritance=inheritance,
- )
-
- @classmethod
- def from_def(
- cls,
- task_def: TaskDef,
- task_name: str,
- config: "PoeConfig",
- ui: "PoeUi",
- invocation: Tuple[str, ...],
- array_item: Union[bool, str] = False,
- capture_stdout: Optional[bool] = None,
- inheritance: Optional[TaskInheritance] = None,
- ) -> "PoeTask":
- task_type = cls.resolve_task_type(task_def, config, array_item)
- if task_type is None:
- # Something is wrong with this task_def
- raise cls.Error(cls.validate_def(task_name, task_def, config))
-
- options: Dict[str, Any] = {}
- if capture_stdout is not None:
- # Override config because we want to specifically capture the stdout of this
- # task for internal use
- options["capture_stdout"] = capture_stdout
-
- if isinstance(task_def, (str, list)):
- task_def = cls.normalize_task_def(
- task_def, config, task_type=cls.__task_types[task_type]
- )
+ self.ctx = ctx
+ self.capture_stdout = spec.options.capture_stdout or capture_stdout
+ self._is_windows = sys.platform == "win32"
- assert isinstance(task_def, dict)
- options = dict(task_def, **options)
- content = options.pop(task_type)
- return cls.__task_types[task_type](
- name=task_name,
- content=content,
- options=options,
- ui=ui,
- config=config,
- invocation=invocation,
- inheritance=inheritance,
- )
+ @property
+ def name(self):
+ return self.spec.name
@classmethod
- def normalize_task_def(
- cls,
- task_def: TaskDef,
- config: "PoeConfig",
- *,
- task_type: Optional[Type["PoeTask"]] = None,
- array_item: Union[bool, str] = False,
- ):
- if isinstance(task_def, dict):
- return task_def
-
- if not task_type:
- task_type_key = cls.resolve_task_type(task_def, config, array_item)
- assert task_type_key
- task_type = cls.__task_types[task_type_key]
-
- return {getattr(task_type, "__key__", "__key_unknown__"): task_def}
+ def lookup_task_spec_cls(cls, task_key: str) -> Type[TaskSpec]:
+ return cls.__task_types[task_key].TaskSpec
@classmethod
def resolve_task_type(
@@ -216,15 +406,21 @@ def resolve_task_type(
return None
+ def _parse_named_args(
+ self, extra_args: Sequence[str], env: "EnvVarsManager"
+ ) -> Optional[Dict[str, str]]:
+ if task_args := self.spec.args:
+ return task_args.parse(extra_args, env, self.ctx.ui.program_name)
+
+ return None
+
def get_parsed_arguments(
self, env: "EnvVarsManager"
) -> Tuple[Dict[str, str], Tuple[str, ...]]:
if self._parsed_args is None:
all_args = self.invocation[1:]
- if args_def := self.options.get("args"):
- from .args import PoeTaskArgs
-
+ if task_args := self.spec.args:
try:
split_index = all_args.index("--")
option_args = all_args[:split_index]
@@ -234,9 +430,7 @@ def get_parsed_arguments(
extra_args = tuple()
self._parsed_args = (
- PoeTaskArgs(args_def, self.name, self._ui.program_name, env).parse(
- option_args
- ),
+ task_args.parse(option_args, env, self.ctx.ui.program_name),
extra_args,
)
@@ -272,11 +466,9 @@ def run(
)
return 0
- task_env = context.get_task_env(
- parent_env,
- self.options.get("envfile"),
- self.options.get("env"),
- upstream_invocations["uses"],
+ task_env = self.spec.get_task_env(
+ parent_env or context.env,
+ context._get_dep_values(upstream_invocations["uses"]),
)
if environ.get("POE_DEBUG"):
@@ -302,19 +494,19 @@ def _get_executor(self, context: "RunContext", env: "EnvVarsManager"):
self.invocation,
env,
working_dir=self.get_working_dir(env),
- executor_config=self.options.get("executor"),
- capture_stdout=self.options.get("capture_stdout", False),
+ executor_config=self.spec.options.get("executor"),
+ capture_stdout=self.capture_stdout,
)
def get_working_dir(
self,
env: "EnvVarsManager",
) -> Path:
- cwd_option = env.fill_template(self.options.get("cwd", self.inheritance.cwd))
+ cwd_option = env.fill_template(self.spec.options.get("cwd", self.ctx.cwd))
working_dir = Path(cwd_option)
if not working_dir.is_absolute():
- working_dir = self._config.project_dir / working_dir
+ working_dir = self.ctx.config.project_dir / working_dir
return working_dir
@@ -336,20 +528,20 @@ def _get_upstream_invocations(self, context: "RunContext"):
"""
import shlex
+ options = self.spec.options
+
if self.__upstream_invocations is None:
- env = context.get_task_env(
- None, self.options.get("envfile"), self.options.get("env")
- )
+ env = self.spec.get_task_env(context.env)
env.update(self.get_parsed_arguments(env)[0])
self.__upstream_invocations = {
"deps": [
tuple(shlex.split(env.fill_template(task_ref)))
- for task_ref in self.options.get("deps", tuple())
+ for task_ref in options.get("deps", tuple())
],
"uses": {
key: tuple(shlex.split(env.fill_template(task_ref)))
- for key, task_ref in self.options.get("uses", {}).items()
+ for key, task_ref in options.get("uses", {}).items()
},
}
@@ -358,175 +550,21 @@ def _get_upstream_invocations(self, context: "RunContext"):
def _instantiate_dep(
self, invocation: Tuple[str, ...], capture_stdout: bool
) -> "PoeTask":
- return self.from_config(
- invocation[0],
- config=self._config,
- ui=self._ui,
+ return self.ctx.specs.get(invocation[0]).create_task(
invocation=invocation,
+ ctx=TaskContext(
+ config=self.ctx.config,
+ cwd=str(self.ctx.config.project_dir),
+ specs=self.ctx.specs,
+ ui=self.ctx.ui,
+ ),
capture_stdout=capture_stdout,
)
def has_deps(self) -> bool:
- return bool(self.options.get("deps", False) or self.options.get("uses", False))
-
- @classmethod
- def validate_def(
- cls,
- task_name: str,
- task_def: TaskDef,
- config: "PoeConfig",
- *,
- anonymous: bool = False,
- extra_options: Collection[str] = tuple(),
- ) -> Optional[str]:
- """
- Check the given task name and definition for validity and return a message
- describing the first encountered issue if any.
- If anonymous is set to True then task_name is not validated.
- """
- if not anonymous and (not (task_name[0].isalpha() or task_name[0] == "_")):
- return (
- f"Invalid task name: {task_name!r}. Task names must start with a letter"
- " or underscore."
- )
-
- if not anonymous and not _TASK_NAME_PATTERN.match(task_name):
- return (
- f"Invalid task name: {task_name!r}. Task names characters must be "
- "alphanumeric, colon, underscore or dash."
- )
-
- if isinstance(task_def, dict):
- task_type_keys = set(task_def.keys()).intersection(cls.__task_types)
- if len(task_type_keys) != 1:
- return (
- f"Invalid task: {task_name!r}. Task definition must include exactly"
- f" one task key from {set(cls.__task_types)!r}"
- )
- task_type_key = next(iter(task_type_keys))
- task_content = task_def[task_type_key]
- task_type = cls.__task_types[task_type_key]
- if not isinstance(task_content, task_type.__content_type__):
- return (
- f"Invalid task: {task_name!r}. Content for {task_type.__name__} "
- f"must be a {task_type.__content_type__.__name__}"
- )
-
- for key in set(task_def) - {task_type_key}:
- expected_type = cls.__base_options.get(
- key, task_type.__options__.get(key)
- )
- if expected_type is None:
- if key not in extra_options:
- return (
- f"Invalid task: {task_name!r}. Unrecognised option "
- f"{key!r} for task of type: {task_type_key}."
- )
- elif not isinstance(task_def[key], expected_type):
- return (
- f"Invalid task: {task_name!r}. Option {key!r} should "
- f"have a value of type {expected_type!r}"
- )
- else:
- if hasattr(task_type, "_validate_task_def"):
- task_type_issue = task_type._validate_task_def(
- task_name, task_def, config
- )
- if task_type_issue:
- return task_type_issue
-
- if "args" in task_def:
- from .args import PoeTaskArgs
-
- return PoeTaskArgs.validate_def(task_name, task_def["args"])
-
- if "cwd" in task_def:
- path = Path(config.project_dir).joinpath(task_def["cwd"]).resolve()
- if not str(path).startswith(config.project_dir):
- return (
- f"Invalid task: {task_name!r}. cwd option may not specify a "
- "directory outside of the project."
- )
-
- if "\n" in task_def.get("help", ""):
- return (
- f"Invalid task: {task_name!r}. Help messages cannot contain "
- "line breaks"
- )
-
- if task_def.get("use_exec") and task_def.get("capture_stdout"):
- return (
- f"Invalid task: {task_name!r}, 'use_exec' and 'capture_stdout'"
- " options cannot be both provided on the same task."
- )
-
- all_task_names = set(config.tasks.keys())
-
- if "deps" in task_def:
- for dep in task_def["deps"]:
- dep_task_name = dep.split(" ", 1)[0]
- if dep_task_name not in all_task_names:
- return (
- f"Invalid task: {task_name!r}. deps options contains "
- f"reference to unknown task: {dep_task_name!r}"
- )
-
- referenced_task = config.tasks[dep_task_name]
- if isinstance(referenced_task, dict) and referenced_task.get(
- "use_exec"
- ):
- return (
- f"Invalid task: {task_name!r}. deps options contains "
- "reference to task with use_exec set to true: "
- f"{dep_task_name!r}"
- )
-
- if "uses" in task_def:
- from ..helpers import is_valid_env_var
-
- for key, dep in task_def["uses"].items():
- if not is_valid_env_var(key):
- return (
- f"Invalid task: {task_name!r} uses options contains invalid"
- f" key: {key!r}"
- )
-
- dep_task_name = dep.split(" ", 1)[0]
- if dep_task_name not in all_task_names:
- return (
- f"Invalid task: {task_name!r}. uses options contains "
- f"reference to unknown task: {dep_task_name!r}"
- )
-
- referenced_task = config.tasks[dep_task_name]
- if isinstance(referenced_task, dict):
- if referenced_task.get("use_exec"):
- return (
- f"Invalid task: {task_name!r}, 'uses' option references"
- " task with 'use_exec' set to true: "
- f"{dep_task_name!r}"
- )
- if referenced_task.get("capture_stdout"):
- return (
- f"Invalid task: {task_name!r}, 'uses' option references"
- " task with 'capture_stdout' option set: "
- f"{dep_task_name!r}"
- )
-
- elif isinstance(task_def, list):
- task_type_key = config.default_array_task_type
- task_type = cls.__task_types[task_type_key]
- normalized_task_def = cls.normalize_task_def(
- task_def, config, task_type=task_type
- )
- if hasattr(task_type, "_validate_task_def"):
- task_type_issue = task_type._validate_task_def(
- task_name, normalized_task_def, config
- )
- if task_type_issue:
- return task_type_issue
-
- return None
+ return bool(
+ self.spec.options.get("deps", False) or self.spec.options.get("uses", False)
+ )
@classmethod
def is_task_type(
@@ -552,27 +590,13 @@ def get_task_types(cls, content_type: Optional[Type] = None) -> Tuple[str, ...]:
)
return tuple(task_type for task_type in cls.__task_types.keys())
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- """
- To be overriden by subclasses to check the given task definition for validity
- specific to that task type and return a message describing the first encountered
- issue if any.
- """
- issue = None
- return issue
-
def _print_action(self, action: str, dry: bool, unresolved: bool = False):
"""
Print the action taken by a task just before executing it.
"""
min_verbosity = -1 if dry else 0
- arrow = (
- "??" if unresolved else "<=" if self.options.get("capture_stdout") else "=>"
- )
- self._ui.print_msg(
+ arrow = "??" if unresolved else "<=" if self.capture_stdout else "=>"
+ self.ctx.ui.print_msg(
f"Poe {arrow} {action}", min_verbosity
)
diff --git a/poethepoet/task/cmd.py b/poethepoet/task/cmd.py
index 22eb8a58e..fd175623e 100644
--- a/poethepoet/task/cmd.py
+++ b/poethepoet/task/cmd.py
@@ -1,13 +1,14 @@
import shlex
-from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union
+from typing import TYPE_CHECKING
-from ..exceptions import PoeException
+from ..exceptions import ConfigValidationError, PoeException
from .base import PoeTask
if TYPE_CHECKING:
from ..config import PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
+ from .base import TaskSpecFactory
class CmdTask(PoeTask):
@@ -15,12 +16,34 @@ class CmdTask(PoeTask):
A task consisting of a reference to a shell command
"""
- content: str
-
__key__ = "cmd"
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "use_exec": bool,
- }
+
+ class TaskOptions(PoeTask.TaskOptions):
+ use_exec: bool = False
+
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ super().validate()
+ if self.use_exec and self.capture_stdout:
+ raise ConfigValidationError(
+ "'use_exec' and 'capture_stdout'"
+ " options cannot be both provided on the same task."
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: str
+ options: "CmdTask.TaskOptions"
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+ if not self.content.strip():
+ raise ConfigValidationError("Task has no content")
+
+ spec: TaskSpec
def _handle_run(
self,
@@ -35,7 +58,7 @@ def _handle_run(
self._print_action(shlex.join(cmd), context.dry)
return self._get_executor(context, env).execute(
- cmd, use_exec=self.options.get("use_exec", False)
+ cmd, use_exec=self.spec.options.get("use_exec", False)
)
def _resolve_commandline(self, context: "RunContext", env: "EnvVarsManager"):
@@ -43,7 +66,7 @@ def _resolve_commandline(self, context: "RunContext", env: "EnvVarsManager"):
from ..helpers.command.ast_core import ParseError
try:
- command_lines = parse_poe_cmd(self.content).command_lines
+ command_lines = parse_poe_cmd(self.spec.content).command_lines
except ParseError as error:
raise PoeException(
f"Couldn't parse command line for task {self.name!r}: {error.args[0]}"
@@ -71,12 +94,3 @@ def _resolve_commandline(self, context: "RunContext", env: "EnvVarsManager"):
result.append(cmd_token)
return result
-
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- if not task_def["cmd"].strip():
- return f"Task {task_name!r} has no content"
-
- return None
diff --git a/poethepoet/task/expr.py b/poethepoet/task/expr.py
index ae5a5e0f5..d65ed1754 100644
--- a/poethepoet/task/expr.py
+++ b/poethepoet/task/expr.py
@@ -6,18 +6,19 @@
Iterable,
Mapping,
Optional,
+ Sequence,
Tuple,
- Type,
Union,
)
-from ..exceptions import ExpressionParseError
+from ..exceptions import ConfigValidationError, ExpressionParseError
from .base import PoeTask
if TYPE_CHECKING:
from ..config import PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
+ from .base import TaskSpecFactory
class ExprTask(PoeTask):
@@ -28,11 +29,35 @@ class ExprTask(PoeTask):
content: str
__key__ = "expr"
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "imports": list,
- "assert": (bool, int),
- "use_exec": bool,
- }
+
+ class TaskOptions(PoeTask.TaskOptions):
+ imports: Sequence[str] = tuple()
+ assert_: Union[bool, int] = False
+ use_exec: bool = False
+
+ def validate(self):
+ super().validate()
+ if self.use_exec and self.capture_stdout:
+ raise ConfigValidationError(
+ "'use_exec' and 'capture_stdout'"
+ " options cannot be both provided on the same task."
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: str
+ options: "ExprTask.TaskOptions"
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+ try:
+ # ruff: noqa: E501
+ self.task_type._substitute_env_vars(self.content.strip(), {}) # type: ignore[attr-defined]
+ except (ValueError, ExpressionParseError) as error:
+ raise ConfigValidationError(f"Invalid expression: {error}")
+
+ spec: TaskSpec
def _handle_run(
self,
@@ -44,13 +69,11 @@ def _handle_run(
named_arg_values, extra_args = self.get_parsed_arguments(env)
env.update(named_arg_values)
- # TODO: do something about extra_args, error?
-
- imports = self.options.get("imports", tuple())
+ imports = self.spec.options.imports
expr, env_values = self.parse_content(named_arg_values, env, imports)
argv = [
- self.name,
+ self.spec.name,
*(env.fill_template(token) for token in self.invocation[1:]),
]
@@ -63,7 +86,7 @@ def _handle_run(
"print(result);",
]
- falsy_return_code = int(self.options.get("assert", False))
+ falsy_return_code = int(self.spec.options.get("assert"))
if falsy_return_code:
script.append(f"exit(0 if result else {falsy_return_code});")
@@ -72,22 +95,11 @@ def _handle_run(
# windows
cmd = ("python", "-c", "".join(script))
- self._print_action(self.content.strip(), context.dry)
+ self._print_action(self.spec.content.strip(), context.dry)
return self._get_executor(context, env).execute(
- cmd, use_exec=self.options.get("use_exec", False)
+ cmd, use_exec=self.spec.options.use_exec
)
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- try:
- cls._substitute_env_vars(task_def["expr"].strip(), {})
- except (ValueError, ExpressionParseError) as error:
- return f"Task {task_name!r} contains invalid expression: {error}"
-
- return None
-
def parse_content(
self,
args: Optional[Dict[str, Any]],
@@ -106,7 +118,7 @@ def parse_content(
from ..helpers.python import resolve_expression
expression, accessed_vars = self._substitute_env_vars(
- self.content.strip(), env.to_dict()
+ self.spec.content.strip(), env.to_dict()
)
expression = resolve_expression(
@@ -124,7 +136,7 @@ def parse_content(
@classmethod
def _substitute_env_vars(cls, content: str, env: Mapping[str, str]):
"""
- Substitute ${template} references to env vars with a refernece to a python class
+ Substitute ${template} references to env vars with a reference to a python class
attribute like __env.var, and collect the accessed env vars so we can construct
that class with the required attributes later.
"""
diff --git a/poethepoet/task/ref.py b/poethepoet/task/ref.py
index d637cff89..b8e11a639 100644
--- a/poethepoet/task/ref.py
+++ b/poethepoet/task/ref.py
@@ -1,11 +1,13 @@
-from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union
+from typing import TYPE_CHECKING
-from .base import PoeTask, TaskInheritance
+from ..exceptions import ConfigValidationError
+from .base import PoeTask, TaskContext
if TYPE_CHECKING:
from ..config import PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
+ from .base import TaskSpecFactory
class RefTask(PoeTask):
@@ -13,10 +15,47 @@ class RefTask(PoeTask):
A task consisting of a reference to another task
"""
- content: str
-
__key__ = "ref"
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {}
+
+ class TaskOptions(PoeTask.TaskOptions):
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ if self.executor:
+ raise ConfigValidationError(
+ "Option 'executor' cannot be set on a ref task"
+ )
+ if self.capture_stdout:
+ raise ConfigValidationError(
+ "Option 'capture_stdout' cannot be set on a ref task"
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: str
+ options: "RefTask.TaskOptions"
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+
+ import shlex
+
+ task_name_ref = shlex.split(self.content)[0]
+
+ if task_name_ref not in task_specs:
+ raise ConfigValidationError(
+ f"Includes reference to unknown task {task_name_ref!r}"
+ )
+
+ if task_specs.get(task_name_ref).options.get("use_exec", False):
+ raise ConfigValidationError(
+ f"Illegal reference to task with "
+ f"'use_exec' set to true: {task_name_ref!r}"
+ )
+
+ spec: TaskSpec
def _handle_run(
self,
@@ -34,17 +73,13 @@ def _handle_run(
ref_invocation = (
*(
env.fill_template(token)
- for token in shlex.split(env.fill_template(self.content.strip()))
+ for token in shlex.split(env.fill_template(self.spec.content.strip()))
),
*extra_args,
)
- task = self.from_config(
- ref_invocation[0],
- self._config,
- self._ui,
- invocation=ref_invocation,
- inheritance=TaskInheritance.from_task(self),
+ task = self.ctx.specs.get(ref_invocation[0]).create_task(
+ invocation=ref_invocation, ctx=TaskContext.from_task(self)
)
if task.has_deps():
@@ -75,34 +110,3 @@ def _run_task_graph(
f"Task graph aborted after failed task {stage_task.name!r}"
)
return 0
-
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- """
- Check the given task definition for validity specific to this task type and
- return a message describing the first encountered issue if any.
- """
-
- # TODO: disallow capture_stdout and executor options?
-
- import shlex
-
- task_ref = task_def["ref"]
- task_name_ref = shlex.split(task_ref)[0]
-
- if task_name_ref not in config.tasks:
- return (
- f"Task {task_name!r} contains reference to unknown task "
- f"{task_name_ref!r}"
- )
-
- referenced_task = config.tasks[task_name_ref]
- if isinstance(referenced_task, dict) and referenced_task.get("use_exec"):
- return (
- f"Invalid task: {task_name!r}. contains illegal reference to task with "
- f"use_exec set to true: {task_ref!r}"
- )
-
- return None
diff --git a/poethepoet/task/script.py b/poethepoet/task/script.py
index 5ff6dc9ed..8ed37445c 100644
--- a/poethepoet/task/script.py
+++ b/poethepoet/task/script.py
@@ -1,14 +1,15 @@
import re
import shlex
-from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union
+from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
-from ..exceptions import ExpressionParseError
+from ..exceptions import ConfigValidationError, ExpressionParseError
from .base import PoeTask
if TYPE_CHECKING:
from ..config import PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
+ from .base import TaskSpecFactory
class ScriptTask(PoeTask):
@@ -19,10 +20,40 @@ class ScriptTask(PoeTask):
content: str
__key__ = "script"
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "use_exec": bool,
- "print_result": bool,
- }
+
+ class TaskOptions(PoeTask.TaskOptions):
+ use_exec: bool = False
+ print_result: bool = False
+
+ def validate(self):
+ super().validate()
+ if self.use_exec and self.capture_stdout:
+ raise ConfigValidationError(
+ "'use_exec' and 'capture_stdout'"
+ " options cannot be both provided on the same task."
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: str
+ options: "ScriptTask.TaskOptions"
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+ from ..helpers.python import parse_and_validate
+
+ try:
+ target_module, target_ref = self.content.split(":", 1)
+ if not target_ref.isidentifier():
+ parse_and_validate(target_ref, call_only=True)
+ except (ValueError, ExpressionParseError):
+ raise ConfigValidationError(
+ f"Invalid callable reference {self.content!r}\n"
+ "(expected something like `module:callable` or `module:callable()`)"
+ )
+
+ spec: TaskSpec
def _handle_run(
self,
@@ -59,7 +90,7 @@ def _handle_run(
f" else _m.{function_call};",
]
- if self.options.get("print_result"):
+ if self.spec.options.get("print_result"):
script.append("_r is not None and print(_r);")
# Exactly which python executable to use is usually resolved by the executor
@@ -69,28 +100,9 @@ def _handle_run(
self._print_action(shlex.join(argv), context.dry)
return self._get_executor(context, env).execute(
- cmd, use_exec=self.options.get("use_exec", False)
+ cmd, use_exec=self.spec.options.get("use_exec", False)
)
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- from ..helpers.python import parse_and_validate
-
- try:
- target_module, target_ref = task_def["script"].split(":", 1)
- if not target_ref.isidentifier():
- parse_and_validate(target_ref, call_only=True)
- except (ValueError, ExpressionParseError):
- return (
- f"Task {task_name!r} contains invalid callable reference "
- f"{task_def['script']!r} (expected something like `module:callable`"
- " or `module:callable()`)"
- )
-
- return None
-
def parse_content(self, args: Optional[Dict[str, Any]]) -> Tuple[str, str]:
"""
Returns the module to load, and the function call to execute.
@@ -102,10 +114,10 @@ def parse_content(self, args: Optional[Dict[str, Any]]) -> Tuple[str, str]:
from ..helpers.python import resolve_expression
try:
- target_module, target_ref = self.content.strip().split(":", 1)
+ target_module, target_ref = self.spec.content.strip().split(":", 1)
except ValueError:
raise ExpressionParseError(
- f"Invalid task content: {self.content.strip()!r}"
+ f"Invalid task content: {self.spec.content.strip()!r}"
)
if target_ref.isidentifier():
diff --git a/poethepoet/task/sequence.py b/poethepoet/task/sequence.py
index 805ed1a5e..dd3b0d879 100644
--- a/poethepoet/task/sequence.py
+++ b/poethepoet/task/sequence.py
@@ -1,22 +1,25 @@
from typing import (
TYPE_CHECKING,
Any,
+ ClassVar,
Dict,
List,
+ Literal,
Optional,
+ Sequence,
Tuple,
Type,
Union,
)
-from ..exceptions import ExecutionError, PoeException
-from .base import PoeTask, TaskContent, TaskInheritance
+from ..exceptions import ConfigValidationError, ExecutionError, PoeException
+from .base import PoeTask, TaskContext
if TYPE_CHECKING:
- from ..config import PoeConfig
+ from ..config import ConfigPartition, PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
- from ..ui import PoeUi
+ from .base import TaskSpecFactory
class SequenceTask(PoeTask):
@@ -27,42 +30,101 @@ class SequenceTask(PoeTask):
content: List[Union[str, Dict[str, Any]]]
__key__ = "sequence"
- __content_type__: Type = list
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "ignore_fail": (bool, str),
- "default_item_type": str,
- }
+ __content_type__: ClassVar[Type] = list
+
+ class TaskOptions(PoeTask.TaskOptions):
+ ignore_fail: Literal[True, False, "return_zero", "return_non_zero"] = False
+ default_item_type: Optional[str] = None
+
+ def validate(self):
+ """
+ Validation rules that don't require any extra context go here.
+ """
+ super().validate()
+ if self.default_item_type is not None and not PoeTask.is_task_type(
+ self.default_item_type, content_type=str
+ ):
+ raise ConfigValidationError(
+ "Unsupported value for option `default_item_type`,\n"
+ f"Expected one of {PoeTask.get_task_types(content_type=str)}"
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: list
+ options: "SequenceTask.TaskOptions"
+ subtasks: Sequence[PoeTask.TaskSpec]
+
+ def __init__(
+ self,
+ name: str,
+ task_def: Dict[str, Any],
+ factory: "TaskSpecFactory",
+ source: "ConfigPartition",
+ parent: Optional["PoeTask.TaskSpec"] = None,
+ ):
+ super().__init__(name, task_def, factory, source, parent)
+
+ self.subtasks = []
+ for index, sub_task_def in enumerate(task_def[SequenceTask.__key__]):
+ if not isinstance(sub_task_def, (str, dict, list)):
+ raise ConfigValidationError(
+ f"Item #{index} in sequence task should be a value of "
+ "type: str | dict | list",
+ task_name=self.name,
+ )
+
+ subtask_name = (
+ sub_task_def
+ if isinstance(sub_task_def, str)
+ else SequenceTask._subtask_name(name, index)
+ )
+ task_type_key = self.task_type.resolve_task_type(
+ sub_task_def,
+ factory.config,
+ array_item=task_def.get("default_item_type", True),
+ )
+
+ try:
+ self.subtasks.append(
+ factory.get(
+ subtask_name, sub_task_def, task_type_key, parent=self
+ )
+ )
+ except PoeException:
+ raise ConfigValidationError(
+ f"Failed to interpret subtask #{index} in sequence",
+ task_name=self.name,
+ )
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ """
+ Perform validations on this TaskSpec that apply to a specific task type
+ """
+ for index, subtask in enumerate(self.subtasks):
+ if subtask.args:
+ raise ConfigValidationError(
+ "Unsupported option 'args' for task declared inside sequence"
+ )
+
+ subtask.validate(config, task_specs)
+
+ spec: TaskSpec
def __init__(
self,
- name: str,
- content: TaskContent,
- options: Dict[str, Any],
- ui: "PoeUi",
- config: "PoeConfig",
+ spec: TaskSpec,
invocation: Tuple[str, ...],
+ ctx: TaskContext,
capture_stdout: bool = False,
- inheritance: Optional[TaskInheritance] = None,
):
- assert capture_stdout is False
- super().__init__(
- name, content, options, ui, config, invocation, False, inheritance
- )
-
+ assert capture_stdout in (False, None)
+ super().__init__(spec, invocation, ctx)
self.subtasks = [
- self.from_def(
- task_def=item,
- task_name=task_name,
- config=config,
- invocation=(task_name,),
- ui=ui,
- array_item=self.options.get("default_item_type", True),
- inheritance=TaskInheritance.from_task(self),
- )
- for index, item in enumerate(self.content)
- for task_name in (
- item if isinstance(item, str) else self._subtask_name(name, index),
+ task_spec.create_task(
+ invocation=(self._subtask_name(task_spec.name, index),),
+ ctx=TaskContext.from_task(self),
)
+ for index, task_spec in enumerate(spec.subtasks)
]
def _handle_run(
@@ -80,7 +142,7 @@ def _handle_run(
# Indicate on the global context that there are multiple stages
context.multistage = True
- ignore_fail = self.options.get("ignore_fail")
+ ignore_fail = self.spec.options.ignore_fail
non_zero_subtasks: List[str] = list()
for subtask in self.subtasks:
task_result = subtask.run(context=context, parent_env=env)
@@ -100,61 +162,3 @@ def _handle_run(
@classmethod
def _subtask_name(cls, task_name: str, index: int):
return f"{task_name}[{index}]"
-
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- default_item_type = task_def.get("default_item_type")
- if default_item_type is not None and not cls.is_task_type(
- default_item_type, content_type=str
- ):
- return (
- "Unsupported value for option `default_item_type` for task "
- f"{task_name!r}. Expected one of {cls.get_task_types(content_type=str)}"
- )
-
- ignore_fail = task_def.get("ignore_fail")
- if ignore_fail is not None and ignore_fail not in (
- True,
- False,
- "return_zero",
- "return_non_zero",
- ):
- return (
- f"Unsupported value for option `ignore_fail` for task {task_name!r}."
- ' Expected one of (true, false, "return_zero", "return_non_zero")'
- )
-
- for index, task_item in enumerate(task_def["sequence"]):
- if isinstance(task_item, dict):
- if len(task_item.get("args", tuple())):
- return (
- "Unsupported option `args` for task declared inside sequence "
- f"task {task_name!r}."
- )
-
- subtask_issue = cls.validate_def(
- cls._subtask_name(task_name, index),
- task_item,
- config,
- anonymous=True,
- )
- if subtask_issue:
- return subtask_issue
-
- else:
- subtask_issue = cls.validate_def(
- cls._subtask_name(task_name, index),
- cls.normalize_task_def(
- task_item,
- config,
- array_item=default_item_type or True,
- ),
- config,
- anonymous=True,
- )
- if subtask_issue:
- return subtask_issue
-
- return None
diff --git a/poethepoet/task/shell.py b/poethepoet/task/shell.py
index c93bf63e2..5ba3f3f92 100644
--- a/poethepoet/task/shell.py
+++ b/poethepoet/task/shell.py
@@ -2,20 +2,16 @@
from os import environ
from typing import (
TYPE_CHECKING,
- Any,
- Dict,
List,
Optional,
Tuple,
- Type,
Union,
)
-from ..exceptions import PoeException
+from ..exceptions import ConfigValidationError, PoeException
from .base import PoeTask
if TYPE_CHECKING:
- from ..config import PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
@@ -28,7 +24,44 @@ class ShellTask(PoeTask):
content: str
__key__ = "shell"
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {"interpreter": (str, list)}
+
+ class TaskOptions(PoeTask.TaskOptions):
+ interpreter: Optional[Union[str, list]] = None
+
+ def validate(self):
+ super().validate()
+
+ from ..config import PoeConfig
+
+ valid_interpreters = PoeConfig.KNOWN_SHELL_INTERPRETERS
+
+ if (
+ isinstance(self.interpreter, str)
+ and self.interpreter not in valid_interpreters
+ ):
+ raise ConfigValidationError(
+ "Invalid value for option 'interpreter',\n"
+ f"Expected one of {valid_interpreters}"
+ )
+
+ if isinstance(self.interpreter, list):
+ if len(self.interpreter) == 0:
+ raise ConfigValidationError(
+ "Invalid value for option 'interpreter',\n"
+ "Expected at least one item in list."
+ )
+ for item in self.interpreter:
+ if item not in valid_interpreters:
+ raise ConfigValidationError(
+ f"Invalid item {item!r} in option 'interpreter',\n"
+ f"Expected one of {valid_interpreters!r}"
+ )
+
+ class TaskSpec(PoeTask.TaskSpec):
+ content: str
+ options: "ShellTask.TaskOptions"
+
+ spec: TaskSpec
def _handle_run(
self,
@@ -39,7 +72,9 @@ def _handle_run(
env.update(named_arg_values)
if not named_arg_values and any(arg.strip() for arg in self.invocation[1:]):
- raise PoeException(f"Shell task {self.name!r} does not accept arguments")
+ raise PoeException(
+ f"Shell task {self.spec.name!r} does not accept arguments"
+ )
interpreter_cmd = self.resolve_interpreter_cmd()
if not interpreter_cmd:
@@ -54,7 +89,7 @@ def _handle_run(
message += "Some dependencies may be missing from your system."
raise PoeException(message)
- content = _unindent_code(self.content).rstrip()
+ content = _unindent_code(self.spec.content).rstrip()
self._print_action(content, context.dry)
@@ -63,8 +98,8 @@ def _handle_run(
)
def _get_interpreter_config(self) -> Tuple[str, ...]:
- result: Union[str, Tuple[str, ...]] = self.options.get(
- "interpreter", self._config.shell_interpreter
+ result: Union[str, Tuple[str, ...]] = self.spec.options.get(
+ "interpreter", self.ctx.config.shell_interpreter
)
if isinstance(result, str):
return (result,)
@@ -152,34 +187,6 @@ def _locate_interpreter(self, interpreter: str) -> Optional[str]:
return result
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- interpreter = task_def.get("interpreter")
- valid_interpreters = config.KNOWN_SHELL_INTERPRETERS
-
- if isinstance(interpreter, str) and interpreter not in valid_interpreters:
- return (
- "Unsupported value for option `interpreter` for task "
- f"{task_name!r}. Expected one of {valid_interpreters}"
- )
-
- if isinstance(interpreter, list):
- if len(interpreter) == 0:
- return (
- "Unsupported value for option `interpreter` for task "
- f"{task_name!r}. Expected at least one item in list."
- )
- for item in interpreter:
- if item not in valid_interpreters:
- return (
- "Unsupported item {item!r} in option `interpreter` for task "
- f"{task_name!r}. Expected one of {valid_interpreters}"
- )
-
- return None
-
def _unindent_code(python_code: str):
"""
diff --git a/poethepoet/task/switch.py b/poethepoet/task/switch.py
index 977508dcd..e553c2110 100644
--- a/poethepoet/task/switch.py
+++ b/poethepoet/task/switch.py
@@ -1,27 +1,28 @@
from typing import (
TYPE_CHECKING,
Any,
+ ClassVar,
Dict,
- List,
+ Literal,
MutableMapping,
Optional,
Tuple,
Type,
Union,
- cast,
)
-from ..exceptions import ExecutionError, PoeException
-from .base import PoeTask, TaskContent, TaskInheritance
+from ..exceptions import ConfigValidationError, ExecutionError, PoeException
+from .base import PoeTask, TaskContext
if TYPE_CHECKING:
- from ..config import PoeConfig
+ from ..config import ConfigPartition, PoeConfig
from ..context import RunContext
from ..env.manager import EnvVarsManager
- from ..ui import PoeUi
+ from .base import TaskSpecFactory
DEFAULT_CASE = "__default__"
+SUBTASK_OPTIONS_BLOCKLIST = ("args", "uses", "deps")
class SwitchTask(PoeTask):
@@ -30,65 +31,165 @@ class SwitchTask(PoeTask):
`switch` subtask.
"""
- content: List[Union[str, Dict[str, Any]]]
-
__key__ = "switch"
- __content_type__: Type = list
- __options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {
- "control": (str, dict),
- "default": str,
- }
+ __content_type__: ClassVar[Type] = list
+
+ class TaskOptions(PoeTask.TaskOptions):
+ control: Union[str, dict]
+ default: Literal["pass", "fail"] = "fail"
+
+ @classmethod
+ def normalize(
+ cls,
+ config: Any,
+ strict: bool = True,
+ ):
+ """
+ Perform validations that require access to to the raw config.
+ """
+ if strict and isinstance(config, dict):
+ # Subtasks may not declare certain options
+ for subtask_def in config.get("switch", tuple()):
+ for banned_option in SUBTASK_OPTIONS_BLOCKLIST:
+ if banned_option in subtask_def:
+ if "case" not in subtask_def:
+ raise ConfigValidationError(
+ "Default case includes incompatible option "
+ f"{banned_option!r}"
+ )
+ raise ConfigValidationError(
+ f"Case {subtask_def.get('case')!r} includes "
+ f"incompatible option {banned_option!r}"
+ )
+
+ return super().normalize(config, strict)
+
+ class TaskSpec(PoeTask.TaskSpec):
+ control_task_spec: PoeTask.TaskSpec
+ case_task_specs: Tuple[Tuple[Tuple[Any, ...], PoeTask.TaskSpec], ...]
+ options: "SwitchTask.TaskOptions"
+
+ def __init__(
+ self,
+ name: str,
+ task_def: Dict[str, Any],
+ factory: "TaskSpecFactory",
+ source: "ConfigPartition",
+ parent: Optional["PoeTask.TaskSpec"] = None,
+ ):
+ super().__init__(name, task_def, factory, source, parent)
+
+ switch_args = task_def.get("args")
+ control_task_def = task_def["control"]
+
+ if switch_args:
+ if isinstance(control_task_def, str):
+ control_task_def = {
+ factory.config.default_task_type: control_task_def
+ }
+ control_task_def = dict(control_task_def, args=switch_args)
+
+ self.control_task_spec = factory.get(
+ task_name=f"{name}[__control__]", task_def=control_task_def, parent=self
+ )
+
+ case_task_specs = []
+ for switch_item in task_def["switch"]:
+ case_task_def = dict(switch_item, args=switch_args)
+ case = case_task_def.pop("case", DEFAULT_CASE)
+ case_tuple = tuple(case) if isinstance(case, list) else (case,)
+ case_task_index = ",".join(str(value) for value in case_tuple)
+ case_task_specs.append(
+ (
+ case_tuple,
+ factory.get(
+ task_name=f"{name}[{case_task_index}]",
+ task_def=case_task_def,
+ parent=self,
+ ),
+ )
+ )
+
+ self.case_task_specs = tuple(case_task_specs)
+
+ def _task_validations(self, config: "PoeConfig", task_specs: "TaskSpecFactory"):
+ from collections import defaultdict
+
+ allowed_control_task_types = ("expr", "cmd", "script")
+ if (
+ self.control_task_spec.task_type.__key__
+ not in allowed_control_task_types
+ ):
+ raise ConfigValidationError(
+ f"Control task must have a type that is one of "
+ f"{allowed_control_task_types!r}"
+ )
+
+ cases: MutableMapping[Any, int] = defaultdict(int)
+ for case_keys, case_task_spec in self.case_task_specs:
+ for case_key in case_keys:
+ cases[case_key] += 1
+
+ # Ensure case keys don't overlap (and only one default case)
+ for case, count in cases.items():
+ if count > 1:
+ if case is DEFAULT_CASE:
+ raise ConfigValidationError(
+ "Switch array includes more than one default case"
+ )
+ raise ConfigValidationError(
+ f"Switch array includes more than one case for {case!r}"
+ )
+
+ if self.options.default != "fail" and DEFAULT_CASE in cases:
+ raise ConfigValidationError(
+ "switch tasks should not declare both a default case and the "
+ "'default' option"
+ )
+
+ # Validate subtask specs
+ self.control_task_spec.validate(config, task_specs)
+ for _, case_task_spec in self.case_task_specs:
+ case_task_spec.validate(config, task_specs)
+
+ spec: TaskSpec
+ control_task: PoeTask
+ switch_tasks: Dict[str, PoeTask]
def __init__(
self,
- name: str,
- content: TaskContent,
- options: Dict[str, Any],
- ui: "PoeUi",
- config: "PoeConfig",
+ spec: TaskSpec,
invocation: Tuple[str, ...],
+ ctx: TaskContext,
capture_stdout: bool = False,
- inheritance: Optional[TaskInheritance] = None,
):
- super().__init__(
- name, content, options, ui, config, invocation, False, inheritance
- )
+ super().__init__(spec, invocation, ctx, capture_stdout)
- control_task_name = f"{name}[control]"
+ control_task_name = f"{spec.name}[__control__]"
control_invocation: Tuple[str, ...] = (control_task_name,)
- if self.options.get("args"):
- self.options["control"]["args"] = self.options["args"]
+ options = self.spec.options
+ if options.get("args"):
control_invocation = (*control_invocation, *invocation[1:])
- self.control_task = self.from_def(
- task_def=self.options.get("control", ""),
- task_name=control_task_name,
- config=config,
+ self.control_task = self.spec.control_task_spec.create_task(
invocation=control_invocation,
- ui=ui,
+ ctx=TaskContext.from_task(self),
capture_stdout=True,
- inheritance=TaskInheritance.from_task(self),
)
self.switch_tasks = {}
- for item in cast(List[Dict[str, Any]], content):
- task_def = {key: value for key, value in item.items() if key != "case"}
-
- task_invocation: Tuple[str, ...] = (name,)
- if self.options.get("args"):
- task_def["args"] = self.options["args"]
+ for case_keys, case_spec in spec.case_task_specs:
+ task_invocation: Tuple[str, ...] = (f"{spec.name}[{','.join(case_keys)}]",)
+ if options.get("args"):
task_invocation = (*task_invocation, *invocation[1:])
- for case_key in self._get_case_keys(item):
- self.switch_tasks[case_key] = self.from_def(
- task_def=task_def,
- task_name=f"{name}__{case_key}",
- config=config,
- invocation=task_invocation,
- ui=ui,
- capture_stdout=self.options.get("capture_stdout", capture_stdout),
- inheritance=TaskInheritance.from_task(self),
- )
+ case_task = case_spec.create_task(
+ invocation=task_invocation,
+ ctx=TaskContext.from_task(self),
+ capture_stdout=self.capture_stdout,
+ )
+ for case_key in case_keys:
+ self.switch_tasks[case_key] = case_task
def _handle_run(
self,
@@ -122,96 +223,20 @@ def _handle_run(
)
if case_task is None:
- if self.options.get("default", "fail") == "pass":
+ if self.spec.options.default == "pass":
return 0
raise ExecutionError(
f"Control value {control_task_output!r} did not match any cases in "
f"switch task {self.name!r}."
)
- return case_task.run(context=context, parent_env=env)
+ result = case_task.run(context=context, parent_env=env)
- @classmethod
- def _get_case_keys(cls, task_def: Dict[str, Any]) -> List[Any]:
- case_value = task_def.get("case", DEFAULT_CASE)
- if isinstance(case_value, list):
- return case_value
- return [case_value]
-
- @classmethod
- def _validate_task_def(
- cls, task_name: str, task_def: Dict[str, Any], config: "PoeConfig"
- ) -> Optional[str]:
- from collections import defaultdict
-
- control_task_def = task_def.get("control")
- if not control_task_def:
- return f"Switch task {task_name!r} has no control task."
-
- allowed_control_task_types = ("expr", "cmd", "script")
- if isinstance(control_task_def, dict) and not any(
- key in control_task_def for key in allowed_control_task_types
- ):
- return (
- f"Control task for {task_name!r} must have a type that is one of "
- f"{allowed_control_task_types!r}"
+ if self.capture_stdout is True:
+ # The executor saved output for the case task, but we need it to be
+ # registered for this switch task as well
+ context.save_task_output(
+ self.invocation, context.get_task_output(case_task.invocation).encode()
)
- control_task_issue = PoeTask.validate_def(
- f"{task_name}[control]", control_task_def, config, anonymous=True
- )
- if control_task_issue:
- return control_task_issue
-
- cases: MutableMapping[Any, int] = defaultdict(int)
- for switch_task in task_def["switch"]:
- for case_key in cls._get_case_keys(switch_task):
- cases[case_key] += 1
-
- case_key = switch_task.get("case", DEFAULT_CASE)
- for invalid_option in ("args", "deps"):
- if invalid_option in switch_task:
- if case_key is DEFAULT_CASE:
- return (
- f"Default case of switch task {task_name!r} includes "
- f"invalid option {invalid_option!r}"
- )
- return (
- f"Case {case_key!r} switch task {task_name!r} include invalid "
- f"option {invalid_option!r}"
- )
-
- switch_task_issue = PoeTask.validate_def(
- f"{task_name}[{case_key}]",
- switch_task,
- config,
- anonymous=True,
- extra_options=("case",),
- )
- if switch_task_issue:
- return switch_task_issue
-
- for case, count in cases.items():
- if count > 1:
- if case is DEFAULT_CASE:
- return (
- f"Switch task {task_name!r} includes more than one default case"
- )
- return (
- f"Switch task {task_name!r} includes more than one case for "
- f"{case!r}"
- )
-
- if "default" in task_def:
- if task_def["default"] not in ("pass", "fail"):
- return (
- f"The 'default' option for switch task {task_name!r} should be one "
- "of ('pass', 'fail')"
- )
- if DEFAULT_CASE in cases:
- return (
- f"Switch task {task_name!r} should not have both a default case "
- f"and the 'default' option."
- )
-
- return None
+ return result
diff --git a/poethepoet/ui.py b/poethepoet/ui.py
index 8c38333de..e22b109ec 100644
--- a/poethepoet/ui.py
+++ b/poethepoet/ui.py
@@ -3,7 +3,7 @@
from typing import IO, TYPE_CHECKING, List, Mapping, Optional, Sequence, Tuple, Union
from .__version__ import __version__
-from .exceptions import ExecutionError, PoeException
+from .exceptions import ConfigValidationError, ExecutionError, PoeException
if TYPE_CHECKING:
from argparse import ArgumentParser, Namespace
@@ -16,6 +16,12 @@ def guess_ansi_support(file):
# https://no-color.org/
return False
+ if (
+ os.environ.get("GITHUB_ACTIONS", "false") == "true"
+ and "PYTEST_CURRENT_TEST" not in os.environ
+ ):
+ return True
+
return (
(sys.platform != "win32" or "ANSICON" in os.environ)
and hasattr(file, "isatty")
@@ -172,10 +178,28 @@ def print_help(
if error:
# TODO: send this to stderr instead?
- error_line = [f"Error: {error.msg} "]
+ error_lines = []
+ if isinstance(error, ConfigValidationError):
+ if error.task_name:
+ if error.context:
+ error_lines.append(
+ f"{error.context} in task {error.task_name!r}"
+ )
+ else:
+ error_lines.append(f"Invalid task {error.task_name!r}")
+ if error.filename:
+ error_lines[-1] += f" in file {error.filename}"
+ elif error.global_option:
+ error_lines.append(f"Invalid global option {error.global_option!r}")
+ if error.filename:
+ error_lines[-1] += f" in file {error.filename}"
+ error_lines.extend(error.msg.split("\n"))
if error.cause:
- error_line.append(f" From: {error.cause} ")
- result.append(error_line)
+ error_lines.append(error.cause)
+ if error.__cause__:
+ error_lines.append(f"From: {error.__cause__!r}")
+
+ result.append(self._format_error_lines(error_lines))
if verbosity >= 0:
result.append(
@@ -201,7 +225,13 @@ def print_help(
max_task_len = max(
max(
len(task),
- max([len(", ".join(opts)) for (opts, _, _) in args] or (0,))
+ max(
+ [
+ len(", ".join(str(opt) for opt in opts))
+ for (opts, _, _) in args
+ ]
+ or (0,)
+ )
+ 2,
)
for task, (_, args) in tasks.items()
@@ -216,9 +246,10 @@ def print_help(
f" {self._padr(task, col_width)} {help_text}"
)
for options, arg_help_text, default in args_help:
+ formatted_options = ", ".join(str(opt) for opt in options)
task_arg_help = [
" ",
- f"{self._padr(', '.join(options), col_width-1)}",
+ f"{self._padr(formatted_options, col_width-1)}",
]
if arg_help_text:
task_arg_help.append(arg_help_text)
@@ -231,6 +262,11 @@ def print_help(
else:
result.append("NO TASKS CONFIGURED")
+ if error and os.environ.get("POE_DEBUG", "0").lower() == "1":
+ import traceback
+
+ result.append("".join(traceback.format_exception(error)).strip())
+
self._print(
"\n\n".join(
section if isinstance(section, str) else "\n".join(section).strip("\n")
@@ -251,9 +287,25 @@ def print_msg(self, message: str, verbosity=0, end="\n"):
self._print(message, end=end)
def print_error(self, error: Union[PoeException, ExecutionError]):
- self._print(f"Error: {error.msg} ")
+ error_lines = error.msg.split("\n")
if error.cause:
- self._print(f" From: {error.cause} ")
+ error_lines.append(f"From: {error.cause}")
+ if error.__cause__:
+ error_lines.append(f"From: {error.__cause__!r}")
+
+ for line in self._format_error_lines(error_lines):
+ self._print(line)
+
+ if os.environ.get("POE_DEBUG", "0").lower() == "1":
+ import traceback
+
+ self._print("".join(traceback.format_exception(error)).strip())
+
+ def _format_error_lines(self, lines: Sequence[str]):
+ return (
+ f"Error: {lines[0]}",
+ *(f" | {line}" for line in lines[1:]),
+ )
def print_version(self):
if self.verbosity >= 0:
diff --git a/poetry.lock b/poetry.lock
index 8bb46ae7c..8bf606d9d 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -36,23 +36,6 @@ files = [
{file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"},
]
-[[package]]
-name = "astroid"
-version = "2.11.7"
-description = "An abstract syntax tree for Python with inference support."
-optional = false
-python-versions = ">=3.6.2"
-files = [
- {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"},
- {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"},
-]
-
-[package.dependencies]
-lazy-object-proxy = ">=1.4.0"
-setuptools = ">=20.0"
-typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""}
-wrapt = ">=1.11,<2"
-
[[package]]
name = "attrs"
version = "23.1.0"
@@ -73,18 +56,17 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
[[package]]
name = "babel"
-version = "2.13.1"
+version = "2.14.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.7"
files = [
- {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"},
- {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"},
+ {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"},
+ {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"},
]
[package.dependencies]
pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""}
-setuptools = {version = "*", markers = "python_version >= \"3.12\""}
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
@@ -109,29 +91,33 @@ lxml = ["lxml"]
[[package]]
name = "black"
-version = "23.11.0"
+version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"},
- {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"},
- {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"},
- {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"},
- {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"},
- {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"},
- {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"},
- {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"},
- {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"},
- {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"},
- {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"},
- {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"},
- {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"},
- {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"},
- {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"},
- {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"},
- {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"},
- {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"},
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
@@ -145,7 +131,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
@@ -456,63 +442,63 @@ files = [
[[package]]
name = "coverage"
-version = "7.3.2"
+version = "7.4.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"},
- {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"},
- {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"},
- {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"},
- {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"},
- {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"},
- {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"},
- {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"},
- {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"},
- {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"},
- {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"},
- {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"},
- {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"},
- {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"},
- {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"},
- {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"},
- {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"},
- {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"},
- {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"},
- {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"},
- {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"},
- {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"},
+ {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"},
+ {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"},
+ {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"},
+ {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"},
+ {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"},
+ {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"},
+ {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"},
+ {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"},
+ {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"},
+ {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"},
+ {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"},
+ {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"},
]
[package.dependencies]
@@ -523,34 +509,34 @@ toml = ["tomli"]
[[package]]
name = "cryptography"
-version = "41.0.5"
+version = "41.0.7"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = true
python-versions = ">=3.7"
files = [
- {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"},
- {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"},
- {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"},
- {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"},
- {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"},
- {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"},
- {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"},
- {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"},
- {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"},
- {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"},
- {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"},
- {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"},
- {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"},
- {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"},
- {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"},
- {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"},
- {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"},
- {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"},
- {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"},
- {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"},
- {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"},
- {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"},
- {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"},
+ {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"},
+ {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"},
+ {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"},
+ {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"},
+ {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"},
+ {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"},
+ {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"},
+ {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"},
+ {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"},
+ {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"},
+ {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"},
]
[package.dependencies]
@@ -626,29 +612,15 @@ files = [
{file = "cwcwidth-0.1.9.tar.gz", hash = "sha256:f19d11a0148d4a8cacd064c96e93bca8ce3415a186ae8204038f45e108db76b8"},
]
-[[package]]
-name = "dill"
-version = "0.3.7"
-description = "serialize all of Python"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"},
- {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"},
-]
-
-[package.extras]
-graph = ["objgraph (>=1.7.2)"]
-
[[package]]
name = "distlib"
-version = "0.3.7"
+version = "0.3.8"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
- {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"},
- {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"},
+ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
+ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
]
[[package]]
@@ -664,13 +636,13 @@ files = [
[[package]]
name = "exceptiongroup"
-version = "1.1.3"
+version = "1.2.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
- {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
- {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
+ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
+ {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
]
[package.extras]
@@ -711,72 +683,73 @@ sphinx-basic-ng = "*"
[[package]]
name = "greenlet"
-version = "3.0.1"
+version = "3.0.3"
description = "Lightweight in-process concurrent programming"
optional = false
python-versions = ">=3.7"
files = [
- {file = "greenlet-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f89e21afe925fcfa655965ca8ea10f24773a1791400989ff32f467badfe4a064"},
- {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28e89e232c7593d33cac35425b58950789962011cc274aa43ef8865f2e11f46d"},
- {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8ba29306c5de7717b5761b9ea74f9c72b9e2b834e24aa984da99cbfc70157fd"},
- {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19bbdf1cce0346ef7341705d71e2ecf6f41a35c311137f29b8a2dc2341374565"},
- {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599daf06ea59bfedbec564b1692b0166a0045f32b6f0933b0dd4df59a854caf2"},
- {file = "greenlet-3.0.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b641161c302efbb860ae6b081f406839a8b7d5573f20a455539823802c655f63"},
- {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d57e20ba591727da0c230ab2c3f200ac9d6d333860d85348816e1dca4cc4792e"},
- {file = "greenlet-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5805e71e5b570d490938d55552f5a9e10f477c19400c38bf1d5190d760691846"},
- {file = "greenlet-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:52e93b28db27ae7d208748f45d2db8a7b6a380e0d703f099c949d0f0d80b70e9"},
- {file = "greenlet-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7bfb769f7efa0eefcd039dd19d843a4fbfbac52f1878b1da2ed5793ec9b1a65"},
- {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e6c7db42638dc45cf2e13c73be16bf83179f7859b07cfc139518941320be96"},
- {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1757936efea16e3f03db20efd0cd50a1c86b06734f9f7338a90c4ba85ec2ad5a"},
- {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19075157a10055759066854a973b3d1325d964d498a805bb68a1f9af4aaef8ec"},
- {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9d21aaa84557d64209af04ff48e0ad5e28c5cca67ce43444e939579d085da72"},
- {file = "greenlet-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2847e5d7beedb8d614186962c3d774d40d3374d580d2cbdab7f184580a39d234"},
- {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:97e7ac860d64e2dcba5c5944cfc8fa9ea185cd84061c623536154d5a89237884"},
- {file = "greenlet-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b2c02d2ad98116e914d4f3155ffc905fd0c025d901ead3f6ed07385e19122c94"},
- {file = "greenlet-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:22f79120a24aeeae2b4471c711dcf4f8c736a2bb2fabad2a67ac9a55ea72523c"},
- {file = "greenlet-3.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:100f78a29707ca1525ea47388cec8a049405147719f47ebf3895e7509c6446aa"},
- {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60d5772e8195f4e9ebf74046a9121bbb90090f6550f81d8956a05387ba139353"},
- {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daa7197b43c707462f06d2c693ffdbb5991cbb8b80b5b984007de431493a319c"},
- {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea6b8aa9e08eea388c5f7a276fabb1d4b6b9d6e4ceb12cc477c3d352001768a9"},
- {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d11ebbd679e927593978aa44c10fc2092bc454b7d13fdc958d3e9d508aba7d0"},
- {file = "greenlet-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dbd4c177afb8a8d9ba348d925b0b67246147af806f0b104af4d24f144d461cd5"},
- {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20107edf7c2c3644c67c12205dc60b1bb11d26b2610b276f97d666110d1b511d"},
- {file = "greenlet-3.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8bef097455dea90ffe855286926ae02d8faa335ed8e4067326257cb571fc1445"},
- {file = "greenlet-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:b2d3337dcfaa99698aa2377c81c9ca72fcd89c07e7eb62ece3f23a3fe89b2ce4"},
- {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80ac992f25d10aaebe1ee15df45ca0d7571d0f70b645c08ec68733fb7a020206"},
- {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:337322096d92808f76ad26061a8f5fccb22b0809bea39212cd6c406f6a7060d2"},
- {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9934adbd0f6e476f0ecff3c94626529f344f57b38c9a541f87098710b18af0a"},
- {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc4d815b794fd8868c4d67602692c21bf5293a75e4b607bb92a11e821e2b859a"},
- {file = "greenlet-3.0.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41bdeeb552d814bcd7fb52172b304898a35818107cc8778b5101423c9017b3de"},
- {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e6061bf1e9565c29002e3c601cf68569c450be7fc3f7336671af7ddb4657166"},
- {file = "greenlet-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fa24255ae3c0ab67e613556375a4341af04a084bd58764731972bcbc8baeba36"},
- {file = "greenlet-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:b489c36d1327868d207002391f662a1d163bdc8daf10ab2e5f6e41b9b96de3b1"},
- {file = "greenlet-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f33f3258aae89da191c6ebaa3bc517c6c4cbc9b9f689e5d8452f7aedbb913fa8"},
- {file = "greenlet-3.0.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d2905ce1df400360463c772b55d8e2518d0e488a87cdea13dd2c71dcb2a1fa16"},
- {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a02d259510b3630f330c86557331a3b0e0c79dac3d166e449a39363beaae174"},
- {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55d62807f1c5a1682075c62436702aaba941daa316e9161e4b6ccebbbf38bda3"},
- {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fcc780ae8edbb1d050d920ab44790201f027d59fdbd21362340a85c79066a74"},
- {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eddd98afc726f8aee1948858aed9e6feeb1758889dfd869072d4465973f6bfd"},
- {file = "greenlet-3.0.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eabe7090db68c981fca689299c2d116400b553f4b713266b130cfc9e2aa9c5a9"},
- {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f2f6d303f3dee132b322a14cd8765287b8f86cdc10d2cb6a6fae234ea488888e"},
- {file = "greenlet-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d923ff276f1c1f9680d32832f8d6c040fe9306cbfb5d161b0911e9634be9ef0a"},
- {file = "greenlet-3.0.1-cp38-cp38-win32.whl", hash = "sha256:0b6f9f8ca7093fd4433472fd99b5650f8a26dcd8ba410e14094c1e44cd3ceddd"},
- {file = "greenlet-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:990066bff27c4fcf3b69382b86f4c99b3652bab2a7e685d968cd4d0cfc6f67c6"},
- {file = "greenlet-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ce85c43ae54845272f6f9cd8320d034d7a946e9773c693b27d620edec825e376"},
- {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ee2e967bd7ff85d84a2de09df10e021c9b38c7d91dead95b406ed6350c6997"},
- {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87c8ceb0cf8a5a51b8008b643844b7f4a8264a2c13fcbcd8a8316161725383fe"},
- {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6a8c9d4f8692917a3dc7eb25a6fb337bff86909febe2f793ec1928cd97bedfc"},
- {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fbc5b8f3dfe24784cee8ce0be3da2d8a79e46a276593db6868382d9c50d97b1"},
- {file = "greenlet-3.0.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85d2b77e7c9382f004b41d9c72c85537fac834fb141b0296942d52bf03fe4a3d"},
- {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:696d8e7d82398e810f2b3622b24e87906763b6ebfd90e361e88eb85b0e554dc8"},
- {file = "greenlet-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:329c5a2e5a0ee942f2992c5e3ff40be03e75f745f48847f118a3cfece7a28546"},
- {file = "greenlet-3.0.1-cp39-cp39-win32.whl", hash = "sha256:cf868e08690cb89360eebc73ba4be7fb461cfbc6168dd88e2fbbe6f31812cd57"},
- {file = "greenlet-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ac4a39d1abae48184d420aa8e5e63efd1b75c8444dd95daa3e03f6c6310e9619"},
- {file = "greenlet-3.0.1.tar.gz", hash = "sha256:816bd9488a94cba78d93e1abb58000e8266fa9cc2aa9ccdd6eb0696acb24005b"},
+ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"},
+ {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"},
+ {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"},
+ {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"},
+ {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"},
+ {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"},
+ {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"},
+ {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"},
+ {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"},
+ {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"},
+ {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"},
+ {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"},
+ {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"},
+ {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"},
+ {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"},
+ {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"},
+ {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"},
+ {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"},
+ {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"},
+ {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"},
+ {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"},
+ {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"},
+ {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"},
+ {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"},
+ {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"},
+ {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"},
+ {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"},
+ {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"},
+ {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"},
+ {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"},
+ {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"},
+ {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"},
+ {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"},
+ {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"},
+ {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"},
+ {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"},
+ {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"},
+ {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"},
+ {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"},
+ {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"},
+ {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"},
+ {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"},
+ {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"},
+ {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"},
+ {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"},
+ {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"},
+ {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"},
+ {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"},
+ {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"},
+ {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"},
+ {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"},
+ {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"},
+ {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"},
+ {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"},
+ {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"},
+ {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"},
+ {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"},
+ {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"},
]
[package.extras]
-docs = ["Sphinx"]
+docs = ["Sphinx", "furo"]
test = ["objgraph", "psutil"]
[[package]]
@@ -802,13 +775,13 @@ lxml = ["lxml"]
[[package]]
name = "idna"
-version = "3.4"
+version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.5"
files = [
- {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
- {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+ {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
+ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
[[package]]
@@ -824,20 +797,20 @@ files = [
[[package]]
name = "importlib-metadata"
-version = "6.8.0"
+version = "7.0.1"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
- {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
- {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
+ {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"},
+ {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
@@ -852,23 +825,6 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
-[[package]]
-name = "isort"
-version = "5.12.0"
-description = "A Python utility / library to sort Python imports."
-optional = false
-python-versions = ">=3.8.0"
-files = [
- {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
- {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
-]
-
-[package.extras]
-colors = ["colorama (>=0.4.3)"]
-pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
-plugins = ["setuptools"]
-requirements-deprecated-finder = ["pip-api", "pipreqs"]
-
[[package]]
name = "jeepney"
version = "0.8.0"
@@ -903,13 +859,13 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "jinxed"
-version = "1.2.0"
+version = "1.2.1"
description = "Jinxed Terminal Library"
optional = false
python-versions = "*"
files = [
- {file = "jinxed-1.2.0-py2.py3-none-any.whl", hash = "sha256:cfc2b2e4e3b4326954d546ba6d6b9a7a796ddcb0aef8d03161d005177eb0d48b"},
- {file = "jinxed-1.2.0.tar.gz", hash = "sha256:032acda92d5c57cd216033cbbd53de731e6ed50deb63eb4781336ca55f72cda5"},
+ {file = "jinxed-1.2.1-py2.py3-none-any.whl", hash = "sha256:37422659c4925969c66148c5e64979f553386a4226b9484d910d3094ced37d30"},
+ {file = "jinxed-1.2.1.tar.gz", hash = "sha256:30c3f861b73279fea1ed928cfd4dfb1f273e16cd62c8a32acfac362da0f78f3f"},
]
[package.dependencies]
@@ -955,51 +911,6 @@ secretstorage = {version = "*", markers = "sys_platform == \"linux\""}
docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"]
testing = ["pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8"]
-[[package]]
-name = "lazy-object-proxy"
-version = "1.9.0"
-description = "A fast and thorough lazy object proxy."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"},
- {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"},
- {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"},
- {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"},
- {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"},
- {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
-]
-
[[package]]
name = "livereload"
version = "2.6.3"
@@ -1109,17 +1020,6 @@ files = [
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
]
-[[package]]
-name = "mccabe"
-version = "0.7.0"
-description = "McCabe checker, plugin for flake8"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
- {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
-]
-
[[package]]
name = "mdurl"
version = "0.1.2"
@@ -1198,38 +1098,38 @@ files = [
[[package]]
name = "mypy"
-version = "1.7.0"
+version = "1.8.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"},
- {file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"},
- {file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"},
- {file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"},
- {file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"},
- {file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"},
- {file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"},
- {file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"},
- {file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"},
- {file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"},
- {file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"},
- {file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"},
- {file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"},
- {file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"},
- {file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"},
- {file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"},
- {file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"},
- {file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"},
- {file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"},
- {file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"},
- {file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"},
- {file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"},
- {file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"},
- {file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"},
- {file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"},
- {file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"},
- {file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"},
+ {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
+ {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
+ {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
+ {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
+ {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
+ {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
+ {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
+ {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
+ {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
+ {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
+ {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
+ {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
+ {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
+ {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
+ {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
+ {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
+ {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
+ {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
+ {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
+ {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
+ {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
+ {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
+ {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
+ {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
+ {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
+ {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
+ {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
]
[package.dependencies]
@@ -1278,24 +1178,24 @@ files = [
[[package]]
name = "pathspec"
-version = "0.11.2"
+version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
- {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "pexpect"
-version = "4.8.0"
+version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = true
python-versions = "*"
files = [
- {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
- {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
+ {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
]
[package.dependencies]
@@ -1317,13 +1217,13 @@ testing = ["pytest", "pytest-cov"]
[[package]]
name = "platformdirs"
-version = "3.11.0"
+version = "4.1.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
- {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
+ {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"},
+ {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"},
]
[package.extras]
@@ -1410,18 +1310,18 @@ files = [
[[package]]
name = "pydantic"
-version = "2.5.1"
+version = "2.5.3"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydantic-2.5.1-py3-none-any.whl", hash = "sha256:dc5244a8939e0d9a68f1f1b5f550b2e1c879912033b1becbedb315accc75441b"},
- {file = "pydantic-2.5.1.tar.gz", hash = "sha256:0b8be5413c06aadfbe56f6dc1d45c9ed25fd43264414c571135c97dd77c2bedb"},
+ {file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"},
+ {file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
-pydantic-core = "2.14.3"
+pydantic-core = "2.14.6"
typing-extensions = ">=4.6.1"
[package.extras]
@@ -1429,116 +1329,116 @@ email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
-version = "2.14.3"
+version = "2.14.6"
description = ""
optional = false
python-versions = ">=3.7"
files = [
- {file = "pydantic_core-2.14.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:ba44fad1d114539d6a1509966b20b74d2dec9a5b0ee12dd7fd0a1bb7b8785e5f"},
- {file = "pydantic_core-2.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a70d23eedd88a6484aa79a732a90e36701048a1509078d1b59578ef0ea2cdf5"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc24728a1a9cef497697e53b3d085fb4d3bc0ef1ef4d9b424d9cf808f52c146"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab4a2381005769a4af2ffddae74d769e8a4aae42e970596208ec6d615c6fb080"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a12bf088d6fa20e094f9a477bf84bd823651d8b8384f59bcd50eaa92e6a52"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:38aed5a1bbc3025859f56d6a32f6e53ca173283cb95348e03480f333b1091e7d"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1767bd3f6370458e60c1d3d7b1d9c2751cc1ad743434e8ec84625a610c8b9195"},
- {file = "pydantic_core-2.14.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7cb0c397f29688a5bd2c0dbd44451bc44ebb9b22babc90f97db5ec3e5bb69977"},
- {file = "pydantic_core-2.14.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ff737f24b34ed26de62d481ef522f233d3c5927279f6b7229de9b0deb3f76b5"},
- {file = "pydantic_core-2.14.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1a39fecb5f0b19faee9a8a8176c805ed78ce45d760259a4ff3d21a7daa4dfc1"},
- {file = "pydantic_core-2.14.3-cp310-none-win32.whl", hash = "sha256:ccbf355b7276593c68fa824030e68cb29f630c50e20cb11ebb0ee450ae6b3d08"},
- {file = "pydantic_core-2.14.3-cp310-none-win_amd64.whl", hash = "sha256:536e1f58419e1ec35f6d1310c88496f0d60e4f182cacb773d38076f66a60b149"},
- {file = "pydantic_core-2.14.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f1f46700402312bdc31912f6fc17f5ecaaaa3bafe5487c48f07c800052736289"},
- {file = "pydantic_core-2.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:88ec906eb2d92420f5b074f59cf9e50b3bb44f3cb70e6512099fdd4d88c2f87c"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:056ea7cc3c92a7d2a14b5bc9c9fa14efa794d9f05b9794206d089d06d3433dc7"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076edc972b68a66870cec41a4efdd72a6b655c4098a232314b02d2bfa3bfa157"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e71f666c3bf019f2490a47dddb44c3ccea2e69ac882f7495c68dc14d4065eac2"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f518eac285c9632be337323eef9824a856f2680f943a9b68ac41d5f5bad7df7c"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dbab442a8d9ca918b4ed99db8d89d11b1f067a7dadb642476ad0889560dac79"},
- {file = "pydantic_core-2.14.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0653fb9fc2fa6787f2fa08631314ab7fc8070307bd344bf9471d1b7207c24623"},
- {file = "pydantic_core-2.14.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c54af5069da58ea643ad34ff32fd6bc4eebb8ae0fef9821cd8919063e0aeeaab"},
- {file = "pydantic_core-2.14.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc956f78651778ec1ab105196e90e0e5f5275884793ab67c60938c75bcca3989"},
- {file = "pydantic_core-2.14.3-cp311-none-win32.whl", hash = "sha256:5b73441a1159f1fb37353aaefb9e801ab35a07dd93cb8177504b25a317f4215a"},
- {file = "pydantic_core-2.14.3-cp311-none-win_amd64.whl", hash = "sha256:7349f99f1ef8b940b309179733f2cad2e6037a29560f1b03fdc6aa6be0a8d03c"},
- {file = "pydantic_core-2.14.3-cp311-none-win_arm64.whl", hash = "sha256:ec79dbe23702795944d2ae4c6925e35a075b88acd0d20acde7c77a817ebbce94"},
- {file = "pydantic_core-2.14.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8f5624f0f67f2b9ecaa812e1dfd2e35b256487566585160c6c19268bf2ffeccc"},
- {file = "pydantic_core-2.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c2d118d1b6c9e2d577e215567eedbe11804c3aafa76d39ec1f8bc74e918fd07"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe863491664c6720d65ae438d4efaa5eca766565a53adb53bf14bc3246c72fe0"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:136bc7247e97a921a020abbd6ef3169af97569869cd6eff41b6a15a73c44ea9b"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeafc7f5bbddc46213707266cadc94439bfa87ecf699444de8be044d6d6eb26f"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16aaf788f1de5a85c8f8fcc9c1ca1dd7dd52b8ad30a7889ca31c7c7606615b8"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc652c354d3362e2932a79d5ac4bbd7170757a41a62c4fe0f057d29f10bebb"},
- {file = "pydantic_core-2.14.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1b92e72babfd56585c75caf44f0b15258c58e6be23bc33f90885cebffde3400"},
- {file = "pydantic_core-2.14.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:75f3f534f33651b73f4d3a16d0254de096f43737d51e981478d580f4b006b427"},
- {file = "pydantic_core-2.14.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c9ffd823c46e05ef3eb28b821aa7bc501efa95ba8880b4a1380068e32c5bed47"},
- {file = "pydantic_core-2.14.3-cp312-none-win32.whl", hash = "sha256:12e05a76b223577a4696c76d7a6b36a0ccc491ffb3c6a8cf92d8001d93ddfd63"},
- {file = "pydantic_core-2.14.3-cp312-none-win_amd64.whl", hash = "sha256:1582f01eaf0537a696c846bea92082082b6bfc1103a88e777e983ea9fbdc2a0f"},
- {file = "pydantic_core-2.14.3-cp312-none-win_arm64.whl", hash = "sha256:96fb679c7ca12a512d36d01c174a4fbfd912b5535cc722eb2c010c7b44eceb8e"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:71ed769b58d44e0bc2701aa59eb199b6665c16e8a5b8b4a84db01f71580ec448"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:5402ee0f61e7798ea93a01b0489520f2abfd9b57b76b82c93714c4318c66ca06"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaab9dc009e22726c62fe3b850b797e7f0e7ba76d245284d1064081f512c7226"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92486a04d54987054f8b4405a9af9d482e5100d6fe6374fc3303015983fc8bda"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf08b43d1d5d1678f295f0431a4a7e1707d4652576e1d0f8914b5e0213bfeee5"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8ca13480ce16daad0504be6ce893b0ee8ec34cd43b993b754198a89e2787f7e"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44afa3c18d45053fe8d8228950ee4c8eaf3b5a7f3b64963fdeac19b8342c987f"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56814b41486e2d712a8bc02a7b1f17b87fa30999d2323bbd13cf0e52296813a1"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c3dc2920cc96f9aa40c6dc54256e436cc95c0a15562eb7bd579e1811593c377e"},
- {file = "pydantic_core-2.14.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e483b8b913fcd3b48badec54185c150cb7ab0e6487914b84dc7cde2365e0c892"},
- {file = "pydantic_core-2.14.3-cp37-none-win32.whl", hash = "sha256:364dba61494e48f01ef50ae430e392f67ee1ee27e048daeda0e9d21c3ab2d609"},
- {file = "pydantic_core-2.14.3-cp37-none-win_amd64.whl", hash = "sha256:a402ae1066be594701ac45661278dc4a466fb684258d1a2c434de54971b006ca"},
- {file = "pydantic_core-2.14.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:10904368261e4509c091cbcc067e5a88b070ed9a10f7ad78f3029c175487490f"},
- {file = "pydantic_core-2.14.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:260692420028319e201b8649b13ac0988974eeafaaef95d0dfbf7120c38dc000"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1bf1a7b05a65d3b37a9adea98e195e0081be6b17ca03a86f92aeb8b110f468"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7abd17a838a52140e3aeca271054e321226f52df7e0a9f0da8f91ea123afe98"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5c51460ede609fbb4fa883a8fe16e749964ddb459966d0518991ec02eb8dfb9"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d06c78074646111fb01836585f1198367b17d57c9f427e07aaa9ff499003e58d"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af452e69446fadf247f18ac5d153b1f7e61ef708f23ce85d8c52833748c58075"},
- {file = "pydantic_core-2.14.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3ad4968711fb379a67c8c755beb4dae8b721a83737737b7bcee27c05400b047"},
- {file = "pydantic_core-2.14.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c5ea0153482e5b4d601c25465771c7267c99fddf5d3f3bdc238ef930e6d051cf"},
- {file = "pydantic_core-2.14.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:96eb10ef8920990e703da348bb25fedb8b8653b5966e4e078e5be382b430f9e0"},
- {file = "pydantic_core-2.14.3-cp38-none-win32.whl", hash = "sha256:ea1498ce4491236d1cffa0eee9ad0968b6ecb0c1cd711699c5677fc689905f00"},
- {file = "pydantic_core-2.14.3-cp38-none-win_amd64.whl", hash = "sha256:2bc736725f9bd18a60eec0ed6ef9b06b9785454c8d0105f2be16e4d6274e63d0"},
- {file = "pydantic_core-2.14.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:1ea992659c03c3ea811d55fc0a997bec9dde863a617cc7b25cfde69ef32e55af"},
- {file = "pydantic_core-2.14.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2b53e1f851a2b406bbb5ac58e16c4a5496038eddd856cc900278fa0da97f3fc"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c7f8e8a7cf8e81ca7d44bea4f181783630959d41b4b51d2f74bc50f348a090f"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d3b9c91eeb372a64ec6686c1402afd40cc20f61a0866850f7d989b6bf39a41a"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ef3e2e407e4cad2df3c89488a761ed1f1c33f3b826a2ea9a411b0a7d1cccf1b"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f86f20a9d5bee1a6ede0f2757b917bac6908cde0f5ad9fcb3606db1e2968bcf5"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61beaa79d392d44dc19d6f11ccd824d3cccb865c4372157c40b92533f8d76dd0"},
- {file = "pydantic_core-2.14.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d41df8e10b094640a6b234851b624b76a41552f637b9fb34dc720b9fe4ef3be4"},
- {file = "pydantic_core-2.14.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c08ac60c3caa31f825b5dbac47e4875bd4954d8f559650ad9e0b225eaf8ed0c"},
- {file = "pydantic_core-2.14.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d8b3932f1a369364606417ded5412c4ffb15bedbcf797c31317e55bd5d920e"},
- {file = "pydantic_core-2.14.3-cp39-none-win32.whl", hash = "sha256:caa94726791e316f0f63049ee00dff3b34a629b0d099f3b594770f7d0d8f1f56"},
- {file = "pydantic_core-2.14.3-cp39-none-win_amd64.whl", hash = "sha256:2494d20e4c22beac30150b4be3b8339bf2a02ab5580fa6553ca274bc08681a65"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:fe272a72c7ed29f84c42fedd2d06c2f9858dc0c00dae3b34ba15d6d8ae0fbaaf"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7e63a56eb7fdee1587d62f753ccd6d5fa24fbeea57a40d9d8beaef679a24bdd6"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7692f539a26265cece1e27e366df5b976a6db6b1f825a9e0466395b314ee48b"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af46f0b7a1342b49f208fed31f5a83b8495bb14b652f621e0a6787d2f10f24ee"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e2f9d76c00e805d47f19c7a96a14e4135238a7551a18bfd89bb757993fd0933"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:de52ddfa6e10e892d00f747bf7135d7007302ad82e243cf16d89dd77b03b649d"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:38113856c7fad8c19be7ddd57df0c3e77b1b2336459cb03ee3903ce9d5e236ce"},
- {file = "pydantic_core-2.14.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:354db020b1f8f11207b35360b92d95725621eb92656725c849a61e4b550f4acc"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:76fc18653a5c95e5301a52d1b5afb27c9adc77175bf00f73e94f501caf0e05ad"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2646f8270f932d79ba61102a15ea19a50ae0d43b314e22b3f8f4b5fabbfa6e38"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37dad73a2f82975ed563d6a277fd9b50e5d9c79910c4aec787e2d63547202315"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:113752a55a8eaece2e4ac96bc8817f134c2c23477e477d085ba89e3aa0f4dc44"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:8488e973547e8fb1b4193fd9faf5236cf1b7cd5e9e6dc7ff6b4d9afdc4c720cb"},
- {file = "pydantic_core-2.14.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3d1dde10bd9962b1434053239b1d5490fc31a2b02d8950a5f731bc584c7a5a0f"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2c83892c7bf92b91d30faca53bb8ea21f9d7e39f0ae4008ef2c2f91116d0464a"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:849cff945284c577c5f621d2df76ca7b60f803cc8663ff01b778ad0af0e39bb9"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa89919fbd8a553cd7d03bf23d5bc5deee622e1b5db572121287f0e64979476"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf15145b1f8056d12c67255cd3ce5d317cd4450d5ee747760d8d088d85d12a2d"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4cc6bb11f4e8e5ed91d78b9880774fbc0856cb226151b0a93b549c2b26a00c19"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:832d16f248ca0cc96929139734ec32d21c67669dcf8a9f3f733c85054429c012"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b02b5e1f54c3396c48b665050464803c23c685716eb5d82a1d81bf81b5230da4"},
- {file = "pydantic_core-2.14.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1f2d4516c32255782153e858f9a900ca6deadfb217fd3fb21bb2b60b4e04d04d"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0a3e51c2be472b7867eb0c5d025b91400c2b73a0823b89d4303a9097e2ec6655"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:df33902464410a1f1a0411a235f0a34e7e129f12cb6340daca0f9d1390f5fe10"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27828f0227b54804aac6fb077b6bb48e640b5435fdd7fbf0c274093a7b78b69c"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2979dc80246e18e348de51246d4c9b410186ffa3c50e77924bec436b1e36cb"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b28996872b48baf829ee75fa06998b607c66a4847ac838e6fd7473a6b2ab68e7"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ca55c9671bb637ce13d18ef352fd32ae7aba21b4402f300a63f1fb1fd18e0364"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:aecd5ed096b0e5d93fb0367fd8f417cef38ea30b786f2501f6c34eabd9062c38"},
- {file = "pydantic_core-2.14.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:44aaf1a07ad0824e407dafc637a852e9a44d94664293bbe7d8ee549c356c8882"},
- {file = "pydantic_core-2.14.3.tar.gz", hash = "sha256:3ad083df8fe342d4d8d00cc1d3c1a23f0dc84fce416eb301e69f1ddbbe124d3f"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"},
+ {file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"},
+ {file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"},
+ {file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"},
+ {file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"},
+ {file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"},
+ {file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"},
+ {file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"},
+ {file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"},
+ {file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"},
+ {file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"},
+ {file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"},
+ {file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"},
+ {file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"},
+ {file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"},
+ {file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"},
+ {file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"},
+ {file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"},
+ {file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"},
+ {file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"},
+ {file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"},
+ {file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"},
+ {file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"},
+ {file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"},
+ {file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"},
+ {file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"},
]
[package.dependencies]
@@ -1546,13 +1446,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pygments"
-version = "2.17.0"
+version = "2.17.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.7"
files = [
- {file = "pygments-2.17.0-py3-none-any.whl", hash = "sha256:cd0c46944b2551af02ecc15961050182ea120d3895000e2676160820f3421527"},
- {file = "pygments-2.17.0.tar.gz", hash = "sha256:edaa0fa2453d055d0ac94449d1f73ec7bc52c5e318204da1377c1392978c4a8d"},
+ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
+ {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
]
[package.extras]
@@ -1570,30 +1470,6 @@ files = [
{file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"},
]
-[[package]]
-name = "pylint"
-version = "2.13.9"
-description = "python code static checker"
-optional = false
-python-versions = ">=3.6.2"
-files = [
- {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"},
- {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"},
-]
-
-[package.dependencies]
-astroid = ">=2.11.5,<=2.12.0-dev0"
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-dill = ">=0.2"
-isort = ">=4.2.5,<6"
-mccabe = ">=0.6,<0.8"
-platformdirs = ">=2.2.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
-
-[package.extras]
-testutil = ["gitpython (>3)"]
-
[[package]]
name = "pyparsing"
version = "2.4.7"
@@ -1839,17 +1715,17 @@ jeepney = ">=0.6"
[[package]]
name = "setuptools"
-version = "68.2.2"
+version = "69.0.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
-optional = false
+optional = true
python-versions = ">=3.8"
files = [
- {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
- {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
+ {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
+ {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
]
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
@@ -2058,13 +1934,13 @@ test = ["pytest"]
[[package]]
name = "sphinxext-opengraph"
-version = "0.9.0"
+version = "0.9.1"
description = "Sphinx Extension to enable OGP support"
optional = false
python-versions = ">=3.8"
files = [
- {file = "sphinxext-opengraph-0.9.0.tar.gz", hash = "sha256:4e57e25b6d56f47b9c06a5a5d68a2a00ed3577c8a39e459b52118c6bfe5e8c8b"},
- {file = "sphinxext_opengraph-0.9.0-py3-none-any.whl", hash = "sha256:ab1eb2ffb531fb85b695e719dba7b0245b0643f6b6c0d1cc258d15a81e72a9f1"},
+ {file = "sphinxext-opengraph-0.9.1.tar.gz", hash = "sha256:dd2868a1e7c9497977fbbf44cc0844a42af39ca65fe1bb0272518af225d06fc5"},
+ {file = "sphinxext_opengraph-0.9.1-py3-none-any.whl", hash = "sha256:b3b230cc6a5b5189139df937f0d9c7b23c7c204493b22646273687969dcb760e"},
]
[package.dependencies]
@@ -2094,22 +1970,22 @@ files = [
[[package]]
name = "tornado"
-version = "6.3.3"
+version = "6.4"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
optional = false
python-versions = ">= 3.8"
files = [
- {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"},
- {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"},
- {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"},
- {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"},
- {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"},
- {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"},
- {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"},
- {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"},
- {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"},
- {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"},
- {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"},
+ {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"},
+ {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"},
+ {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"},
+ {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"},
+ {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"},
]
[[package]]
@@ -2138,13 +2014,13 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.
[[package]]
name = "typing-extensions"
-version = "4.8.0"
+version = "4.9.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
- {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
- {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
+ {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
+ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
]
[[package]]
@@ -2165,19 +2041,19 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
-version = "20.24.6"
+version = "20.25.0"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
- {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
- {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
+ {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
+ {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
-platformdirs = ">=3.9.1,<4"
+platformdirs = ">=3.9.1,<5"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
@@ -2185,13 +2061,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
[[package]]
name = "wcwidth"
-version = "0.2.10"
+version = "0.2.12"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
- {file = "wcwidth-0.2.10-py2.py3-none-any.whl", hash = "sha256:aec5179002dd0f0d40c456026e74a729661c9d468e1ed64405e3a6c2176ca36f"},
- {file = "wcwidth-0.2.10.tar.gz", hash = "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97"},
+ {file = "wcwidth-0.2.12-py2.py3-none-any.whl", hash = "sha256:f26ec43d96c8cbfed76a5075dac87680124fa84e0855195a6184da9c187f133c"},
+ {file = "wcwidth-0.2.12.tar.gz", hash = "sha256:f01c104efdf57971bcb756f054dd58ddec5204dd15fa31d6503ea57947d97c02"},
]
[[package]]
@@ -2205,85 +2081,6 @@ files = [
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
-[[package]]
-name = "wrapt"
-version = "1.16.0"
-description = "Module for decorators, wrappers and monkey patching."
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"},
- {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"},
- {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"},
- {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"},
- {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"},
- {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"},
- {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"},
- {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"},
- {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"},
- {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"},
- {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"},
- {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"},
- {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"},
- {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"},
- {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"},
- {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"},
- {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"},
- {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"},
- {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"},
- {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"},
- {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"},
- {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"},
- {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"},
- {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"},
- {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"},
- {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"},
- {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"},
- {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"},
- {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"},
- {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"},
- {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"},
- {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"},
- {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"},
- {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"},
- {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"},
- {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"},
- {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"},
- {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"},
- {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"},
- {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"},
- {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"},
- {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"},
- {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"},
- {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"},
- {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"},
- {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"},
- {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"},
- {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"},
- {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"},
- {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"},
- {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"},
- {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"},
- {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"},
- {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"},
- {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"},
- {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"},
- {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"},
- {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"},
- {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"},
- {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"},
- {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"},
- {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"},
- {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"},
- {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"},
- {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"},
- {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"},
- {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"},
- {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"},
- {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"},
- {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"},
-]
-
[[package]]
name = "zipp"
version = "3.17.0"
@@ -2305,4 +2102,4 @@ poetry-plugin = ["poetry"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.8"
-content-hash = "31d1ea280f619d3573d9b16bb93e992cebf8ddc3953a4d5fe59b580cc844c23b"
+content-hash = "bc143bfbfbdc35f145adb30e9427178934ce3eb35654aba47cb3276f12e5d11d"
diff --git a/pyproject.toml b/pyproject.toml
index 35b1cf077..9e5fa36a0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,6 @@ poetry = {version = "^1.0", allow-prereleases = true, optional = true}
[tool.poetry.group.ci.dependencies]
black = "^23.3.0"
mypy = "^1.1.1"
-pylint = "^2.13.0"
pytest = "^7.1.2"
pytest-cov = "^3.0.0"
rstcheck = { version = "^6.1.2", python = "<4" }
@@ -115,16 +114,8 @@ _clean_docs.script = "shutil:rmtree('docs/_build', ignore_errors=1)"
[tool.poe.tasks.lint]
help = "Run linting tools on the code base"
- sequence = ["lint-ruff", "lint-pylint"]
-
- [tool.poe.tasks.lint-ruff]
- help = "Evaluate ruff rules"
cmd = "ruff check ."
- [tool.poe.tasks.lint-pylint]
- help = "Evaluate pylint rules"
- cmd = "pylint poethepoet"
-
[tool.poe.tasks.style]
help = "Validate black code style"
cmd = "black . --check --diff"
@@ -147,7 +138,7 @@ _clean_docs.script = "shutil:rmtree('docs/_build', ignore_errors=1)"
[tool.rstcheck]
-ignore_messages = [
+ignore_messages = [
"Unknown directive type \"autoclass\"",
"Hyperlink target \"shell-completion\" is not referenced.",
"Hyperlink target \"envfile-option\" is not referenced.",
@@ -193,7 +184,7 @@ select = [
"PTH", # flake8-use-pathlib
"PGH", # pygrep-hooks
"PERF", # perflint
- "RUF" # ruff-specific rules
+ "RUF", # ruff-specific rules
]
ignore = [
"C408", # unnecessary-collection-call
@@ -201,7 +192,7 @@ ignore = [
"SIM118", # in-dict-keys
"PTH109", # os-getcwd
"PTH123", # builtin-open
- "RUF012" # mutable-class-default
+ "RUF012", # mutable-class-default
]
fixable = ["E", "F", "I"]
diff --git a/tests/conftest.py b/tests/conftest.py
index 94112d91a..f7dd92b17 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -146,6 +146,7 @@ def run_poe_subproc(
subproc_env = dict(os.environ)
subproc_env.pop("VIRTUAL_ENV", None)
+ subproc_env.pop("POE_CWD", None) # do not inherit this from the test
subproc_env.pop("POE_PWD", None) # do not inherit this from the test
if env:
subproc_env.update(env)
diff --git a/tests/fixtures/cwd_project/pyproject.toml b/tests/fixtures/cwd_project/pyproject.toml
index eb760bd8a..ecb9f4912 100644
--- a/tests/fixtures/cwd_project/pyproject.toml
+++ b/tests/fixtures/cwd_project/pyproject.toml
@@ -11,7 +11,7 @@ cwd = "./subdir/${BAR_ENV}"
[tool.poe.tasks.cwd_poe_pwd]
cmd = "poe_test_pwd"
-cwd = "${POE_PWD}"
+cwd = "${POE_CWD}"
[tool.poe.tasks.cwd_arg]
cmd = "poe_test_pwd"
diff --git a/tests/fixtures/envfile_project/pyproject.toml b/tests/fixtures/envfile_project/pyproject.toml
index 9dd6e1985..a1ed0b8ae 100644
--- a/tests/fixtures/envfile_project/pyproject.toml
+++ b/tests/fixtures/envfile_project/pyproject.toml
@@ -1,12 +1,12 @@
[tool.poe]
envfile = "credentials.env"
-env = { HOST = "${HOST}:80" }
+env = { HOST = "${HOST}:80" } # reference and override value from envfile
[tool.poe.tasks.deploy-dev]
cmd = """
poe_test_echo "deploying to ${USER}:${PASSWORD}@${HOST}${PATH_SUFFIX}"
"""
-env = { HOST = "${HOST}80" } # reference and override value from envfile
+env = { HOST = "${HOST}80" } # reference and override value from envfile again
[tool.poe.tasks.deploy-prod]
cmd = """
diff --git a/tests/fixtures/monorepo_project/env1 b/tests/fixtures/monorepo_project/env1
new file mode 100644
index 000000000..20af8f5fc
--- /dev/null
+++ b/tests/fixtures/monorepo_project/env1
@@ -0,0 +1 @@
+REL_ROOT="rel to root"
diff --git a/tests/fixtures/monorepo_project/env1t b/tests/fixtures/monorepo_project/env1t
new file mode 100644
index 000000000..fb1a32c90
--- /dev/null
+++ b/tests/fixtures/monorepo_project/env1t
@@ -0,0 +1 @@
+TASK_REL_ROOT="task level rel to root"
diff --git a/tests/fixtures/monorepo_project/env2 b/tests/fixtures/monorepo_project/env2
new file mode 100644
index 000000000..5353a7aa1
--- /dev/null
+++ b/tests/fixtures/monorepo_project/env2
@@ -0,0 +1 @@
+REL_PROC_CWD="rel to process cwd"
diff --git a/tests/fixtures/monorepo_project/env2t b/tests/fixtures/monorepo_project/env2t
new file mode 100644
index 000000000..d13a10867
--- /dev/null
+++ b/tests/fixtures/monorepo_project/env2t
@@ -0,0 +1 @@
+TASK_REL_PROC_CWD="task level rel to process cwd"
diff --git a/tests/fixtures/monorepo_project/pyproject.toml b/tests/fixtures/monorepo_project/pyproject.toml
index 9e2be8304..27f7e22e0 100644
--- a/tests/fixtures/monorepo_project/pyproject.toml
+++ b/tests/fixtures/monorepo_project/pyproject.toml
@@ -1,12 +1,18 @@
[[tool.poe.include]]
path = "subproject_1/pyproject.toml"
+
[[tool.poe.include]]
path = "subproject_2/pyproject.toml"
cwd = "subproject_2"
+
[[tool.poe.include]]
path = "subproject_3/pyproject.toml"
+[[tool.poe.include]]
+path = "subproject_4/pyproject.toml"
+cwd = "subproject_4/exec_dir"
+
[tool.poe.tasks.get_cwd_0]
interpreter = "python"
diff --git a/tests/fixtures/monorepo_project/subproject_3/env3 b/tests/fixtures/monorepo_project/subproject_3/env3
new file mode 100644
index 000000000..da6d20f60
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_3/env3
@@ -0,0 +1 @@
+REL_SOURCE_CONFIG="rel to source config"
diff --git a/tests/fixtures/monorepo_project/subproject_3/env3t b/tests/fixtures/monorepo_project/subproject_3/env3t
new file mode 100644
index 000000000..f0c1d1c76
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_3/env3t
@@ -0,0 +1 @@
+TASK_REL_SOURCE_CONFIG="task level rel to source config"
diff --git a/tests/fixtures/monorepo_project/subproject_3/pyproject.toml b/tests/fixtures/monorepo_project/subproject_3/pyproject.toml
index f33eb7fe9..c2a0cf6df 100644
--- a/tests/fixtures/monorepo_project/subproject_3/pyproject.toml
+++ b/tests/fixtures/monorepo_project/subproject_3/pyproject.toml
@@ -1,5 +1,27 @@
+[tool.poe]
+envfile = ["env1", "${POE_CWD}/env2", "${POE_CONF_DIR}/env3"]
+
+[tool.poe.tasks.subproj3_env]
+shell = """
+echo POE_ROOT: ${POE_ROOT}
+echo POE_CWD: ${POE_CWD}
+echo POE_CONF_DIR: ${POE_CONF_DIR}
+echo POE_ROOT_COPY: ${POE_ROOT_COPY}
+echo POE_CWD_COPY: ${POE_CWD_COPY}
+echo POE_CONF_DIR_COPY: ${POE_CONF_DIR_COPY}
+echo REL_ROOT: ${REL_ROOT}
+echo REL_PROC_CWD: ${REL_PROC_CWD}
+echo REL_SOURCE_CONFIG: ${REL_SOURCE_CONFIG}
+echo TASK_REL_ROOT: ${TASK_REL_ROOT}
+echo TASK_REL_PROC_CWD: ${TASK_REL_PROC_CWD}
+echo TASK_REL_SOURCE_CONFIG: ${TASK_REL_SOURCE_CONFIG}
+"""
+envfile = ["env1t", "${POE_CWD}/env2t", "${POE_CONF_DIR}/env3t"]
+env = { POE_ROOT_COPY = "${POE_ROOT}", POE_CWD_COPY = "${POE_CWD}", POE_CONF_DIR_COPY = "${POE_CONF_DIR}" }
+
+
[tool.poe.tasks.get_cwd_3]
interpreter = "python"
diff --git a/tests/fixtures/monorepo_project/subproject_4/env3 b/tests/fixtures/monorepo_project/subproject_4/env3
new file mode 100644
index 000000000..da6d20f60
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_4/env3
@@ -0,0 +1 @@
+REL_SOURCE_CONFIG="rel to source config"
diff --git a/tests/fixtures/monorepo_project/subproject_4/env3t b/tests/fixtures/monorepo_project/subproject_4/env3t
new file mode 100644
index 000000000..f0c1d1c76
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_4/env3t
@@ -0,0 +1 @@
+TASK_REL_SOURCE_CONFIG="task level rel to source config"
diff --git a/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0 b/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0
new file mode 100644
index 000000000..8ab149fe0
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0
@@ -0,0 +1 @@
+FROM_INCLUDE_CWD="rel to cwd"
diff --git a/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0t b/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0t
new file mode 100644
index 000000000..b411a9c7e
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_4/exec_dir/env0t
@@ -0,0 +1 @@
+TASK_FROM_INCLUDE_CWD="task level rel to cwd"
diff --git a/tests/fixtures/monorepo_project/subproject_4/pyproject.toml b/tests/fixtures/monorepo_project/subproject_4/pyproject.toml
new file mode 100644
index 000000000..b10e25ae5
--- /dev/null
+++ b/tests/fixtures/monorepo_project/subproject_4/pyproject.toml
@@ -0,0 +1,23 @@
+
+[tool.poe]
+envfile = ["env0", "${POE_ROOT}/env1", "${POE_CWD}/env2", "${POE_CONF_DIR}/../env3"]
+
+[tool.poe.tasks.subproj4_env]
+shell = """
+echo POE_ROOT: ${POE_ROOT}
+echo POE_CWD: ${POE_CWD}
+echo POE_CONF_DIR: ${POE_CONF_DIR}
+echo POE_ROOT_COPY: ${POE_ROOT_COPY}
+echo POE_CWD_COPY: ${POE_CWD_COPY}
+echo POE_CONF_DIR_COPY: ${POE_CONF_DIR_COPY}
+echo FROM_INCLUDE_CWD: ${FROM_INCLUDE_CWD}
+echo REL_ROOT: ${REL_ROOT}
+echo REL_PROC_CWD: ${REL_PROC_CWD}
+echo REL_SOURCE_CONFIG: ${REL_SOURCE_CONFIG}
+echo TASK_FROM_INCLUDE_CWD: ${TASK_FROM_INCLUDE_CWD}
+echo TASK_REL_ROOT: ${TASK_REL_ROOT}
+echo TASK_REL_PROC_CWD: ${TASK_REL_PROC_CWD}
+echo TASK_REL_SOURCE_CONFIG: ${TASK_REL_SOURCE_CONFIG}
+"""
+envfile = ["env0t", "${POE_ROOT}/env1t", "${POE_CWD}/env2t", "${POE_CONF_DIR}/../env3t"]
+env = { POE_ROOT_COPY = "${POE_ROOT}", POE_CWD_COPY = "${POE_CWD}", POE_CONF_DIR_COPY = "${POE_CONF_DIR}" }
diff --git a/tests/test_cmd_tasks.py b/tests/test_cmd_tasks.py
index 1965dee28..37f1cd0d6 100644
--- a/tests/test_cmd_tasks.py
+++ b/tests/test_cmd_tasks.py
@@ -115,7 +115,7 @@ def test_cmd_task_with_cwd_option_pwd_override(run_poe_subproc, poe_project_path
"cwd_poe_pwd",
project="cwd",
env={
- "POE_PWD": str(
+ "POE_CWD": str(
poe_project_path.joinpath(
"tests", "fixtures", "cwd_project", "subdir", "bar"
)
diff --git a/tests/test_ignore_fail.py b/tests/test_ignore_fail.py
index 335cf44a3..65c8b2405 100644
--- a/tests/test_ignore_fail.py
+++ b/tests/test_ignore_fail.py
@@ -19,6 +19,7 @@ def generator(ignore_fail):
project_tmpl += "\nignore_fail = true"
elif not isinstance(ignore_fail, bool):
project_tmpl += f'\nignore_fail = "{ignore_fail}"'
+
with open(tmp_path / "pyproject.toml", "w") as fp:
fp.write(project_tmpl)
@@ -62,7 +63,6 @@ def test_invalid_ingore_value(generate_pyproject, run_poe):
result = run_poe("all_tasks", cwd=project_path)
assert result.code == 1, "Expected non-zero result"
assert (
- "Unsupported value for option `ignore_fail` for task 'all_tasks'."
- ' Expected one of (true, false, "return_zero", "return_non_zero")'
- in result.capture
- )
+ "| Option 'ignore_fail' must be one of "
+ "(True, False, 'return_zero', 'return_non_zero')\n"
+ ) in result.capture
diff --git a/tests/test_includes.py b/tests/test_includes.py
index 15990f9b0..17d9909d7 100644
--- a/tests/test_includes.py
+++ b/tests/test_includes.py
@@ -103,7 +103,9 @@ def test_monorepo_contains_only_expected_tasks(run_poe_subproc, projects):
" get_cwd_1 \n"
" add \n"
" get_cwd_2 \n"
- " get_cwd_3 \n\n\n"
+ " subproj3_env \n"
+ " get_cwd_3 \n"
+ " subproj4_env \n\n\n"
)
assert result.stdout == ""
assert result.stderr == ""
@@ -180,3 +182,129 @@ def test_monorepo_runs_each_task_with_expected_cwd(
else:
assert result.stdout.endswith("/tests/fixtures/example_project\n")
assert result.stderr == ""
+
+
+def test_include_subproject_envfiles_no_cwd_set(run_poe_subproc, projects, is_windows):
+ result = run_poe_subproc("subproj3_env", project="monorepo")
+ assert result.capture == (
+ "Poe => echo POE_ROOT: ${POE_ROOT}\n"
+ "echo POE_CWD: ${POE_CWD}\n"
+ "echo POE_CONF_DIR: ${POE_CONF_DIR}\n"
+ "echo POE_ROOT_COPY: ${POE_ROOT_COPY}\n"
+ "echo POE_CWD_COPY: ${POE_CWD_COPY}\n"
+ "echo POE_CONF_DIR_COPY: ${POE_CONF_DIR_COPY}\n"
+ "echo REL_ROOT: ${REL_ROOT}\n"
+ "echo REL_PROC_CWD: ${REL_PROC_CWD}\n"
+ "echo REL_SOURCE_CONFIG: ${REL_SOURCE_CONFIG}\n"
+ "echo TASK_REL_ROOT: ${TASK_REL_ROOT}\n"
+ "echo TASK_REL_PROC_CWD: ${TASK_REL_PROC_CWD}\n"
+ "echo TASK_REL_SOURCE_CONFIG: ${TASK_REL_SOURCE_CONFIG}\n"
+ )
+ printed_vars = {
+ line.split(": ")[0]: line.split(": ")[1]
+ for line in result.stdout.split("\n")
+ if ": " in line
+ }
+ if is_windows:
+ assert printed_vars["POE_ROOT"].endswith("\\tests\\fixtures\\monorepo_project")
+ assert printed_vars["POE_CWD"].endswith("\\tests\\fixtures\\monorepo_project")
+ assert printed_vars["POE_CONF_DIR"].endswith(
+ "\\tests\\fixtures\\monorepo_project\\subproject_3"
+ )
+ assert printed_vars["POE_ROOT_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project"
+ )
+ assert printed_vars["POE_CWD_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project"
+ )
+ assert printed_vars["POE_CONF_DIR_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project\\subproject_3"
+ )
+ else:
+ assert printed_vars["POE_ROOT"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CWD"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CONF_DIR"].endswith(
+ "/tests/fixtures/monorepo_project/subproject_3"
+ )
+ assert printed_vars["POE_ROOT_COPY"].endswith(
+ "/tests/fixtures/monorepo_project"
+ )
+ assert printed_vars["POE_CWD_COPY"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CONF_DIR_COPY"].endswith(
+ "/tests/fixtures/monorepo_project/subproject_3"
+ )
+ assert result.stdout.endswith(
+ "REL_ROOT: rel to root\n"
+ "REL_PROC_CWD: rel to process cwd\n"
+ "REL_SOURCE_CONFIG: rel to source config\n"
+ "TASK_REL_ROOT: task level rel to root\n"
+ "TASK_REL_PROC_CWD: task level rel to process cwd\n"
+ "TASK_REL_SOURCE_CONFIG: task level rel to source config\n"
+ )
+ assert result.stderr == ""
+
+
+def test_include_subproject_envfiles_with_cwd_set(
+ run_poe_subproc, projects, is_windows
+):
+ result = run_poe_subproc("subproj4_env", project="monorepo")
+ assert result.capture == (
+ "Poe => echo POE_ROOT: ${POE_ROOT}\n"
+ "echo POE_CWD: ${POE_CWD}\n"
+ "echo POE_CONF_DIR: ${POE_CONF_DIR}\n"
+ "echo POE_ROOT_COPY: ${POE_ROOT_COPY}\n"
+ "echo POE_CWD_COPY: ${POE_CWD_COPY}\n"
+ "echo POE_CONF_DIR_COPY: ${POE_CONF_DIR_COPY}\n"
+ "echo FROM_INCLUDE_CWD: ${FROM_INCLUDE_CWD}\n"
+ "echo REL_ROOT: ${REL_ROOT}\n"
+ "echo REL_PROC_CWD: ${REL_PROC_CWD}\n"
+ "echo REL_SOURCE_CONFIG: ${REL_SOURCE_CONFIG}\n"
+ "echo TASK_FROM_INCLUDE_CWD: ${TASK_FROM_INCLUDE_CWD}\n"
+ "echo TASK_REL_ROOT: ${TASK_REL_ROOT}\n"
+ "echo TASK_REL_PROC_CWD: ${TASK_REL_PROC_CWD}\n"
+ "echo TASK_REL_SOURCE_CONFIG: ${TASK_REL_SOURCE_CONFIG}\n"
+ )
+ printed_vars = {
+ line.split(": ")[0]: line.split(": ")[1]
+ for line in result.stdout.split("\n")
+ if ": " in line
+ }
+ if is_windows:
+ assert printed_vars["POE_ROOT"].endswith("\\tests\\fixtures\\monorepo_project")
+ assert printed_vars["POE_CWD"].endswith("\\tests\\fixtures\\monorepo_project")
+ assert printed_vars["POE_CONF_DIR"].endswith(
+ "\\tests\\fixtures\\monorepo_project\\subproject_4\\exec_dir"
+ )
+ assert printed_vars["POE_ROOT_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project"
+ )
+ assert printed_vars["POE_CWD_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project"
+ )
+ assert printed_vars["POE_CONF_DIR_COPY"].endswith(
+ "\\tests\\fixtures\\monorepo_project\\subproject_4\\exec_dir"
+ )
+ else:
+ assert printed_vars["POE_ROOT"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CWD"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CONF_DIR"].endswith(
+ "/tests/fixtures/monorepo_project/subproject_4/exec_dir"
+ )
+ assert printed_vars["POE_ROOT_COPY"].endswith(
+ "/tests/fixtures/monorepo_project"
+ )
+ assert printed_vars["POE_CWD_COPY"].endswith("/tests/fixtures/monorepo_project")
+ assert printed_vars["POE_CONF_DIR_COPY"].endswith(
+ "/tests/fixtures/monorepo_project/subproject_4/exec_dir"
+ )
+ assert result.stdout.endswith(
+ "FROM_INCLUDE_CWD: rel to cwd\n"
+ "REL_ROOT: rel to root\n"
+ "REL_PROC_CWD: rel to process cwd\n"
+ "REL_SOURCE_CONFIG: rel to source config\n"
+ "TASK_FROM_INCLUDE_CWD: task level rel to cwd\n"
+ "TASK_REL_ROOT: task level rel to root\n"
+ "TASK_REL_PROC_CWD: task level rel to process cwd\n"
+ "TASK_REL_SOURCE_CONFIG: task level rel to source config\n"
+ )
+ assert result.stderr == ""
diff --git a/tests/test_poetry_plugin.py b/tests/test_poetry_plugin.py
index c76f37340..d265beb9d 100644
--- a/tests/test_poetry_plugin.py
+++ b/tests/test_poetry_plugin.py
@@ -1,3 +1,4 @@
+import os
import re
import pytest
@@ -27,6 +28,11 @@ def test_poetry_help(run_poetry, projects):
# assert result.stderr == ""
+# TODO: re-enable this test
+@pytest.mark.skipif(
+ 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.usefixtures("_setup_poetry_project")
def test_task_with_cli_dependency(run_poetry, projects, is_windows):
@@ -47,6 +53,11 @@ def test_task_with_cli_dependency(run_poetry, projects, is_windows):
# assert result.stderr == ""
+# TODO: re-enable this test
+@pytest.mark.skipif(
+ 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.usefixtures("_setup_poetry_project")
def test_task_with_lib_dependency(run_poetry, projects):
@@ -148,6 +159,11 @@ def test_running_poetry_command_with_hooks_with_directory(run_poetry, projects):
# assert result.stderr == ""
+# TODO: re-enable this test
+@pytest.mark.skipif(
+ 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.usefixtures("_setup_poetry_project")
def test_task_with_cli_dependency_with_directory(run_poetry, projects, is_windows):
diff --git a/tests/test_script_tasks.py b/tests/test_script_tasks.py
index 35f74c141..cbd9e3721 100644
--- a/tests/test_script_tasks.py
+++ b/tests/test_script_tasks.py
@@ -212,9 +212,11 @@ def test_script_task_bad_type(run_poe_subproc, projects):
"--greeting=hello",
)
assert (
- "Error: 'datetime' is not a valid type for arg 'greeting' of task 'bad-type'. "
- "Choose one of {boolean float integer string} \n" in result.capture
- )
+ "Error: Invalid argument 'greeting' declared in task 'bad-type'\n"
+ " | Option 'type' must be one of "
+ "('string', 'float', 'integer', 'boolean')\n"
+ ) in result.capture
+
assert result.stdout == ""
assert result.stderr == ""
@@ -226,9 +228,9 @@ def test_script_task_bad_content(run_poe_subproc, projects):
"--greeting=hello",
)
assert (
- "Error: Task 'bad-type' contains invalid callable reference "
- "'dummy_package:main[greeting]' "
- "(expected something like `module:callable` or `module:callable()`)"
+ "Error: Invalid task 'bad-type'\n"
+ " | Invalid callable reference 'dummy_package:main[greeting]'\n"
+ " | (expected something like `module:callable` or `module:callable()`)\n"
) in result.capture
assert result.stdout == ""
assert result.stderr == ""
diff --git a/tests/test_shell_task.py b/tests/test_shell_task.py
index 37fe6fa7a..8ab904568 100644
--- a/tests/test_shell_task.py
+++ b/tests/test_shell_task.py
@@ -97,9 +97,10 @@ def test_bad_interpreter_config(run_poe_subproc, projects):
"bad-interpreter",
)
assert (
- "Error: Unsupported value for option `interpreter` for task 'bad-interpreter'."
- " Expected one of ("
- "'posix', 'sh', 'bash', 'zsh', 'fish', 'pwsh', 'powershell', 'python')"
+ "Error: Invalid task 'bad-interpreter'\n"
+ " | Invalid value for option 'interpreter',\n"
+ " | Expected one of "
+ "('posix', 'sh', 'bash', 'zsh', 'fish', 'pwsh', 'powershell', 'python')\n"
) in result.capture
assert result.stdout == ""
assert result.stderr == ""
diff --git a/tests/test_switch_task.py b/tests/test_switch_task.py
index 6f0943fc9..10952afdc 100644
--- a/tests/test_switch_task.py
+++ b/tests/test_switch_task.py
@@ -70,8 +70,9 @@ def test_switch_default_pass(run_poe_subproc):
def test_switch_default_fail(run_poe_subproc):
result = run_poe_subproc("default_fail", project="switch")
assert result.capture == (
- "Poe <= poe_test_echo nothing\nError: Control value 'nothing' did not match "
- "any cases in switch task 'default_fail'. \n"
+ "Poe <= poe_test_echo nothing\n"
+ "Error: Control value 'nothing' did not match any cases in switch task "
+ "'default_fail'.\n"
)
assert result.stdout == ""
assert result.stderr == ""