From 3d19a2e675f03089dc67afbf89e3e4787658336a Mon Sep 17 00:00:00 2001 From: Thomas Phil Date: Tue, 6 May 2025 10:30:42 +0200 Subject: [PATCH 01/53] Enable Overriding of Hashables with allow_override=True This code modification enables hashables to be overwritten when the allow_override parameter is set to True. Previously, the library would crash if a hashable was overwritten. This change allows overrides, preventing the library from crashing in such scenarios. Thanks to @jjlangen for contributing to this change. --- src/inject/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index a9a54c6..f6292dc 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -182,7 +182,7 @@ def _check_class(self, cls: Binding) -> None: if self._is_forward_str(cls): ref = ForwardRef(cls) - if ref in self._bindings: + if not self.allow_override and ref in self._bindings: raise InjectorException('Duplicate forward binding, i.e. "int" and int, key=%s', cls) def _maybe_bind_forward(self, cls: Binding, binding: Any) -> None: From b14f45da1233d8b56dfe15ff6c5d0c5fead5933e Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Wed, 30 Jul 2025 21:53:35 +0400 Subject: [PATCH 02/53] Alias attr_dc and fix typings --- src/inject/__init__.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index ea3067a..02bb2c7 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -84,7 +84,7 @@ def my_config(binder): from functools import wraps from typing import (Any, Awaitable, Callable, Dict, Generic, Hashable, Optional, Set, Type, TypeVar, Union, cast, get_type_hints, - overload) + overload, no_type_check, Self) _HAS_PEP604_SUPPORT = sys.version_info[:3] >= (3, 10, 0) # PEP 604 if _HAS_PEP604_SUPPORT: @@ -277,15 +277,20 @@ def __call__(self) -> T: class _AttributeInjection(Generic[T]): - def __init__(self, cls: Type[T]) -> None: + def __init__(self, cls: Type[T] | Hashable) -> None: self._cls = cls - def __get__(self, obj: Any, owner: Type) -> T: + @overload + def __get__(self, obj: None, owner: Any) -> Self: ... + + @overload + def __get__(self, obj: Hashable, owner: Any) -> Injectable: ... + + def __get__(self, obj, owner): if obj is None: return self - inst = instance(self._cls) - return inst + return instance(self._cls) class _ParameterInjection(Generic[T]): @@ -487,11 +492,18 @@ def instance(cls: Binding) -> Injectable: @overload def attr(cls: Hashable) -> Injectable: ... -def attr(cls: Type[T]) -> T: +@overload +def attr(cls: Type[T]) -> T: ... + +def attr(cls): """Return an attribute injection (descriptor).""" return _AttributeInjection(cls) +# Deprecated, use `attr` +attr_dc = attr + + def param(name: str, cls: Optional[Binding] = None) -> Callable: """Deprecated, use @inject.params. Return a decorator which injects an arg into a function.""" return _ParameterInjection(name, cls) @@ -510,11 +522,13 @@ def sign_up(name, email, cache, db): @overload -def autoparams(fn: Callable[..., T]) -> Callable[..., T]: - ... +def autoparams[T](fn: Callable[..., T]) -> Callable[..., T]: ... +@overload +def autoparams[C: Callable](*selected: str) -> Callable[[C], C]: ... -def autoparams(*selected: str) -> Callable: +@no_type_check +def autoparams(*selected: str): """Return a decorator that will inject args into a function using type annotations, Python >= 3.5 only. For example:: From cf0c4ed98699fa734135d2d11c3ff45dc7eaf518 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Thu, 31 Jul 2025 18:55:37 +0400 Subject: [PATCH 03/53] Improve classifiers, urls and move build settings lower --- pyproject.toml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9603367..27fbd3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,3 @@ -[build-system] -requires = ['hatchling', 'hatch-vcs'] -build-backend = 'hatchling.build' - [project] name = 'inject' dynamic = ['version'] @@ -16,17 +12,26 @@ classifiers = [ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Software Development :: Libraries :: Python Modules', ] -dependencies = [] -[project.scripts] +requires-python = '>=3.9' +dependencies = [] [project.urls] -Homepage = 'https://github.com/ivankorobkov/python-inject' +homepage = 'https://github.com/ivankorobkov/python-inject' +source = 'https://github.com/ivankorobkov/python-inject' +issues = 'https://github.com/ivankorobkov/python-inject/issues' + +[build-system] +requires = ['hatchling', 'hatch-vcs'] +build-backend = 'hatchling.build' [tool.black] line-length = 120 From 6ca0c21cdb1723e7a9a69f37bd85a24fb0638e09 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Fri, 1 Aug 2025 00:59:23 +0400 Subject: [PATCH 04/53] Add dependency groups and base tox configuration --- pyproject.toml | 91 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 27fbd3c..b84bcb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,17 @@ homepage = 'https://github.com/ivankorobkov/python-inject' source = 'https://github.com/ivankorobkov/python-inject' issues = 'https://github.com/ivankorobkov/python-inject/issues' +[dependency-groups] +tests = [ + 'pytest', + 'pytest-cov', +] + +dev = [ + 'ipython', + { include-group = 'tests' }, +] + [build-system] requires = ['hatchling', 'hatch-vcs'] build-backend = 'hatchling.build' @@ -89,6 +100,8 @@ warn_redundant_casts = true warn_return_any = true warn_unused_configs = true warn_unused_ignores = true +#strict = true # TODO(pyctrl): improve typings and enable strict mode +exclude = ['test', '.venv'] [tool.pyright] defineConstant = { DEBUG = true } @@ -101,12 +114,6 @@ pythonVersion = '3.11' reportMissingImports = true reportMissingTypeStubs = false -[tool.pytest.ini_options] -addopts = '-rfEX --strict-markers --tb=long' -minversion = '7.2' -python_files = ['test_*.py'] -testpaths = ['./test'] - [tool.ruff] ignore = [ # Allow non-abstract empty methods in abstract base classes @@ -173,3 +180,75 @@ known-first-party = ['inject'] [tool.ruff.per-file-ignores] # Tests can use magic values, assertions, and relative imports '/**/test_*.py' = ['PLR2004', 'S101', 'TID252'] + + +#### Pytest & Coverage #### +[tool.pytest.ini_options] +minversion = '8.4' +addopts = '-vvv -ra --strict-markers --strict-config' +testpaths = ['test'] +#filterwarnings = ['error'] # TODO(pyctrl): handle all warnings later + +# Coverage configuration +[tool.coverage.run] +branch = true +[tool.coverage.report] +include_namespace_packages = true +# Regexes for lines to exclude from consideration +omit = ['*/.venv/*', '*/.tox/*', '*/.uv-cache/*'] +exclude_also = [ + # Don't complain if tests don't hit defensive assertion code: + 'raise NotImplementedError', + + # Don't complain if non-runnable code isn't run: + 'if __name__ == .__main__.:', + + # Don't complain about abstract methods, they aren't run: + '@(abc\\.)?abstractmethod', + + 'if TYPE_CHECKING:', +] + + +# Tox config +[tool.tox] +requires = ['tox>=4.23', 'tox-uv>=1.13'] +runner = 'uv-venv-lock-runner' +skip_missing_interpreters = true + +env_list = [ + 'py39', + 'py310', + 'py311', + 'py312', + 'py313', +# 'lint-mypy', # TODO(pyctrl): make it green & uncomment + 'coverage', +] + +# default env +[tool.tox.env_run_base] +description = 'Run unit tests with coverage report ({env_name})' +use_develop = true +dependency_groups = ['tests'] +commands = [['pytest', { replace = 'posargs', extend = true }]] + +# tox envs +[tool.tox.env.lint-mypy] +description = 'Type checking' +deps = ['mypy'] +commands = [['mypy', { replace = 'posargs', default = ['{tox_root}'], extend = true }]] + +[tool.tox.env.coverage] +description = 'run coverage' +use_develop = true +dependency_groups = ['tests'] +commands = [ + [ + 'pytest', + '--cov=.', + '--cov-branch', + '--cov-report=term-missing:skip-covered', + { replace = 'posargs', default = ['--cov-report=xml:coverage.xml'], extend = true }, + ], +] From a28e50042b2f7ce7441d8a2701963312fbf0dc90 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Sat, 16 Aug 2025 21:52:44 +0400 Subject: [PATCH 05/53] Make code 3.9-runnable --- src/inject/__init__.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 02bb2c7..f324873 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -73,6 +73,8 @@ def my_config(binder): inject.configure(my_config) """ +from __future__ import annotations + import contextlib from inject._version import __version__ @@ -84,7 +86,7 @@ def my_config(binder): from functools import wraps from typing import (Any, Awaitable, Callable, Dict, Generic, Hashable, Optional, Set, Type, TypeVar, Union, cast, get_type_hints, - overload, no_type_check, Self) + overload, no_type_check) _HAS_PEP604_SUPPORT = sys.version_info[:3] >= (3, 10, 0) # PEP 604 if _HAS_PEP604_SUPPORT: @@ -214,8 +216,12 @@ def __init__( else: self._bindings = {} + # NOTE(pyctrl): only since 3.12 + # @overload + # def get_instance(self, cls: Type[T]) -> T: ... + @overload - def get_instance(self, cls: Type[T]) -> T: ... + def get_instance(self, cls: Binding) -> T: ... @overload def get_instance(self, cls: Hashable) -> Injectable: ... @@ -521,11 +527,19 @@ def sign_up(name, email, cache, db): return _ParametersInjection(**args_to_classes) +# NOTE(pyctrl): only since 3.12 +# @overload +# def autoparams[T](fn: Callable[..., T]) -> Callable[..., T]: ... + @overload -def autoparams[T](fn: Callable[..., T]) -> Callable[..., T]: ... +def autoparams(fn: Callable) -> Callable: ... + +# NOTE(pyctrl): only since 3.12 +# @overload +# def autoparams[C: Callable](*selected: str) -> Callable[[C], C]: ... @overload -def autoparams[C: Callable](*selected: str) -> Callable[[C], C]: ... +def autoparams(*selected: str) -> Callable: ... @no_type_check def autoparams(*selected: str): From 57c94e6188bde8b5e63e215ce87ac24f818792bc Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Sun, 17 Aug 2025 01:14:41 +0400 Subject: [PATCH 06/53] Add config to run unit tests with GitHub Actions --- .github/workflows/tests.yaml | 70 ++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..440f225 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,70 @@ +--- +name: tests + +"on": + push: + pull_request: + +jobs: + +# misc: +# name: "Linting configs and git" +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# with: +# fetch-depth: 12 +# +# - uses: docker://docker.io/tamasfe/taplo:latest +# name: "Lint TOMLs" +# with: +# args: fmt --check --diff +# +# - uses: actions/setup-python@v5 +# - name: "Install tox" +# run: | +# pip install tox +# - name: "Lint YAMLs" +# run: | +# tox -e lint-yaml +# - name: "Lint git" +# run: | +# tox -e lint-git + +# lint: +# name: "Linting and type checking" +# runs-on: ubuntu-24.04 +# strategy: +# fail-fast: true +# steps: +# - uses: actions/checkout@v4 +# - uses: actions/setup-python@v5 +# with: +# python-version: "3.13" +# - name: "Install tox" +# run: | +# pip install tox +# - name: "Lint formatting" +# run: | +# tox -e lint-py +# - name: "Type checking" +# run: | +# tox -e lint-mypy + + tests: + name: "Run unit tests" + runs-on: ubuntu-24.04 + strategy: + fail-fast: true + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: "Installing tox" + run: pip install tox + - name: "Executing unit tests" + run: | + tox run -e ${{ matrix.python-version }} From b0fabaf0aeced54a1174f5994bc522a8a02e76de Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Mon, 18 Aug 2025 18:07:25 +0400 Subject: [PATCH 07/53] Change attr implementation to be a property --- src/inject/__init__.py | 53 +++++++++++++++++++++++++++++++----------- test/test_attr.py | 9 +++++++ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 02c862f..88db0d1 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -282,21 +282,48 @@ def __call__(self) -> T: return self._instance -class _AttributeInjection(Generic[T]): +# NOTE(pyctrl): we MUST inherit `_AttributeInjection` from `property` +# 0. (personal opinion, based on a bunch of cases including this one) +# dataclasses are mess +# 1. dataclasses treat all non-`property` descriptors by the very specific logic +# https://docs.python.org/3/library/dataclasses.html#descriptor-typed-fields +# 2. and treat `property` descriptors in a special way — like we used to know: +# ``` +# @dataclass +# class MyDataclass: +# @property +# def my_prop(self) -> int: +# return 42 +# MyDataclass.my_prop # gives '' on class +# MyDataclass().my_prop # and on instance will show you '42' +# ``` +# it behaves the same in the case of alternative notation: +# ``` +# @dataclass +# class MyDataclass2: +# my_prop = property(fget=lambda _: 42) +# MyDataclass2.my_prop # gives '' on class +# MyDataclass2().my_prop # and on instance will show you '42' +# ``` +# which is more relevant to the `inject.attr` case +# 3. but the behavior around `property`-ies has an exception +# - you can't annotate `property` attribute when using the second notation +# (this one `my_prop: int = property(fget=lambda _: 42)` will fail) +# - so the type hinting the very matters +# - in this case dataclasses don't treat class member as property +# (even if it's inherited from `property` or used directly) +# - dataclasses behave greedy when discover their attributes +# and class member annotations are "must have" markers +# 4. so for `inject.attr`s case we should follow 2 rules: +# - `attr` implementation is inherited from `property` +# - `attr` class member is not annotated +class _AttributeInjection(property): def __init__(self, cls: Type[T] | Hashable) -> None: self._cls = cls - - @overload - def __get__(self, obj: None, owner: Any) -> Self: ... - - @overload - def __get__(self, obj: Hashable, owner: Any) -> Injectable: ... - - def __get__(self, obj, owner): - if obj is None: - return self - - return instance(self._cls) + super().__init__( + fget=lambda _: instance(self._cls), + doc="Return an attribute injection", + ) class _ParameterInjection(Generic[T]): diff --git a/test/test_attr.py b/test/test_attr.py index 5fd8199..93cbbe3 100644 --- a/test/test_attr.py +++ b/test/test_attr.py @@ -11,6 +11,7 @@ class MyDataClass: class MyClass: field = inject.attr(int) + field2: int = inject.attr(int) inject.configure(lambda binder: binder.bind(int, 123)) my = MyClass() @@ -25,6 +26,14 @@ class MyClass: assert value2 == 123 assert value3 == 123 + def test_invalid_attachment_to_dataclass(self): + @dataclass + class MyDataClass: + # dataclasses treat this definition as regular descriptor + field: int = inject.attr(int) + + self.assertRaises(AttributeError, MyDataClass) + def test_class_attr(self): descriptor = inject.attr(int) From 29714bb258c1e876aa34569ce749c5f5127ec579 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Tue, 19 Aug 2025 01:30:31 +0400 Subject: [PATCH 08/53] Implement class member type auto-discovery for inject.attr --- src/inject/__init__.py | 21 ++++++++++++++++++++- test/test_attr.py | 38 +++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 88db0d1..8517a16 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -94,6 +94,7 @@ def my_config(binder): else: _HAS_PEP560_SUPPORT = sys.version_info[:3] >= (3, 7, 0) # PEP 560 _RETURN = 'return' +_MISSING = object() if _HAS_PEP604_SUPPORT: from types import UnionType @@ -325,6 +326,10 @@ def __init__(self, cls: Type[T] | Hashable) -> None: doc="Return an attribute injection", ) + def __set_name__(self, owner: Type[T], name: str) -> None: + if self._cls is _MISSING: + self._cls = _unwrap_cls_annotation(owner, name) + class _ParameterInjection(Generic[T]): __slots__ = ('_name', '_cls') @@ -522,13 +527,16 @@ def instance(cls: Binding) -> Injectable: """Inject an instance of a class.""" return get_injector_or_die().get_instance(cls) +@overload +def attr() -> Injectable: ... + @overload def attr(cls: Hashable) -> Injectable: ... @overload def attr(cls: Type[T]) -> T: ... -def attr(cls): +def attr(cls=_MISSING): """Return an attribute injection (descriptor).""" return _AttributeInjection(cls) @@ -653,3 +661,14 @@ def _is_union_type(typ): return (typ is Union or isinstance(typ, _GenericAlias) and typ.__origin__ is Union) return type(typ) is _Union + + +def _unwrap_cls_annotation(cls: Type, attr_name: str): + types = get_type_hints(cls) + try: + attr_type = types[attr_name] + except KeyError: + msg = f"Couldn't find type annotation for {attr_name}" + raise InjectorException(msg) + + return _unwrap_union_arg(attr_type) diff --git a/test/test_attr.py b/test/test_attr.py index 93cbbe3..6f4fb53 100644 --- a/test/test_attr.py +++ b/test/test_attr.py @@ -12,19 +12,20 @@ class MyDataClass: class MyClass: field = inject.attr(int) field2: int = inject.attr(int) + auto_typed_field: int = inject.attr() inject.configure(lambda binder: binder.bind(int, 123)) my = MyClass() my_dc = MyDataClass() - value0 = my.field - value1 = my.field - value2 = my_dc.field - value3 = my_dc.field - assert value0 == 123 - assert value1 == 123 - assert value2 == 123 - assert value3 == 123 + assert my.field == 123 + assert my.field == 123 + assert my.field2 == 123 + assert my.field2 == 123 + assert my.auto_typed_field == 123 + assert my.auto_typed_field == 123 + assert my_dc.field == 123 + assert my_dc.field == 123 def test_invalid_attachment_to_dataclass(self): @dataclass @@ -36,6 +37,7 @@ class MyDataClass: def test_class_attr(self): descriptor = inject.attr(int) + auto_descriptor = inject.attr() @dataclass class MyDataClass: @@ -43,14 +45,16 @@ class MyDataClass: class MyClass(object): field = descriptor + field2: int = descriptor + auto_typed_field: int = auto_descriptor inject.configure(lambda binder: binder.bind(int, 123)) - value0 = MyClass.field - value1 = MyClass.field - value2 = MyDataClass.field - value3 = MyDataClass.field - - assert value0 is descriptor - assert value1 is descriptor - assert value2 is descriptor - assert value3 is descriptor + + assert MyClass.field is descriptor + assert MyClass.field is descriptor + assert MyClass.field2 is descriptor + assert MyClass.field2 is descriptor + assert MyClass.auto_typed_field is auto_descriptor + assert MyClass.auto_typed_field is auto_descriptor + assert MyDataClass.field is descriptor + assert MyDataClass.field is descriptor From 55de2bbf7fa21310ce2b63480a23f269c96d6825 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Sat, 16 Aug 2025 22:38:25 +0400 Subject: [PATCH 09/53] Add TOML linting and format toml-files according to linter --- .taplo.toml | 8 ++ pyproject.toml | 266 +++++++++++++++++++++++++++---------------------- 2 files changed, 155 insertions(+), 119 deletions(-) create mode 100644 .taplo.toml diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 0000000..27870a4 --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,8 @@ +exclude = [ + "**/.venv/**/*.toml", + "**/.tox/**/*.toml", +] + +[formatting] +column_width = 88 +array_auto_collapse = false diff --git a/pyproject.toml b/pyproject.toml index b84bcb4..9b7c20b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,53 +1,53 @@ [project] -name = 'inject' -dynamic = ['version'] -description = 'Python dependency injection framework.' -license = 'Apache-2.0' -readme = 'README.md' -authors = [{ name = 'Ivan Korobkov', email = 'ivan.korobkov@gmail.com' }] -maintainers = [{ name = 'Ivan Korobkov', email = 'ivan.korobkov@gmail.com' }] +name = "inject" +dynamic = ["version"] +description = "Python dependency injection framework." +license = "Apache-2.0" +readme = "README.md" +authors = [{ name = "Ivan Korobkov", email = "ivan.korobkov@gmail.com" }] +maintainers = [{ name = "Ivan Korobkov", email = "ivan.korobkov@gmail.com" }] classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = '>=3.9' +requires-python = ">=3.9" dependencies = [] [project.urls] -homepage = 'https://github.com/ivankorobkov/python-inject' -source = 'https://github.com/ivankorobkov/python-inject' -issues = 'https://github.com/ivankorobkov/python-inject/issues' +homepage = "https://github.com/ivankorobkov/python-inject" +source = "https://github.com/ivankorobkov/python-inject" +issues = "https://github.com/ivankorobkov/python-inject/issues" [dependency-groups] tests = [ - 'pytest', - 'pytest-cov', + "pytest", + "pytest-cov", ] dev = [ - 'ipython', - { include-group = 'tests' }, + "ipython", + { include-group = "tests" }, ] [build-system] -requires = ['hatchling', 'hatch-vcs'] -build-backend = 'hatchling.build' +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" [tool.black] line-length = 120 skip-string-normalization = true -target_version = ['py39', 'py310', 'py311'] +target_version = ["py39", "py310", "py311"] include = '\.pyi?$' exclude = ''' /( @@ -68,22 +68,22 @@ exclude = ''' ''' [tool.hatch.build.hooks.vcs] -version-file = 'src/inject/_version.py' +version-file = "src/inject/_version.py" [tool.hatch.build.targets.wheel] -packages = ['src/inject'] +packages = ["src/inject"] [tool.hatch.build.targets.wheel.shared-data] [tool.hatch.version] -source = 'vcs' +source = "vcs" [tool.isort] case_sensitive = true include_trailing_comma = true line_length = 120 multi_line_output = 3 -profile = 'black' +profile = "black" [tool.mypy] check_untyped_defs = true @@ -95,99 +95,96 @@ disallow_untyped_decorators = true disallow_untyped_defs = true ignore_missing_imports = true no_implicit_optional = true -python_version = '3.11' +python_version = "3.11" warn_redundant_casts = true warn_return_any = true warn_unused_configs = true warn_unused_ignores = true #strict = true # TODO(pyctrl): improve typings and enable strict mode -exclude = ['test', '.venv'] +exclude = ["test", ".venv"] [tool.pyright] defineConstant = { DEBUG = true } exclude = [] executionEnvironments = [] ignore = [] -include = ['src/inject', 'test'] -pythonPlatform = 'Linux' -pythonVersion = '3.11' +include = ["src/inject", "test"] +pythonPlatform = "Linux" +pythonVersion = "3.11" reportMissingImports = true reportMissingTypeStubs = false [tool.ruff] ignore = [ - # Allow non-abstract empty methods in abstract base classes - 'B027', - # Allow boolean positional values in function calls, like `dict.get(... True)` - 'FBT003', - # Ignore checks for possible passwords - 'S105', - 'S106', - 'S107', - # Ignore complexity - 'C901', - 'PLR0911', - 'PLR0912', - 'PLR0913', - 'PLR0915', - 'PLC1901', # empty string comparisons - 'PLW2901', # `for` loop variable overwritten - 'SIM114', # Combine `if` branches using logical `or` operator + "B027", # Allow non-abstract empty methods in abstract base classes + "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)` + # Ignore checks for possible passwords + "S105", + "S106", + "S107", + # Ignore complexity + "C901", + "PLR0911", + "PLR0912", + "PLR0913", + "PLR0915", + "PLC1901", # empty string comparisons + "PLW2901", # `for` loop variable overwritten + "SIM114", # Combine `if` branches using logical `or` operator ] line-length = 120 select = [ - 'A', - 'B', - 'C', - 'DTZ', - 'E', - 'EM', - 'F', - 'FBT', - 'I', - 'ICN', - 'ISC', - 'N', - 'PLC', - 'PLE', - 'PLR', - 'PLW', - 'Q', - 'RUF', - 'S', - 'SIM', - 'T', - 'TID', - 'UP', - 'W', - 'YTT', + "A", + "B", + "C", + "DTZ", + "E", + "EM", + "F", + "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "SIM", + "T", + "TID", + "UP", + "W", + "YTT", ] -target-version = ['py39', 'py310', 'py311'] +target-version = ["py39", "py310", "py311"] unfixable = [ - # Don't touch unused imports - 'F401', + "F401", # Don't touch unused imports ] [tool.ruff.flake8-quotes] -inline-quotes = 'single' +inline-quotes = "single" [tool.ruff.flake8-tidy-imports] -ban-relative-imports = 'all' +ban-relative-imports = "all" [tool.ruff.isort] -known-first-party = ['inject'] +known-first-party = ["inject"] [tool.ruff.per-file-ignores] # Tests can use magic values, assertions, and relative imports -'/**/test_*.py' = ['PLR2004', 'S101', 'TID252'] +"/**/test_*.py" = ["PLR2004", "S101", "TID252"] #### Pytest & Coverage #### [tool.pytest.ini_options] -minversion = '8.4' -addopts = '-vvv -ra --strict-markers --strict-config' -testpaths = ['test'] -#filterwarnings = ['error'] # TODO(pyctrl): handle all warnings later +minversion = "8.4" +addopts = "-vvv -ra --strict-markers --strict-config" +testpaths = ["test"] +#filterwarnings = ["error"] # TODO(pyctrl): handle all warnings later # Coverage configuration [tool.coverage.run] @@ -195,60 +192,91 @@ branch = true [tool.coverage.report] include_namespace_packages = true # Regexes for lines to exclude from consideration -omit = ['*/.venv/*', '*/.tox/*', '*/.uv-cache/*'] +omit = ["*/.venv/*", "*/.tox/*", "*/.uv-cache/*"] exclude_also = [ # Don't complain if tests don't hit defensive assertion code: - 'raise NotImplementedError', + "raise NotImplementedError", # Don't complain if non-runnable code isn't run: - 'if __name__ == .__main__.:', + "if __name__ == .__main__.:", # Don't complain about abstract methods, they aren't run: - '@(abc\\.)?abstractmethod', + "@(abc\\.)?abstractmethod", - 'if TYPE_CHECKING:', + "if TYPE_CHECKING:", ] # Tox config [tool.tox] -requires = ['tox>=4.23', 'tox-uv>=1.13'] -runner = 'uv-venv-lock-runner' +requires = ["tox>=4.23", "tox-uv>=1.13"] +runner = "uv-venv-lock-runner" skip_missing_interpreters = true env_list = [ - 'py39', - 'py310', - 'py311', - 'py312', - 'py313', -# 'lint-mypy', # TODO(pyctrl): make it green & uncomment - 'coverage', + "py39", + "py310", + "py311", + "py312", + "py313", + "fmt-toml", + #"lint-mypy", # TODO(pyctrl): make it green & uncomment + "lint-toml", + #"lint-yaml", + "coverage", +] + +[tool.tox.labels] +fmt = [ + # "fmt-py", + "fmt-toml", +] +lint = [ + #"lint-py", + #"lint-mypy", # TODO(pyctrl): make it green & uncomment + "lint-toml", + #"lint-yaml", + #"lint-git", ] # default env [tool.tox.env_run_base] -description = 'Run unit tests with coverage report ({env_name})' +description = "Run unit tests with coverage report ({env_name})" use_develop = true -dependency_groups = ['tests'] -commands = [['pytest', { replace = 'posargs', extend = true }]] +dependency_groups = ["tests"] +commands = [["pytest", { replace = "posargs", extend = true }]] # tox envs [tool.tox.env.lint-mypy] -description = 'Type checking' -deps = ['mypy'] -commands = [['mypy', { replace = 'posargs', default = ['{tox_root}'], extend = true }]] +description = "Type checking" +deps = ["mypy"] +commands = [["mypy", { replace = "posargs", default = ["{tox_root}"], extend = true }]] + +[tool.tox.env.lint-toml] +description = "Lint TOML files" +allowlist_externals = ["taplo"] +skip_install = true +commands = [ + ["taplo", "lint", { replace = "posargs", extend = true }], + ["taplo", "format", "--check", "--diff", { replace = "posargs", extend = true }], +] + +[tool.tox.env.fmt-toml] +description = "Format TOML files" +allowlist_externals = ["taplo"] +skip_install = true +commands = [["taplo", "format", { replace = "posargs", extend = true }]] [tool.tox.env.coverage] -description = 'run coverage' +description = "run coverage" use_develop = true -dependency_groups = ['tests'] +dependency_groups = ["tests"] commands = [ [ - 'pytest', - '--cov=.', - '--cov-branch', - '--cov-report=term-missing:skip-covered', - { replace = 'posargs', default = ['--cov-report=xml:coverage.xml'], extend = true }, + "pytest", + "--cov=.", + "--cov-branch", + "--cov-report=term-missing:skip-covered", + { replace = "posargs", default = ["--cov-report=xml:coverage.xml"], extend = true }, ], ] From da6933ab23aad2f80c0f11bc6e8c9595343e3ee8 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Sun, 17 Aug 2025 05:21:31 +0400 Subject: [PATCH 10/53] Add YAML linting --- .travis.yml | 9 +++++---- .yamllint.yaml | 18 ++++++++++++++++++ pyproject.toml | 18 ++++++++++++++++-- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 .yamllint.yaml diff --git a/.travis.yml b/.travis.yml index 7de3f9b..b46d518 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,15 @@ +--- dist: xenial language: python matrix: include: - - python: '3.6' - - python: '3.7' + - python: "3.6" + - python: "3.7" env: TOXENV=py37 - - python: '3.8' + - python: "3.8" env: TOXENV=py37 install: - python -m pip install --upgrade --editable=./ -script: +script: - nosetests test $EXTRA_ARGS - if [ "$TOXENV" = "py37" ]; then nosetests test37; fi diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..d136089 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,18 @@ +--- +extends: default + +ignore: + - "**/.venv/*" + - "**/.tox/*" + +rules: + comments: + require-starting-space: true + min-spaces-from-content: 1 + comments-indentation: disable + quoted-strings: + quote-type: double + required: false + check-keys: true + line-length: + max: 88 diff --git a/pyproject.toml b/pyproject.toml index 9b7c20b..43682c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,7 +222,7 @@ env_list = [ "fmt-toml", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", - #"lint-yaml", + "lint-yaml", "coverage", ] @@ -235,7 +235,7 @@ lint = [ #"lint-py", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", - #"lint-yaml", + "lint-yaml", #"lint-git", ] @@ -261,6 +261,20 @@ commands = [ ["taplo", "format", "--check", "--diff", { replace = "posargs", extend = true }], ] +[tool.tox.env.lint-yaml] +description = "Lint YAML files" +deps = ["yamllint"] +skip_install = true +commands = [ + [ + "yamllint", + "--strict", + { replace = "posargs", default = [ + "{tox_root}", + ], extend = true }, + ], +] + [tool.tox.env.fmt-toml] description = "Format TOML files" allowlist_externals = ["taplo"] From 48c898bb1a9675ee1cfa29ecda29695d3264a904 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Tue, 19 Aug 2025 22:20:42 +0400 Subject: [PATCH 11/53] Lint configs with GitHub Actions --- .github/workflows/tests.yaml | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 440f225..e6e661e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,26 +7,27 @@ name: tests jobs: -# misc: + misc: # name: "Linting configs and git" -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# with: -# fetch-depth: 12 -# -# - uses: docker://docker.io/tamasfe/taplo:latest -# name: "Lint TOMLs" -# with: -# args: fmt --check --diff -# -# - uses: actions/setup-python@v5 -# - name: "Install tox" -# run: | -# pip install tox -# - name: "Lint YAMLs" -# run: | -# tox -e lint-yaml + name: "Linting configs" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 12 + + - uses: docker://docker.io/tamasfe/taplo:latest + name: "Lint TOMLs" + with: + args: fmt --check --diff + + - uses: actions/setup-python@v5 + - name: "Install tox" + run: | + pip install tox + - name: "Lint YAMLs" + run: | + tox -e lint-yaml # - name: "Lint git" # run: | # tox -e lint-git From 41f2ea6381fe59e338e983d5cdfa2fb2905e202e Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Sat, 16 Aug 2025 23:01:30 +0400 Subject: [PATCH 12/53] Rename tests directory --- makefile | 4 ++-- pyproject.toml | 6 +++--- {test => tests}/__init__.py | 0 {test => tests}/test_attr.py | 2 +- {test => tests}/test_autoparams.py | 2 +- {test => tests}/test_binder.py | 0 {test => tests}/test_context_manager.py | 2 +- {test => tests}/test_functional.py | 0 {test => tests}/test_inject_configuration.py | 2 +- {test => tests}/test_injector.py | 0 {test => tests}/test_instance.py | 2 +- {test => tests}/test_param.py | 2 +- {test => tests}/test_params.py | 2 +- 13 files changed, 12 insertions(+), 12 deletions(-) rename {test => tests}/__init__.py (100%) rename {test => tests}/test_attr.py (98%) rename {test => tests}/test_autoparams.py (99%) rename {test => tests}/test_binder.py (100%) rename {test => tests}/test_context_manager.py (98%) rename {test => tests}/test_functional.py (100%) rename {test => tests}/test_inject_configuration.py (98%) rename {test => tests}/test_injector.py (100%) rename {test => tests}/test_instance.py (87%) rename {test => tests}/test_param.py (96%) rename {test => tests}/test_params.py (99%) diff --git a/makefile b/makefile index 12e5f02..bcba55f 100644 --- a/makefile +++ b/makefile @@ -26,8 +26,8 @@ clean: pytest: if ! command -v pytest &>/dev/null; then python3 -m pip install --upgrade pytest; fi - pytest test + pytest tests test: if ! command -v nosetests &>/dev/null; then python3 -m pip install --upgrade nose; fi - nosetests test + nosetests tests diff --git a/pyproject.toml b/pyproject.toml index 43682c3..2433b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,14 +101,14 @@ warn_return_any = true warn_unused_configs = true warn_unused_ignores = true #strict = true # TODO(pyctrl): improve typings and enable strict mode -exclude = ["test", ".venv"] +exclude = ["tests", ".venv"] [tool.pyright] defineConstant = { DEBUG = true } exclude = [] executionEnvironments = [] ignore = [] -include = ["src/inject", "test"] +include = ["src/inject", "tests"] pythonPlatform = "Linux" pythonVersion = "3.11" reportMissingImports = true @@ -183,7 +183,7 @@ known-first-party = ["inject"] [tool.pytest.ini_options] minversion = "8.4" addopts = "-vvv -ra --strict-markers --strict-config" -testpaths = ["test"] +testpaths = ["tests"] #filterwarnings = ["error"] # TODO(pyctrl): handle all warnings later # Coverage configuration diff --git a/test/__init__.py b/tests/__init__.py similarity index 100% rename from test/__init__.py rename to tests/__init__.py diff --git a/test/test_attr.py b/tests/test_attr.py similarity index 98% rename from test/test_attr.py rename to tests/test_attr.py index 6f4fb53..5411b30 100644 --- a/test/test_attr.py +++ b/tests/test_attr.py @@ -1,6 +1,6 @@ from dataclasses import dataclass import inject -from test import BaseTestInject +from tests import BaseTestInject class TestInjectAttr(BaseTestInject): diff --git a/test/test_autoparams.py b/tests/test_autoparams.py similarity index 99% rename from test/test_autoparams.py rename to tests/test_autoparams.py index 6068e33..a3239c9 100644 --- a/test/test_autoparams.py +++ b/tests/test_autoparams.py @@ -1,7 +1,7 @@ import sys from typing import Optional -from test import BaseTestInject +from tests import BaseTestInject import inject diff --git a/test/test_binder.py b/tests/test_binder.py similarity index 100% rename from test/test_binder.py rename to tests/test_binder.py diff --git a/test/test_context_manager.py b/tests/test_context_manager.py similarity index 98% rename from test/test_context_manager.py rename to tests/test_context_manager.py index 652a8ad..058bf52 100644 --- a/test/test_context_manager.py +++ b/tests/test_context_manager.py @@ -1,7 +1,7 @@ import contextlib import inject -from test import BaseTestInject +from tests import BaseTestInject class Destroyable: diff --git a/test/test_functional.py b/tests/test_functional.py similarity index 100% rename from test/test_functional.py rename to tests/test_functional.py diff --git a/test/test_inject_configuration.py b/tests/test_inject_configuration.py similarity index 98% rename from test/test_inject_configuration.py rename to tests/test_inject_configuration.py index 447b8d5..7ea10f1 100644 --- a/test/test_inject_configuration.py +++ b/tests/test_inject_configuration.py @@ -1,6 +1,6 @@ import inject from inject import InjectorException -from test import BaseTestInject +from tests import BaseTestInject class TestInjectConfiguration(BaseTestInject): diff --git a/test/test_injector.py b/tests/test_injector.py similarity index 100% rename from test/test_injector.py rename to tests/test_injector.py diff --git a/test/test_instance.py b/tests/test_instance.py similarity index 87% rename from test/test_instance.py rename to tests/test_instance.py index 27fcb61..fcfb36b 100644 --- a/test/test_instance.py +++ b/tests/test_instance.py @@ -1,5 +1,5 @@ import inject -from test import BaseTestInject +from tests import BaseTestInject class TestInjectInstance(BaseTestInject): diff --git a/test/test_param.py b/tests/test_param.py similarity index 96% rename from test/test_param.py rename to tests/test_param.py index a21bde4..29767a7 100644 --- a/test/test_param.py +++ b/tests/test_param.py @@ -1,5 +1,5 @@ import inject -from test import BaseTestInject +from tests import BaseTestInject import inspect import asyncio diff --git a/test/test_params.py b/tests/test_params.py similarity index 99% rename from test/test_params.py rename to tests/test_params.py index fc2e8dc..b6ff9de 100644 --- a/test/test_params.py +++ b/tests/test_params.py @@ -1,5 +1,5 @@ import inject -from test import BaseTestInject +from tests import BaseTestInject import inspect import asyncio From 87c6496d480baae09c2e9c12f1497e8c4f944c38 Mon Sep 17 00:00:00 2001 From: Dima Burmistrov Date: Wed, 20 Aug 2025 23:00:28 +0400 Subject: [PATCH 13/53] Enable ruff linting and formatting, formatted and tuned code to satisfy ruff --- .github/workflows/tests.yaml | 27 +- pyproject.toml | 261 +++++++++++----- src/inject/__init__.py | 472 +++++++++++++++++------------ tests/__init__.py | 12 +- tests/test_attr.py | 15 +- tests/test_autoparams.py | 81 ++--- tests/test_binder.py | 50 ++- tests/test_context_manager.py | 24 +- tests/test_functional.py | 53 ++-- tests/test_inject_configuration.py | 20 +- tests/test_injector.py | 41 ++- tests/test_param.py | 21 +- tests/test_params.py | 69 ++--- 13 files changed, 696 insertions(+), 450 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e6e661e..04e06d2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -32,22 +32,23 @@ jobs: # run: | # tox -e lint-git -# lint: + lint: # name: "Linting and type checking" -# runs-on: ubuntu-24.04 + name: "Linting" + runs-on: ubuntu-24.04 # strategy: # fail-fast: true -# steps: -# - uses: actions/checkout@v4 -# - uses: actions/setup-python@v5 -# with: -# python-version: "3.13" -# - name: "Install tox" -# run: | -# pip install tox -# - name: "Lint formatting" -# run: | -# tox -e lint-py + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: "Install tox" + run: | + pip install tox + - name: "Lint formatting" + run: | + tox -e lint-py # - name: "Type checking" # run: | # tox -e lint-mypy diff --git a/pyproject.toml b/pyproject.toml index 2433b81..bc872a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,43 +29,26 @@ homepage = "https://github.com/ivankorobkov/python-inject" source = "https://github.com/ivankorobkov/python-inject" issues = "https://github.com/ivankorobkov/python-inject/issues" + [dependency-groups] +dev = [ + "ipython", + "ruff", + "mypy", + "yamllint", + { include-group = "tests" }, +] + tests = [ "pytest", "pytest-cov", ] -dev = [ - "ipython", - { include-group = "tests" }, -] [build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" -[tool.black] -line-length = 120 -skip-string-normalization = true -target_version = ["py39", "py310", "py311"] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.github - | \.hg - | \.idea - | \.mypy_cache - | \.tox - | \.pyre_configuration - | \.venv - | _build - | build - | dist - | var -) -''' [tool.hatch.build.hooks.vcs] version-file = "src/inject/_version.py" @@ -78,6 +61,7 @@ packages = ["src/inject"] [tool.hatch.version] source = "vcs" + [tool.isort] case_sensitive = true include_trailing_comma = true @@ -85,6 +69,7 @@ line_length = 120 multi_line_output = 3 profile = "black" + [tool.mypy] check_untyped_defs = true disallow_any_generics = true @@ -103,6 +88,8 @@ warn_unused_ignores = true #strict = true # TODO(pyctrl): improve typings and enable strict mode exclude = ["tests", ".venv"] + +# TODO(pyctrl): deprecate pyright — stay with mypy [tool.pyright] defineConstant = { DEBUG = true } exclude = [] @@ -114,69 +101,133 @@ pythonVersion = "3.11" reportMissingImports = true reportMissingTypeStubs = false + [tool.ruff] -ignore = [ - "B027", # Allow non-abstract empty methods in abstract base classes - "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)` - # Ignore checks for possible passwords - "S105", - "S106", - "S107", - # Ignore complexity - "C901", - "PLR0911", - "PLR0912", - "PLR0913", - "PLR0915", - "PLC1901", # empty string comparisons - "PLW2901", # `for` loop variable overwritten - "SIM114", # Combine `if` branches using logical `or` operator +line-length = 88 +extend-exclude = [".git", ".venv", "docs"] + +[tool.ruff.lint] +preview = true +extend-select = ["ALL"] +extend-ignore = [ + "D10", # missing documentation + "D203", # 1 of conflicting code-styles + "D212", # 1 of conflicting code-styles + "C408", # allow `dict()` instead of literal + "TD003", # don't require issue link + # Completely disable + "FIX", + "CPY", + # formatter conflict rules + "W191", + "E111", + "E114", + "E117", + "EM101", + "EM102", + "ERA001", # commented code + "D206", + "D300", + "DOC201", + "DOC402", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", + "N818", # Exception name should be named with an Error suffix + "TRY003", + "UP006", + "UP007", + "UP035", + "UP045", + "FA100", ] -line-length = 120 -select = [ - "A", - "B", - "C", - "DTZ", - "E", - "EM", - "F", - "FBT", - "I", - "ICN", - "ISC", - "N", - "PLC", - "PLE", - "PLR", - "PLW", - "Q", - "RUF", - "S", - "SIM", - "T", - "TID", - "UP", - "W", - "YTT", -] -target-version = ["py39", "py310", "py311"] -unfixable = [ - "F401", # Don't touch unused imports + +[tool.ruff.lint.extend-per-file-ignores] +"**/tests/**/test_*.py" = [ + "ANN", # annotations not required in tests + "E731", # Do not assign a `lambda` expression, use a `def` + "PLC2701", # Private name import + "PLR0913", # Too many arguments in function definition + "PLR0917", # Too many positional arguments + "PLR2004", # allow "magic" values in tests + "PLR6301", # Method could be a function, class method, or static method + "PT017", # Found assertion on exception `except` block, use `pytest.raises()` instead + "PT027", # Use `pytest.raises` instead of unittest-style `assertRaisesRegex` + "S101", # allow asserts + "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes + "SLF001", # allow private member access ] +"/**/tests/__init__.py" = ["PLR6301"] -[tool.ruff.flake8-quotes] -inline-quotes = "single" +[tool.ruff.lint.flake8-import-conventions.extend-aliases] +"typing" = "t" -[tool.ruff.flake8-tidy-imports] -ban-relative-imports = "all" +[tool.ruff.lint.flake8-import-conventions] +banned-from = ["dataclasses", "inject", "typing"] -[tool.ruff.isort] +[tool.ruff.lint.isort] +force-single-line = true known-first-party = ["inject"] -[tool.ruff.per-file-ignores] -# Tests can use magic values, assertions, and relative imports -"/**/test_*.py" = ["PLR2004", "S101", "TID252"] +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +#ignore = [ +# "B027", # Allow non-abstract empty methods in abstract base classes +# "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)` +# # Ignore checks for possible passwords +# "S105", +# "S106", +# "S107", +# # Ignore complexity +# "C901", +# "PLR0911", +# "PLR0912", +# "PLR0913", +# "PLR0915", +# "PLC1901", # empty string comparisons +# "PLW2901", # `for` loop variable overwritten +# "SIM114", # Combine `if` branches using logical `or` operator +#] +#select = [ +# "A", +# "B", +# "C", +# "DTZ", +# "E", +# "EM", +# "F", +# "FBT", +# "I", +# "ICN", +# "ISC", +# "N", +# "PLC", +# "PLE", +# "PLR", +# "PLW", +# "Q", +# "RUF", +# "S", +# "SIM", +# "T", +# "TID", +# "UP", +# "W", +# "YTT", +#] +#unfixable = [ +# "F401", # Don't touch unused imports +#] #### Pytest & Coverage #### @@ -219,20 +270,23 @@ env_list = [ "py311", "py312", "py313", + "fmt-py", "fmt-toml", + "lint-py", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", + #"lint-git", "coverage", ] [tool.tox.labels] fmt = [ - # "fmt-py", + "fmt-py", "fmt-toml", ] lint = [ - #"lint-py", + "lint-py", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", @@ -247,6 +301,28 @@ dependency_groups = ["tests"] commands = [["pytest", { replace = "posargs", extend = true }]] # tox envs +[tool.tox.env.lint-py] +description = "Lint python files" +deps = ["ruff"] +skip_install = true +commands = [ + [ + "ruff", + "format", + "--diff", + { replace = "posargs", default = [ + "{tox_root}", + ], extend = true }, + ], + [ + "ruff", + "check", + { replace = "posargs", default = [ + "{tox_root}", + ], extend = true }, + ], +] + [tool.tox.env.lint-mypy] description = "Type checking" deps = ["mypy"] @@ -275,6 +351,25 @@ commands = [ ], ] +[tool.tox.env.fmt-py] +description = "Format python files" +deps = ["ruff"] +skip_install = true +commands = [ + [ + "ruff", + "format", + { replace = "posargs", default = ["{tox_root}"], extend = true }, + ], + [ + "ruff", + "check", + "--fix", + "--show-fixes", + { replace = "posargs", default = ["{tox_root}"], extend = true }, + ], +] + [tool.tox.env.fmt-toml] description = "Format TOML files" allowlist_externals = ["taplo"] diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 8517a16..851f310 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -72,163 +72,186 @@ def my_config(binder): inject.configure(my_config) -""" +""" # noqa: E501 + from __future__ import annotations import contextlib - -from inject._version import __version__ - +import functools import inspect import logging import sys import threading -from functools import wraps -from typing import (Any, Awaitable, Callable, Dict, Generic, Hashable, - Optional, Set, Type, TypeVar, Union, cast, get_type_hints, - overload, no_type_check) +import typing as t -_HAS_PEP604_SUPPORT = sys.version_info[:3] >= (3, 10, 0) # PEP 604 -if _HAS_PEP604_SUPPORT: - _HAS_PEP560_SUPPORT = True -else: - _HAS_PEP560_SUPPORT = sys.version_info[:3] >= (3, 7, 0) # PEP 560 -_RETURN = 'return' +from inject._version import __version__ as __version__ + +# PEP 604 +_HAS_PEP604_SUPPORT = sys.version_info[:3] >= (3, 10, 0) +# PEP 560 +_HAS_PEP560_SUPPORT = _HAS_PEP604_SUPPORT or sys.version_info[:3] >= (3, 7, 0) + +_RETURN = "return" _MISSING = object() if _HAS_PEP604_SUPPORT: from types import UnionType - from typing import ForwardRef, _GenericAlias + from typing import _GenericAlias # noqa: ICN003 elif _HAS_PEP560_SUPPORT: - from typing import ForwardRef, _GenericAlias + from typing import _GenericAlias # noqa: ICN003, PLC2701 else: - from typing import _Union + from typing import _Union # noqa: ICN003, PLC2701 -logger = logging.getLogger('inject') +logger = logging.getLogger("inject") _INJECTOR = None # Shared injector instance. _INJECTOR_LOCK = threading.RLock() # Guards injector initialization. _BINDING_LOCK = threading.RLock() # Guards runtime bindings. -Injectable = Union[object, Any] -T = TypeVar('T', bound=Injectable) -Binding = Union[Type[Injectable], Hashable] -Constructor = Callable[[], Injectable] +Injectable = t.Union[object, t.Any] +T = t.TypeVar("T", bound=Injectable) +Binding = t.Union[type[Injectable], t.Hashable] +Constructor = t.Callable[[], Injectable] Provider = Constructor -BinderCallable = Callable[['Binder'], Optional['Binder']] +BinderCallable = t.Callable[["Binder"], t.Optional["Binder"]] class ConstructorTypeError(TypeError): - def __init__(self, constructor: Callable, previous_error: TypeError): - super(ConstructorTypeError, self).__init__("%s raised an error: %s" % (constructor, previous_error)) + def __init__(self, constructor: t.Callable, previous_error: TypeError) -> None: + super().__init__(f"{constructor} raised an error: {previous_error}") -class Binder(object): - _bindings: Dict[Binding, Constructor] +class Binder: + _bindings: dict[Binding, Constructor] - def __init__(self, allow_override: bool = False) -> None: + def __init__(self, allow_override: bool = False) -> None: # noqa: FBT001, FBT002 self._bindings = {} self.allow_override = allow_override - def install(self, config: BinderCallable) -> 'Binder': + def install(self, config: BinderCallable) -> Binder: """Install another callable configuration.""" config(self) return self - def bind(self, cls: Binding, instance: T) -> 'Binder': + def bind(self, cls: Binding, instance: T) -> Binder: """Bind a class to an instance.""" self._check_class(cls) - b = lambda: instance + b = lambda: instance # noqa: E731 self._bindings[cls] = b self._maybe_bind_forward(cls, b) - logger.debug('Bound %s to an instance %s', cls, instance) + logger.debug("Bound %s to an instance %s", cls, instance) return self - def bind_to_constructor(self, cls: Binding, constructor: Constructor) -> 'Binder': - """Bind a class to a callable singleton constructor.""" + def bind_to_constructor(self, cls: Binding, constructor: Constructor) -> Binder: + """ + Bind a class to a callable singleton constructor. + + Raises: + InjectorException: if no constructor + + """ self._check_class(cls) if constructor is None: - raise InjectorException('Constructor cannot be None, key=%s' % cls) - + raise InjectorException(f"Constructor cannot be None, key={cls}") + b = _ConstructorBinding(constructor) self._bindings[cls] = b self._maybe_bind_forward(cls, b) - logger.debug('Bound %s to a constructor %s', cls, constructor) + logger.debug("Bound %s to a constructor %s", cls, constructor) return self - def bind_to_provider(self, cls: Binding, provider: Provider) -> 'Binder': + def bind_to_provider(self, cls: Binding, provider: Provider) -> Binder: """ Bind a class to a callable instance provider executed for each injection. - A provider can be a normal function or a context manager. Both sync and async are supported. + + A provider can be a normal function or a context manager. + Both sync and async are supported. + + Raises: + InjectorException: if no provider + """ self._check_class(cls) if provider is None: - raise InjectorException('Provider cannot be None, key=%s' % cls) + raise InjectorException(f"Provider cannot be None, key={cls}") b = provider self._bindings[cls] = b self._maybe_bind_forward(cls, b) - logger.debug('Bound %s to a provider %s', cls, provider) + logger.debug("Bound %s to a provider %s", cls, provider) return self def _check_class(self, cls: Binding) -> None: if cls is None: - raise InjectorException('Binding key cannot be None') + raise InjectorException("Binding key cannot be None") if not self.allow_override and cls in self._bindings: - raise InjectorException('Duplicate binding, key=%s' % cls) + raise InjectorException(f"Duplicate binding, key={cls}") if self._is_forward_str(cls): - ref = ForwardRef(cls) + ref = t.ForwardRef(cls) if not self.allow_override and ref in self._bindings: - raise InjectorException('Duplicate forward binding, i.e. "int" and int, key=%s', cls) - - def _maybe_bind_forward(self, cls: Binding, binding: Any) -> None: + msg = f'Duplicate forward binding, i.e. "int" and int, key={cls}' + raise InjectorException(msg) + + def _maybe_bind_forward(self, cls: Binding, binding: t.Any) -> None: # noqa: ANN401 """Bind a string forward reference.""" if not _HAS_PEP560_SUPPORT: return if not isinstance(cls, str): return - - ref = ForwardRef(cls) + + ref = t.ForwardRef(cls) self._bindings[ref] = binding logger.debug('Bound forward ref "%s"', cls) - def _is_forward_str(self, cls: Binding) -> bool: - return _HAS_PEP560_SUPPORT and isinstance(cls, str) + @staticmethod + def _is_forward_str(kls: Binding) -> bool: + return _HAS_PEP560_SUPPORT and isinstance(kls, str) -class Injector(object): - _bindings: Dict[Binding, Constructor] +class Injector: + _bindings: dict[Binding, Constructor] def __init__( - self, config: Optional[BinderCallable] = None, bind_in_runtime: bool = True, allow_override: bool = False - ): + self, + config: t.Optional[BinderCallable] = None, + # TODO(pyctrl): force following flags to be kwargs + bind_in_runtime: bool = True, # noqa: FBT001, FBT002 + allow_override: bool = False, # noqa: FBT001, FBT002 + ) -> None: self._bind_in_runtime = bind_in_runtime if config: binder = Binder(allow_override) config(binder) - self._bindings = binder._bindings + self._bindings = binder._bindings # noqa: SLF001 else: self._bindings = {} # NOTE(pyctrl): only since 3.12 - # @overload - # def get_instance(self, cls: Type[T]) -> T: ... + # @t.overload + # def get_instance(self, cls: type[T]) -> T: ... - @overload + @t.overload def get_instance(self, cls: Binding) -> T: ... - @overload - def get_instance(self, cls: Hashable) -> Injectable: ... + @t.overload + def get_instance(self, cls: t.Hashable) -> Injectable: ... def get_instance(self, cls: Binding) -> Injectable: - """Return an instance for a class.""" + """ + Return an instance for a class. + + Raises: + InjectorException: on errors + ConstructorTypeError: over TypeError + + """ binding = self._bindings.get(cls) if binding: return binding() @@ -240,22 +263,25 @@ def get_instance(self, cls: Binding) -> Injectable: return binding() if not self._bind_in_runtime: - raise InjectorException( - 'No binding was found for key=%s' % cls) + msg = f"No binding was found for key={cls}" + raise InjectorException(msg) if not callable(cls): - raise InjectorException( - 'Cannot create a runtime binding, the key is not callable, key=%s' % cls) + msg = ( + "Cannot create a runtime binding, the key is not callable," + f" key={cls}", + ) + raise InjectorException(msg) try: instance = cls() except TypeError as previous_error: - raise ConstructorTypeError(cls, previous_error) + raise ConstructorTypeError(cls, previous_error) # noqa: B904 self._bindings[cls] = lambda: instance - logger.debug( - 'Created a runtime binding for key=%s, instance=%s', cls, instance) + msg = "Created a runtime binding for key=%s, instance=%s" + logger.debug(msg, cls, instance) return instance @@ -263,10 +289,10 @@ class InjectorException(Exception): pass -class _ConstructorBinding(Generic[T]): - _instance: Optional[T] +class _ConstructorBinding(t.Generic[T]): + _instance: t.Optional[T] - def __init__(self, constructor: Callable[[], T]) -> None: + def __init__(self, constructor: t.Callable[[], T]) -> None: self._constructor = constructor self._created = False self._instance = None @@ -319,136 +345,162 @@ def __call__(self) -> T: # - `attr` implementation is inherited from `property` # - `attr` class member is not annotated class _AttributeInjection(property): - def __init__(self, cls: Type[T] | Hashable) -> None: + def __init__(self, cls: t.Union[type[T], t.Hashable]) -> None: self._cls = cls super().__init__( fget=lambda _: instance(self._cls), doc="Return an attribute injection", ) - def __set_name__(self, owner: Type[T], name: str) -> None: + def __set_name__(self, owner: type[T], name: str) -> None: if self._cls is _MISSING: self._cls = _unwrap_cls_annotation(owner, name) -class _ParameterInjection(Generic[T]): - __slots__ = ('_name', '_cls') +class _ParameterInjection(t.Generic[T]): + __slots__ = ("_cls", "_name") - def __init__(self, name: str, cls: Optional[Binding] = None) -> None: + def __init__(self, name: str, cls: t.Optional[Binding] = None) -> None: self._name = name self._cls = cls - def __call__(self, func: Callable[..., Union[T, Awaitable[T]]]) -> Callable[..., Union[T, Awaitable[T]]]: + def __call__( + self, func: t.Callable[..., t.Union[T, t.Awaitable[T]]] + ) -> t.Callable[..., t.Union[T, t.Awaitable[T]]]: if inspect.iscoroutinefunction(func): - @wraps(func) - async def async_injection_wrapper(*args: Any, **kwargs: Any) -> T: + + @functools.wraps(func) + async def async_injection_wrapper(*args: t.Any, **kwargs: t.Any) -> T: # noqa: ANN401 if self._name not in kwargs: kwargs[self._name] = instance(self._cls or self._name) - async_func = cast(Callable[..., Awaitable[T]], func) + async_func = t.cast("t.Callable[..., t.Awaitable[T]]", func) return await async_func(*args, **kwargs) + return async_injection_wrapper - - @wraps(func) - def injection_wrapper(*args: Any, **kwargs: Any) -> T: + + @functools.wraps(func) + def injection_wrapper(*args: t.Any, **kwargs: t.Any) -> T: # noqa: ANN401 if self._name not in kwargs: kwargs[self._name] = instance(self._cls or self._name) - sync_func = cast(Callable[..., T], func) + sync_func = t.cast("t.Callable[..., T]", func) return sync_func(*args, **kwargs) return injection_wrapper -class _ParametersInjection(Generic[T]): - __slots__ = ('_params', ) +class _ParametersInjection(t.Generic[T]): + __slots__ = ("_params",) - def __init__(self, **kwargs: Any) -> None: + def __init__(self, **kwargs: Binding) -> None: self._params = kwargs @staticmethod def _aggregate_sync_stack( - sync_stack: contextlib.ExitStack, - provided_params: frozenset[str], - kwargs: dict[str, Any] + sync_stack: contextlib.ExitStack, + provided_params: frozenset[str], + kwargs: dict[str, t.Any], ) -> None: - """Extracts context managers, aggregate them in an ExitStack and swap out the param value with results of - running __enter__(). The result is equivalent to using `with` multiple times """ + """ + Manage context stack. + + Extracts context managers, aggregate them in an ExitStack + and swap out the param value with results of running `__enter__()`. + The result is equivalent to using `with` multiple times. + """ executed_kwargs = { param: sync_stack.enter_context(inst) for param, inst in kwargs.items() - if param not in provided_params and isinstance(inst, contextlib._GeneratorContextManager) + if param not in provided_params + and isinstance(inst, contextlib._GeneratorContextManager) # noqa: SLF001 } kwargs.update(executed_kwargs) @staticmethod async def _aggregate_async_stack( - async_stack: contextlib.AsyncExitStack, - provided_params: frozenset[str], - kwargs: dict[str, Any] + async_stack: contextlib.AsyncExitStack, + provided_params: frozenset[str], + kwargs: dict[str, t.Any], ) -> None: - """Similar to _aggregate_sync_stack, but for async context managers""" + """Similar to _aggregate_sync_stack, but for async context managers.""" executed_kwargs = { param: await async_stack.enter_async_context(inst) for param, inst in kwargs.items() - if param not in provided_params and isinstance(inst, contextlib._AsyncGeneratorContextManager) + if param not in provided_params + and isinstance(inst, contextlib._AsyncGeneratorContextManager) # noqa: SLF001 } kwargs.update(executed_kwargs) - def __call__(self, func: Callable[..., Union[Awaitable[T], T]]) -> Callable[..., Union[Awaitable[T], T]]: - if sys.version_info.major == 2: - arg_names = inspect.getargspec(func).args - else: - arg_names = inspect.getfullargspec(func).args + def __call__( + self, func: t.Callable[..., t.Union[t.Awaitable[T], T]] + ) -> t.Callable[..., t.Union[t.Awaitable[T], T]]: + arg_names = inspect.getfullargspec(func).args params_to_provide = self._params if inspect.iscoroutinefunction(func): - @wraps(func) - async def async_injection_wrapper(*args: Any, **kwargs: Any) -> T: - provided_params = frozenset( - arg_names[:len(args)]) | frozenset(kwargs.keys()) + + @functools.wraps(func) + async def async_injection_wrapper(*args: t.Any, **kwargs: t.Any) -> T: # noqa: ANN401 + provided_params = frozenset(arg_names[: len(args)]) | frozenset( + kwargs.keys() + ) for param, cls in params_to_provide.items(): if param not in provided_params: kwargs[param] = instance(cls) - async_func = cast(Callable[..., Awaitable[T]], func) + async_func = t.cast("t.Callable[..., t.Awaitable[T]]", func) try: with contextlib.ExitStack() as sync_stack: async with contextlib.AsyncExitStack() as async_stack: - self._aggregate_sync_stack(sync_stack, provided_params, kwargs) - await self._aggregate_async_stack(async_stack, provided_params, kwargs) + self._aggregate_sync_stack( + sync_stack, provided_params, kwargs + ) + await self._aggregate_async_stack( + async_stack, provided_params, kwargs + ) return await async_func(*args, **kwargs) except TypeError as previous_error: - raise ConstructorTypeError(func, previous_error) + raise ConstructorTypeError(func, previous_error) # noqa: B904 return async_injection_wrapper - @wraps(func) - def injection_wrapper(*args: Any, **kwargs: Any) -> T: - provided_params = frozenset( - arg_names[:len(args)]) | frozenset(kwargs.keys()) + @functools.wraps(func) + def injection_wrapper(*args: t.Any, **kwargs: t.Any) -> T: # noqa: ANN401 + provided_params = frozenset(arg_names[: len(args)]) | frozenset( + kwargs.keys() + ) for param, cls in params_to_provide.items(): if param not in provided_params: kwargs[param] = instance(cls) - sync_func = cast(Callable[..., T], func) + sync_func = t.cast("t.Callable[..., T]", func) try: with contextlib.ExitStack() as sync_stack: self._aggregate_sync_stack(sync_stack, provided_params, kwargs) return sync_func(*args, **kwargs) except TypeError as previous_error: - raise ConstructorTypeError(func, previous_error) + raise ConstructorTypeError(func, previous_error) # noqa: B904 + return injection_wrapper def configure( - config: Optional[BinderCallable] = None, - bind_in_runtime: bool = True, - allow_override: bool = False, - clear: bool = False, - once: bool = False + config: t.Optional[BinderCallable] = None, + # TODO(pyctrl): force following flags to be kwargs + bind_in_runtime: bool = True, # noqa: FBT001, FBT002 + allow_override: bool = False, # noqa: FBT001, FBT002 + clear: bool = False, # noqa: FBT001, FBT002 + once: bool = False, # noqa: FBT001, FBT002 ) -> Injector: - """Create an injector with a callable config or raise an exception when already configured.""" - global _INJECTOR + """ + Create an injector using callable config. + + Raises: + InjectorException: if already configured. + + """ + global _INJECTOR # noqa: PLW0603 if clear and once: - raise InjectorException('clear and once are mutually exclusive, only one can be True') + msg = "clear and once are mutually exclusive, only one can be True" + raise InjectorException(msg) with _INJECTOR_LOCK: if _INJECTOR: @@ -457,41 +509,55 @@ def configure( elif once: return _INJECTOR else: - raise InjectorException('Injector is already configured') + raise InjectorException("Injector is already configured") - _INJECTOR = Injector(config, bind_in_runtime=bind_in_runtime, allow_override=allow_override) - logger.debug('Created and configured an injector, config=%s', config) + _INJECTOR = Injector( + config, + bind_in_runtime=bind_in_runtime, + allow_override=allow_override, + ) + logger.debug("Created and configured an injector, config=%s", config) return _INJECTOR def configure_once( - config: Optional[BinderCallable] = None, - bind_in_runtime: bool = True, - allow_override: bool = False + config: t.Optional[BinderCallable] = None, + # TODO(pyctrl): force following flags to be kwargs + bind_in_runtime: bool = True, # noqa: FBT001, FBT002 + allow_override: bool = False, # noqa: FBT001, FBT002 ) -> Injector: - """Create an injector with a callable config if not present, otherwise, do nothing. - + """ + Create an injector with a callable config if not present, otherwise, do nothing. + Deprecated, use `configure(once=True)` instead. """ with _INJECTOR_LOCK: if _INJECTOR: return _INJECTOR - return configure(config, bind_in_runtime=bind_in_runtime, allow_override=allow_override) + return configure( + config, bind_in_runtime=bind_in_runtime, allow_override=allow_override + ) def clear_and_configure( - config: Optional[BinderCallable] = None, - bind_in_runtime: bool = True, - allow_override: bool = False + config: t.Optional[BinderCallable] = None, + # TODO(pyctrl): force following flags to be kwargs + bind_in_runtime: bool = True, # noqa: FBT001, FBT002 + allow_override: bool = False, # noqa: FBT001, FBT002 ) -> Injector: - """Clear an existing injector and create another one with a callable config. - + """ + Clear an existing injector and create another one with a callable config. + Deprecated, use configure(clear=True) instead. """ with _INJECTOR_LOCK: _clear_injector() - return configure(config, bind_in_runtime=bind_in_runtime, allow_override=allow_override) + return configure( + config, + bind_in_runtime=bind_in_runtime, + allow_override=allow_override, + ) def is_configured() -> bool: @@ -507,34 +573,40 @@ def clear() -> None: def _clear_injector() -> None: """Clear an existing injector if present.""" - global _INJECTOR + global _INJECTOR # noqa: PLW0603 with _INJECTOR_LOCK: if _INJECTOR is None: return _INJECTOR = None - logger.debug('Cleared an injector') + logger.debug("Cleared an injector") -@overload -def instance(cls: Type[T]) -> T: ... +@t.overload +def instance(cls: type[T]) -> T: ... + + +@t.overload +def instance(cls: t.Hashable) -> Injectable: ... -@overload -def instance(cls: Hashable) -> Injectable: ... def instance(cls: Binding) -> Injectable: """Inject an instance of a class.""" return get_injector_or_die().get_instance(cls) -@overload + +@t.overload def attr() -> Injectable: ... -@overload -def attr(cls: Hashable) -> Injectable: ... -@overload -def attr(cls: Type[T]) -> T: ... +@t.overload +def attr(cls: t.Hashable) -> Injectable: ... + + +@t.overload +def attr(cls: type[T]) -> T: ... + def attr(cls=_MISSING): """Return an attribute injection (descriptor).""" @@ -545,13 +617,18 @@ def attr(cls=_MISSING): attr_dc = attr -def param(name: str, cls: Optional[Binding] = None) -> Callable: - """Deprecated, use @inject.params. Return a decorator which injects an arg into a function.""" +def param(name: str, cls: t.Optional[Binding] = None) -> t.Callable: + """ + Return a decorator which injects an arg into a function. + + Deprecated, use @inject.params. + """ return _ParameterInjection(name, cls) -def params(**args_to_classes: Binding) -> Callable: - """Return a decorator which injects args into a function. +def params(**args_to_classes: Binding) -> t.Callable: + """ + Return a decorator which injects args into a function. For example:: @@ -563,22 +640,27 @@ def sign_up(name, email, cache, db): # NOTE(pyctrl): only since 3.12 -# @overload -# def autoparams[T](fn: Callable[..., T]) -> Callable[..., T]: ... +# @t.overload +# def autoparams[T](fn: t.Callable[..., T]) -> t.Callable[..., T]: ... + + +@t.overload +def autoparams(fn: t.Callable) -> t.Callable: ... -@overload -def autoparams(fn: Callable) -> Callable: ... # NOTE(pyctrl): only since 3.12 -# @overload -# def autoparams[C: Callable](*selected: str) -> Callable[[C], C]: ... +# @t.overload +# def autoparams[C: t.Callable](*selected: str) -> t.Callable[[C], C]: + + +@t.overload +def autoparams(*selected: str) -> t.Callable: ... -@overload -def autoparams(*selected: str) -> Callable: ... -@no_type_check +@t.no_type_check def autoparams(*selected: str): - """Return a decorator that will inject args into a function using type annotations, Python >= 3.5 only. + """ + Return a decorator injecting args based on function type hints, only since 3.5. For example:: @@ -586,7 +668,8 @@ def autoparams(*selected: str): def refresh_cache(cache: RedisCache, db: DbInterface): pass - There is an option to specify which arguments we want to inject without attempts of injecting everything: + There is an option to specify which arguments we want to + inject without attempts of injecting everything: For example:: @@ -594,13 +677,13 @@ def refresh_cache(cache: RedisCache, db: DbInterface): def sign_up(name, email, cache: RedisCache, db: DbInterface): pass """ - only_these: Set[str] = set() + only_these: set[str] = set() - def autoparams_decorator(fn: Callable[..., T]) -> Callable[..., T]: + def autoparams_decorator(fn: t.Callable[..., T]) -> t.Callable[..., T]: if inspect.isclass(fn): - types = get_type_hints(fn.__init__) + types = t.get_type_hints(fn.__init__) else: - types = get_type_hints(fn) + types = t.get_type_hints(fn) # Skip the return annotation. types = {name: typ for name, typ in types.items() if name != _RETURN} @@ -611,7 +694,7 @@ def autoparams_decorator(fn: Callable[..., T]) -> Callable[..., T]: # Filter types if selected args present. if only_these: types = {name: typ for name, typ in types.items() if name in only_these} - + wrapper: _ParametersInjection[T] = _ParametersInjection(**types) return wrapper(fn) @@ -623,29 +706,41 @@ def autoparams_decorator(fn: Callable[..., T]) -> Callable[..., T]: return autoparams_decorator -def get_injector() -> Optional[Injector]: +def get_injector() -> t.Optional[Injector]: """Return the current injector or None.""" return _INJECTOR def get_injector_or_die() -> Injector: - """Return the current injector or raise an InjectorException.""" + """ + Return the current injector or raise an InjectorException. + + Raises: + InjectorException: If injector is not configured. + + Returns: + Configured injector. + + """ injector = _INJECTOR if not injector: - raise InjectorException('No injector is configured') + raise InjectorException("No injector is configured") return injector -def _unwrap_union_arg(typ): +def _unwrap_union_arg(typ: type) -> type: """Return the first type A in typing.Union[A, B] or typ if not Union.""" if not _is_union_type(typ): return typ return typ.__args__[0] -def _is_union_type(typ): - """Test if the type is a union type. Examples:: +def _is_union_type(typ: type) -> bool: + """ + Test if the type is a union type. + + Examples:: is_union_type(int) == False is_union_type(Union) == True is_union_type(Union[int, int]) == False @@ -654,21 +749,24 @@ def _is_union_type(typ): Source: https://github.com/ilevkivskyi/typing_inspect/blob/master/typing_inspect.py """ if _HAS_PEP604_SUPPORT: - return (typ is Union or - isinstance(typ, UnionType) or - isinstance(typ, _GenericAlias) and typ.__origin__ is Union) - elif _HAS_PEP560_SUPPORT: - return (typ is Union or - isinstance(typ, _GenericAlias) and typ.__origin__ is Union) + return ( + typ is t.Union + or isinstance(typ, UnionType) + or (isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union) + ) + if _HAS_PEP560_SUPPORT: + return typ is t.Union or ( + isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union + ) return type(typ) is _Union -def _unwrap_cls_annotation(cls: Type, attr_name: str): - types = get_type_hints(cls) +def _unwrap_cls_annotation(cls: type, attr_name: str) -> type: + types = t.get_type_hints(cls) try: attr_type = types[attr_name] except KeyError: msg = f"Couldn't find type annotation for {attr_name}" - raise InjectorException(msg) + raise InjectorException(msg) from None return _unwrap_union_arg(attr_type) diff --git a/tests/__init__.py b/tests/__init__.py index d4e0635..f372226 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,12 +1,14 @@ -from unittest import TestCase import asyncio +import typing as t +from unittest import TestCase + import inject class BaseTestInject(TestCase): - def tearDown(self): + def tearDown(self) -> None: inject.clear() - - def run_async(self, awaitable): + + def run_async(self, awaitable: t.Awaitable): # noqa: ANN201 loop = asyncio.get_event_loop() - return loop.run_until_complete(awaitable) \ No newline at end of file + return loop.run_until_complete(awaitable) diff --git a/tests/test_attr.py b/tests/test_attr.py index 5411b30..cedfc8e 100644 --- a/tests/test_attr.py +++ b/tests/test_attr.py @@ -1,13 +1,14 @@ -from dataclasses import dataclass +import dataclasses + import inject from tests import BaseTestInject class TestInjectAttr(BaseTestInject): def test_attr(self): - @dataclass + @dataclasses.dataclass class MyDataClass: - field = inject.attr(int) + field = inject.attr(int) # noqa: RUF045 class MyClass: field = inject.attr(int) @@ -28,7 +29,7 @@ class MyClass: assert my_dc.field == 123 def test_invalid_attachment_to_dataclass(self): - @dataclass + @dataclasses.dataclass class MyDataClass: # dataclasses treat this definition as regular descriptor field: int = inject.attr(int) @@ -39,11 +40,11 @@ def test_class_attr(self): descriptor = inject.attr(int) auto_descriptor = inject.attr() - @dataclass + @dataclasses.dataclass class MyDataClass: - field = descriptor + field = descriptor # noqa: RUF045 - class MyClass(object): + class MyClass: field = descriptor field2: int = descriptor auto_typed_field: int = auto_descriptor diff --git a/tests/test_autoparams.py b/tests/test_autoparams.py index a3239c9..03775a2 100644 --- a/tests/test_autoparams.py +++ b/tests/test_autoparams.py @@ -1,14 +1,20 @@ import sys -from typing import Optional +import typing as t +import inject from tests import BaseTestInject -import inject +class A: + pass + + +class B: + pass -class A: pass -class B: pass -class C: pass + +class C: + pass class TestInjectEmptyAutoparamsWithBraces(BaseTestInject): @@ -18,7 +24,7 @@ def _get_decorator(): def test_autoparams_by_class(self): @self._get_decorator() - def test_func(val: int = None): + def test_func(val: t.Optional[int] = None): return val inject.configure(lambda binder: binder.bind(int, 123)) @@ -42,16 +48,16 @@ def config(binder): assert test_func(10) == (10, 2, 3) assert test_func(10, 20) == (10, 20, 3) assert test_func(10, 20, c=30) == (10, 20, 30) - assert test_func(a='a') == ('a', 2, 3) - assert test_func(b='b') == (1, 'b', 3) - assert test_func(c='c') == (1, 2, 'c') + assert test_func(a="a") == ("a", 2, 3) + assert test_func(b="b") == (1, "b", 3) + assert test_func(c="c") == (1, 2, "c") assert test_func(a=10, c=30) == (10, 2, 30) assert test_func(c=30, b=20, a=10) == (10, 20, 30) assert test_func(10, b=20) == (10, 20, 3) def test_autoparams_strings(self): @self._get_decorator() - def test_func(a: 'A', b: 'B', *, c: 'C'): + def test_func(a: A, b: B, *, c: C): return a, b, c def config(binder): @@ -65,16 +71,16 @@ def config(binder): assert test_func(10) == (10, 2, 3) assert test_func(10, 20) == (10, 20, 3) assert test_func(10, 20, c=30) == (10, 20, 30) - assert test_func(a='a') == ('a', 2, 3) - assert test_func(b='b') == (1, 'b', 3) - assert test_func(c='c') == (1, 2, 'c') + assert test_func(a="a") == ("a", 2, 3) + assert test_func(b="b") == (1, "b", 3) + assert test_func(c="c") == (1, 2, "c") assert test_func(a=10, c=30) == (10, 2, 30) assert test_func(c=30, b=20, a=10) == (10, 20, 30) assert test_func(10, b=20) == (10, 20, 3) def test_autoparams_with_defaults(self): @self._get_decorator() - def test_func(a=1, b: 'B' = None, *, c: 'C' = 300): + def test_func(a=1, b: B = None, *, c: C = 300): return a, b, c def config(binder): @@ -87,9 +93,9 @@ def config(binder): assert test_func(10) == (10, 2, 3) assert test_func(10, 20) == (10, 20, 3) assert test_func(10, 20, c=30) == (10, 20, 30) - assert test_func(a='a') == ('a', 2, 3) - assert test_func(b='b') == (1, 'b', 3) - assert test_func(c='c') == (1, 2, 'c') + assert test_func(a="a") == ("a", 2, 3) + assert test_func(b="b") == (1, "b", 3) + assert test_func(c="c") == (1, 2, "c") assert test_func(a=10, c=30) == (10, 2, 30) assert test_func(c=30, b=20, a=10) == (10, 20, 30) assert test_func(10, b=20) == (10, 20, 3) @@ -97,7 +103,7 @@ def config(binder): def test_autoparams_on_method(self): class Test: @self._get_decorator() - def func(self, a=1, b: 'B' = None, *, c: 'C' = None): + def func(self, a=1, b: B = None, *, c: t.Optional[C] = None): return self, a, b, c def config(binder): @@ -111,9 +117,9 @@ def config(binder): assert test.func(10) == (test, 10, 2, 3) assert test.func(10, 20) == (test, 10, 20, 3) assert test.func(10, 20, c=30) == (test, 10, 20, 30) - assert test.func(a='a') == (test, 'a', 2, 3) - assert test.func(b='b') == (test, 1, 'b', 3) - assert test.func(c='c') == (test, 1, 2, 'c') + assert test.func(a="a") == (test, "a", 2, 3) + assert test.func(b="b") == (test, 1, "b", 3) + assert test.func(c="c") == (test, 1, 2, "c") assert test.func(a=10, c=30) == (test, 10, 2, 30) assert test.func(c=30, b=20, a=10) == (test, 10, 20, 30) assert test.func(10, b=20) == (test, 10, 20, 3) @@ -123,7 +129,7 @@ class Test: # note inject must be *before* classmethod! @classmethod @self._get_decorator() - def func(cls, a=1, b: 'B' = None, *, c: 'C' = None): + def func(cls, a=1, b: B = None, *, c: t.Optional[C] = None): return cls, a, b, c def config(binder): @@ -136,9 +142,9 @@ def config(binder): assert Test.func(10) == (Test, 10, 2, 3) assert Test.func(10, 20) == (Test, 10, 20, 3) assert Test.func(10, 20, c=30) == (Test, 10, 20, 30) - assert Test.func(a='a') == (Test, 'a', 2, 3) - assert Test.func(b='b') == (Test, 1, 'b', 3) - assert Test.func(c='c') == (Test, 1, 2, 'c') + assert Test.func(a="a") == (Test, "a", 2, 3) + assert Test.func(b="b") == (Test, 1, "b", 3) + assert Test.func(c="c") == (Test, 1, 2, "c") assert Test.func(a=10, c=30) == (Test, 10, 2, 30) assert Test.func(c=30, b=20, a=10) == (Test, 10, 20, 30) assert Test.func(10, b=20) == (Test, 10, 20, 3) @@ -148,7 +154,7 @@ class Test: # note inject must be *before* classmethod! @classmethod @self._get_decorator() - def func(cls, a=1, b: 'B' = None, *, c: 'C' = None): + def func(cls, a=1, b: B = None, *, c: t.Optional[C] = None): return cls, a, b, c def config(binder): @@ -162,9 +168,9 @@ def config(binder): assert test.func(10) == (Test, 10, 2, 3) assert test.func(10, 20) == (Test, 10, 20, 3) assert test.func(10, 20, c=30) == (Test, 10, 20, 30) - assert test.func(a='a') == (Test, 'a', 2, 3) - assert test.func(b='b') == (Test, 1, 'b', 3) - assert test.func(c='c') == (Test, 1, 2, 'c') + assert test.func(a="a") == (Test, "a", 2, 3) + assert test.func(b="b") == (Test, 1, "b", 3) + assert test.func(c="c") == (Test, 1, 2, "c") assert test.func(a=10, c=30) == (Test, 10, 2, 30) assert test.func(c=30, b=20, a=10) == (Test, 10, 20, 30) assert test.func(10, b=20) == (Test, 10, 20, 3) @@ -175,11 +181,11 @@ def test_func(a: str) -> int: return a def config(binder): - binder.bind(str, 'bazinga') + binder.bind(str, "bazinga") inject.configure(config) - assert test_func() == 'bazinga' + assert test_func() == "bazinga" class TestInjectEmptyAutoparamsNoBraces(TestInjectEmptyAutoparamsWithBraces): @@ -189,10 +195,9 @@ def _get_decorator(): class TestInjectSelectedAutoparams(BaseTestInject): - def test_autoparams_only_selected(self): - @inject.autoparams('a', 'c') - def test_func(a: 'A', b: 'B', *, c: 'C'): + @inject.autoparams("a", "c") + def test_func(a: A, b: B, *, c: C): return a, b, c def config(binder): @@ -206,8 +211,8 @@ def config(binder): self.assertRaises(TypeError, test_func, a=1, c=3) def test_autoparams_only_selected_with_optional(self): - @inject.autoparams('a', 'c') - def test_func(a: 'A', b: 'B', *, c: Optional[C] = None): + @inject.autoparams("a", "c") + def test_func(a: A, b: B, *, c: t.Optional[C] = None): return a, b, c def config(binder): @@ -224,8 +229,8 @@ def test_autoparams_only_selected_with_optional_pep604_union(self): if not sys.version_info[:3] >= (3, 10, 0): return - @inject.autoparams('a', 'c') - def test_func(a: 'A', b: 'B', *, c: C | None = None): + @inject.autoparams("a", "c") + def test_func(a: A, b: B, *, c: t.Optional[C] = None): return a, b, c def config(binder): diff --git a/tests/test_binder.py b/tests/test_binder.py index 9decd2a..bd7e9d1 100644 --- a/tests/test_binder.py +++ b/tests/test_binder.py @@ -1,50 +1,74 @@ from unittest import TestCase -from inject import Binder, InjectorException +import inject class TestBinder(TestCase): def test_bind(self): - binder = Binder() + binder = inject.Binder() binder.bind(int, 123) assert int in binder._bindings def test_bind__class_required(self): - binder = Binder() + binder = inject.Binder() - self.assertRaisesRegex(InjectorException, 'Binding key cannot be None', binder.bind, None, None) + self.assertRaisesRegex( + inject.InjectorException, + "Binding key cannot be None", + binder.bind, + None, + None, + ) def test_bind__duplicate_binding(self): - binder = Binder() + binder = inject.Binder() binder.bind(int, 123) - self.assertRaisesRegex(InjectorException, "Duplicate binding", binder.bind, int, 456) + self.assertRaisesRegex( + inject.InjectorException, + "Duplicate binding", + binder.bind, + int, + 456, + ) def test_bind__allow_override(self): - binder = Binder(allow_override=True) + binder = inject.Binder(allow_override=True) binder.bind(int, 123) binder.bind(int, 456) assert int in binder._bindings def test_bind_provider(self): provider = lambda: 123 - binder = Binder() + binder = inject.Binder() binder.bind_to_provider(int, provider) assert binder._bindings[int] is provider def test_bind_provider__provider_required(self): - binder = Binder() - self.assertRaisesRegex(InjectorException, "Provider cannot be None", binder.bind_to_provider, int, None) + binder = inject.Binder() + self.assertRaisesRegex( + inject.InjectorException, + "Provider cannot be None", + binder.bind_to_provider, + int, + None, + ) def test_bind_constructor(self): constructor = lambda: 123 - binder = Binder() + binder = inject.Binder() binder.bind_to_constructor(int, constructor) assert binder._bindings[int]._constructor is constructor def test_bind_constructor__constructor_required(self): - binder = Binder() - self.assertRaisesRegex(InjectorException, "Constructor cannot be None", binder.bind_to_constructor, int, None) + binder = inject.Binder() + self.assertRaisesRegex( + inject.InjectorException, + "Constructor cannot be None", + binder.bind_to_constructor, + int, + None, + ) diff --git a/tests/test_context_manager.py b/tests/test_context_manager.py index 058bf52..9b19bc8 100644 --- a/tests/test_context_manager.py +++ b/tests/test_context_manager.py @@ -12,16 +12,13 @@ def destroy(self): self.started = False -class MockFile(Destroyable): - ... +class MockFile(Destroyable): ... -class MockConnection(Destroyable): - ... +class MockConnection(Destroyable): ... -class MockFoo(Destroyable): - ... +class MockFoo(Destroyable): ... @contextlib.contextmanager @@ -46,21 +43,20 @@ def get_foo_sync(): @contextlib.asynccontextmanager -async def get_file_async(): +async def get_file_async(): # noqa: RUF029 obj = MockFile() yield obj obj.destroy() @contextlib.asynccontextmanager -async def get_conn_async(): +async def get_conn_async(): # noqa: RUF029 obj = MockConnection() yield obj obj.destroy() class TestContextManagerFunctional(BaseTestInject): - def test_provider_as_context_manager_sync(self): def config(binder): binder.bind_to_provider(MockFile, get_file_sync) @@ -93,7 +89,13 @@ def config(binder): inject.configure(config) @inject.autoparams() - async def mock_func(conn: MockConnection, name: str, f: MockFile, number: int, foo: MockFoo): + async def mock_func( # noqa: RUF029 + conn: MockConnection, + name: str, + f: MockFile, + number: int, + foo: MockFoo, + ): assert f.started assert conn.started assert foo.started @@ -104,4 +106,4 @@ async def mock_func(conn: MockConnection, name: str, f: MockFile, number: int, f f_, conn_, foo_ = self.run_async(mock_func()) assert not f_.started assert not conn_.started - assert not foo_.started \ No newline at end of file + assert not foo_.started diff --git a/tests/test_functional.py b/tests/test_functional.py index 7377154..eae7356 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -1,7 +1,7 @@ +import dataclasses from unittest import TestCase import inject -from inject import autoparams, ConstructorTypeError class TestFunctional(TestCase): @@ -9,43 +9,38 @@ def tearDown(self): inject.clear() def test(self): - class Config(object): + class Config: def __init__(self, greeting): self.greeting = greeting - class Cache(object): + class Cache: config = inject.attr(Config) def load_greeting(self): return self.config.greeting - class User(object): + class User: cache = inject.attr(Cache) def __init__(self, name): self.name = name def greet(self): - return '%s, %s' % (self.cache.load_greeting(), self.name) + return f"{self.cache.load_greeting()}, {self.name}" def config(binder): - binder.bind(Config, Config('Hello')) + binder.bind(Config, Config("Hello")) inject.configure(config) - user = User('John Doe') + user = User("John Doe") greeting = user.greet() - assert greeting == 'Hello, John Doe' + assert greeting == "Hello, John Doe" def test_class_with_restricted_bool_casting(self): - class DataFrame(object): - def __nonzero__(self): - """Python 2""" - raise NotImplementedError('Casting to boolean is not allowed') - + class DataFrame: def __bool__(self): - """Python 3""" - raise NotImplementedError('Casting to boolean is not allowed') + raise NotImplementedError("Casting to boolean is not allowed") def create_data_frame(): return DataFrame() @@ -63,13 +58,13 @@ def test_class_support_in_autoparams_programmaticaly(self): class AnotherClass: pass + @dataclasses.dataclass class SomeClass: - def __init__(self, another_object: AnotherClass): - self.another_object = another_object + another_object: AnotherClass def config(binder): - binder.bind_to_constructor(SomeClass, autoparams()(SomeClass)) - binder.bind_to_constructor(AnotherClass, autoparams()(AnotherClass)) + binder.bind_to_constructor(SomeClass, inject.autoparams()(SomeClass)) + binder.bind_to_constructor(AnotherClass, inject.autoparams()(AnotherClass)) inject.configure(config) @@ -89,7 +84,7 @@ def create_some_class(missing_arg): return AnotherClass(missing_arg) def config(binder): - binder.bind_to_constructor(SomeClass, autoparams()(SomeClass)) + binder.bind_to_constructor(SomeClass, inject.autoparams()(SomeClass)) binder.bind_to_constructor(AnotherClass, create_some_class) inject.configure() @@ -97,22 +92,22 @@ def config(binder): # covers case when no constructor provided try: inject.instance(SomeClass) - except ConstructorTypeError as err: - assert 'SomeClass' in str(err) - assert 'missing_arg' in str(err) + except inject.ConstructorTypeError as err: + assert "SomeClass" in str(err) + assert "missing_arg" in str(err) inject.clear_and_configure(config) # covers case with provided constructor try: inject.instance(SomeClass) - except ConstructorTypeError as err: - assert 'SomeClass' in str(err) - assert 'missing_arg' in str(err) + except inject.ConstructorTypeError as err: + assert "SomeClass" in str(err) + assert "missing_arg" in str(err) try: inject.instance(AnotherClass) except TypeError as err: - assert not isinstance(err, ConstructorTypeError) - assert 'create_some_class' in str(err) - assert 'missing_arg' in str(err) \ No newline at end of file + assert not isinstance(err, inject.ConstructorTypeError) + assert "create_some_class" in str(err) + assert "missing_arg" in str(err) diff --git a/tests/test_inject_configuration.py b/tests/test_inject_configuration.py index 7ea10f1..573ec26 100644 --- a/tests/test_inject_configuration.py +++ b/tests/test_inject_configuration.py @@ -1,5 +1,4 @@ import inject -from inject import InjectorException from tests import BaseTestInject @@ -18,7 +17,11 @@ def test_configure__should_add_bindings(self): def test_configure__already_configured(self): inject.configure() - self.assertRaisesRegex(InjectorException, 'Injector is already configured', inject.configure) + self.assertRaisesRegex( + inject.InjectorException, + "Injector is already configured", + inject.configure, + ) def test_configure_once__should_create_injector(self): injector = inject.configure_once() @@ -46,11 +49,20 @@ def test_clear_and_configure(self): assert injector1 is not injector0 def test_get_injector_or_die(self): - self.assertRaisesRegex(InjectorException, 'No injector is configured', inject.get_injector_or_die) + self.assertRaisesRegex( + inject.InjectorException, + "No injector is configured", + inject.get_injector_or_die, + ) def test_configure__runtime_binding_disabled(self): injector = inject.configure(bind_in_runtime=False) - self.assertRaisesRegex(InjectorException, "No binding was found for key=<.* 'int'>", injector.get_instance, int) + self.assertRaisesRegex( + inject.InjectorException, + "No binding was found for key=<.* 'int'>", + injector.get_instance, + int, + ) def test_configure__install_allow_override(self): def base_config(binder): diff --git a/tests/test_injector.py b/tests/test_injector.py index 59335f0..fea7e10 100644 --- a/tests/test_injector.py +++ b/tests/test_injector.py @@ -1,34 +1,37 @@ -from random import random +import random from unittest import TestCase -from inject import Injector, InjectorException +import inject class TestInjector(TestCase): def test_instance_binding__should_use_the_same_instance(self): - injector = Injector(lambda binder: binder.bind(int, 123)) + injector = inject.Injector(lambda binder: binder.bind(int, 123)) instance = injector.get_instance(int) assert instance == 123 def test_constructor_binding__should_construct_singleton(self): - injector = Injector(lambda binder: binder.bind_to_constructor(int, random)) + injector = inject.Injector( + lambda binder: binder.bind_to_constructor(int, random.random) + ) instance0 = injector.get_instance(int) instance1 = injector.get_instance(int) assert instance0 == instance1 def test_provider_binding__should_call_provider_for_each_injection(self): - injector = Injector(lambda binder: binder.bind_to_provider(int, random)) + injector = inject.Injector( + lambda binder: binder.bind_to_provider(int, random.random) + ) instance0 = injector.get_instance(int) instance1 = injector.get_instance(int) assert instance0 != instance1 - def test_runtime_binding__should_create_runtime_singleton(self): - class MyClass(object): + class MyClass: pass - injector = Injector() + injector = inject.Injector() instance0 = injector.get_instance(MyClass) instance1 = injector.get_instance(MyClass) @@ -36,13 +39,19 @@ class MyClass(object): assert isinstance(instance0, MyClass) def test_runtime_binding__not_callable(self): - injector = Injector() - self.assertRaisesRegex(InjectorException, - 'Cannot create a runtime binding, the key is not callable, key=123', - injector.get_instance, 123) + injector = inject.Injector() + self.assertRaisesRegex( + inject.InjectorException, + "Cannot create a runtime binding, the key is not callable, key=123", + injector.get_instance, + 123, + ) def test_runtime_binding__disabled(self): - injector = Injector(bind_in_runtime=False) - self.assertRaisesRegex(InjectorException, - "No binding was found for key=<.* 'int'>", - injector.get_instance, int) + injector = inject.Injector(bind_in_runtime=False) + self.assertRaisesRegex( + inject.InjectorException, + "No binding was found for key=<.* 'int'>", + injector.get_instance, + int, + ) diff --git a/tests/test_param.py b/tests/test_param.py index 29767a7..a8c9593 100644 --- a/tests/test_param.py +++ b/tests/test_param.py @@ -1,34 +1,35 @@ +import inspect + import inject from tests import BaseTestInject -import inspect -import asyncio + class TestInjectParams(BaseTestInject): def test_param_by_name(self): - @inject.param('val') + @inject.param("val") def test_func(val=None): return val - inject.configure(lambda binder: binder.bind('val', 123)) + inject.configure(lambda binder: binder.bind("val", 123)) assert test_func() == 123 assert test_func(val=321) == 321 def test_param_by_class(self): - @inject.param('val', int) + @inject.param("val", int) def test_func(val): return val inject.configure(lambda binder: binder.bind(int, 123)) assert test_func() == 123 - + def test_async_param(self): - @inject.param('val') - async def test_func(val): + @inject.param("val") + async def test_func(val): # noqa: RUF029 return val - - inject.configure(lambda binder: binder.bind('val', 123)) + + inject.configure(lambda binder: binder.bind("val", 123)) assert inspect.iscoroutinefunction(test_func) assert self.run_async(test_func()) == 123 diff --git a/tests/test_params.py b/tests/test_params.py index b6ff9de..cf5ad95 100644 --- a/tests/test_params.py +++ b/tests/test_params.py @@ -1,7 +1,8 @@ +import inspect + import inject from tests import BaseTestInject -import inspect -import asyncio + class TestInjectParams(BaseTestInject): def test_params(self): @@ -16,14 +17,14 @@ def test_func(val): assert test_func(val=42) == 42 def test_params_multi(self): - @inject.params(a='A', b='B', c='C') + @inject.params(a="A", b="B", c="C") def test_func(a, b, c): return a, b, c def config(binder): - binder.bind('A', 1) - binder.bind('B', 2) - binder.bind('C', 3) + binder.bind("A", 1) + binder.bind("B", 2) + binder.bind("C", 3) inject.configure(config) @@ -31,22 +32,22 @@ def config(binder): assert test_func(10) == (10, 2, 3) assert test_func(10, 20) == (10, 20, 3) assert test_func(10, 20, 30) == (10, 20, 30) - assert test_func(a='a') == ('a', 2, 3) - assert test_func(b='b') == (1, 'b', 3) - assert test_func(c='c') == (1, 2, 'c') + assert test_func(a="a") == ("a", 2, 3) + assert test_func(b="b") == (1, "b", 3) + assert test_func(c="c") == (1, 2, "c") assert test_func(a=10, c=30) == (10, 2, 30) assert test_func(c=30, b=20, a=10) == (10, 20, 30) assert test_func(10, b=20) == (10, 20, 3) def test_params_with_defaults(self): # note the inject overrides default parameters - @inject.params(b='B', c='C') + @inject.params(b="B", c="C") def test_func(a=1, b=None, c=300): return a, b, c def config(binder): - binder.bind('B', 2) - binder.bind('C', 3) + binder.bind("B", 2) + binder.bind("C", 3) inject.configure(config) @@ -54,22 +55,22 @@ def config(binder): assert test_func(10) == (10, 2, 3) assert test_func(10, 20) == (10, 20, 3) assert test_func(10, 20, 30) == (10, 20, 30) - assert test_func(a='a') == ('a', 2, 3) - assert test_func(b='b') == (1, 'b', 3) - assert test_func(c='c') == (1, 2, 'c') + assert test_func(a="a") == ("a", 2, 3) + assert test_func(b="b") == (1, "b", 3) + assert test_func(c="c") == (1, 2, "c") assert test_func(a=10, c=30) == (10, 2, 30) assert test_func(c=30, b=20, a=10) == (10, 20, 30) assert test_func(10, b=20) == (10, 20, 3) def test_params_on_method(self): class Test: - @inject.params(b='B', c='C') + @inject.params(b="B", c="C") def func(self, a=1, b=None, c=None): return self, a, b, c def config(binder): - binder.bind('B', 2) - binder.bind('C', 3) + binder.bind("B", 2) + binder.bind("C", 3) inject.configure(config) test = Test() @@ -78,9 +79,9 @@ def config(binder): assert test.func(10) == (test, 10, 2, 3) assert test.func(10, 20) == (test, 10, 20, 3) assert test.func(10, 20, 30) == (test, 10, 20, 30) - assert test.func(a='a') == (test, 'a', 2, 3) - assert test.func(b='b') == (test, 1, 'b', 3) - assert test.func(c='c') == (test, 1, 2, 'c') + assert test.func(a="a") == (test, "a", 2, 3) + assert test.func(b="b") == (test, 1, "b", 3) + assert test.func(c="c") == (test, 1, 2, "c") assert test.func(a=10, c=30) == (test, 10, 2, 30) assert test.func(c=30, b=20, a=10) == (test, 10, 20, 30) assert test.func(10, b=20) == (test, 10, 20, 3) @@ -89,13 +90,13 @@ def test_params_on_classmethod(self): class Test: # note inject must be *before* classmethod! @classmethod - @inject.params(b='B', c='C') + @inject.params(b="B", c="C") def func(cls, a=1, b=None, c=None): return cls, a, b, c def config(binder): - binder.bind('B', 2) - binder.bind('C', 3) + binder.bind("B", 2) + binder.bind("C", 3) inject.configure(config) @@ -103,9 +104,9 @@ def config(binder): assert Test.func(10) == (Test, 10, 2, 3) assert Test.func(10, 20) == (Test, 10, 20, 3) assert Test.func(10, 20, 30) == (Test, 10, 20, 30) - assert Test.func(a='a') == (Test, 'a', 2, 3) - assert Test.func(b='b') == (Test, 1, 'b', 3) - assert Test.func(c='c') == (Test, 1, 2, 'c') + assert Test.func(a="a") == (Test, "a", 2, 3) + assert Test.func(b="b") == (Test, 1, "b", 3) + assert Test.func(c="c") == (Test, 1, 2, "c") assert Test.func(a=10, c=30) == (Test, 10, 2, 30) assert Test.func(c=30, b=20, a=10) == (Test, 10, 20, 30) assert Test.func(10, b=20) == (Test, 10, 20, 3) @@ -114,13 +115,13 @@ def test_params_on_classmethod_ob_object(self): class Test: # note inject must be *before* classmethod! @classmethod - @inject.params(b='B', c='C') + @inject.params(b="B", c="C") def func(cls, a=1, b=None, c=None): return cls, a, b, c def config(binder): - binder.bind('B', 2) - binder.bind('C', 3) + binder.bind("B", 2) + binder.bind("C", 3) inject.configure(config) test = Test @@ -129,16 +130,16 @@ def config(binder): assert test.func(10) == (Test, 10, 2, 3) assert test.func(10, 20) == (Test, 10, 20, 3) assert test.func(10, 20, 30) == (Test, 10, 20, 30) - assert test.func(a='a') == (Test, 'a', 2, 3) - assert test.func(b='b') == (Test, 1, 'b', 3) - assert test.func(c='c') == (Test, 1, 2, 'c') + assert test.func(a="a") == (Test, "a", 2, 3) + assert test.func(b="b") == (Test, 1, "b", 3) + assert test.func(c="c") == (Test, 1, 2, "c") assert test.func(a=10, c=30) == (Test, 10, 2, 30) assert test.func(c=30, b=20, a=10) == (Test, 10, 20, 30) assert test.func(10, b=20) == (Test, 10, 20, 3) def test_async_params(self): @inject.params(val=int) - async def test_func(val): + async def test_func(val): # noqa: RUF029 return val inject.configure(lambda binder: binder.bind(int, 123)) From 6b79e740ee144098ab83b91c9bc3ad26388bc9f5 Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 7 Nov 2025 13:10:11 +0100 Subject: [PATCH 14/53] chore(ci): also test Python 3.14; run linter in Python 3.14 instead of 3.13 --- .github/workflows/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 04e06d2..c4b9cad 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.13" + python-version: "3.14" - name: "Install tox" run: | pip install tox @@ -59,7 +59,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From 3d525d0ba6faad365702b3580bd560af5814e766 Mon Sep 17 00:00:00 2001 From: Manuel Traut Date: Sat, 3 Jan 2026 11:24:26 +0100 Subject: [PATCH 15/53] tests: Don't use deprecated asyncio method It was removed in python3.14 Signed-off-by: Manuel Traut --- tests/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index f372226..eef83c2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -10,5 +10,10 @@ def tearDown(self) -> None: inject.clear() def run_async(self, awaitable: t.Awaitable): # noqa: ANN201 - loop = asyncio.get_event_loop() - return loop.run_until_complete(awaitable) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + ret = loop.run_until_complete(awaitable) + finally: + loop.close() + return ret From b48832f97562e6c7342a167bb10ac592e32eca54 Mon Sep 17 00:00:00 2001 From: Perchun Pak Date: Wed, 10 Jun 2026 21:09:18 +0200 Subject: [PATCH 16/53] Support all context managers --- src/inject/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 851f310..8f3628c 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -112,7 +112,14 @@ def my_config(binder): Injectable = t.Union[object, t.Any] T = t.TypeVar("T", bound=Injectable) Binding = t.Union[type[Injectable], t.Hashable] -Constructor = t.Callable[[], Injectable] +Constructor = t.Callable[ + [], + t.Union[ + Injectable, + contextlib.AbstractContextManager[Injectable], + contextlib.AbstractAsyncContextManager[Injectable], + ], +] Provider = Constructor BinderCallable = t.Callable[["Binder"], t.Optional["Binder"]] @@ -411,7 +418,7 @@ def _aggregate_sync_stack( param: sync_stack.enter_context(inst) for param, inst in kwargs.items() if param not in provided_params - and isinstance(inst, contextlib._GeneratorContextManager) # noqa: SLF001 + and isinstance(inst, contextlib.AbstractContextManager) } kwargs.update(executed_kwargs) @@ -426,7 +433,7 @@ async def _aggregate_async_stack( param: await async_stack.enter_async_context(inst) for param, inst in kwargs.items() if param not in provided_params - and isinstance(inst, contextlib._AsyncGeneratorContextManager) # noqa: SLF001 + and isinstance(inst, contextlib.AbstractAsyncContextManager) } kwargs.update(executed_kwargs) From 848c27b66980fbdfc619f658935de2c354ca6b1c Mon Sep 17 00:00:00 2001 From: Perchun Pak Date: Wed, 10 Jun 2026 21:22:36 +0200 Subject: [PATCH 17/53] Fix `autoparams` type annotation --- pyproject.toml | 2 ++ src/inject/__init__.py | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bc872a6..f793b7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", ] @@ -36,6 +37,7 @@ dev = [ "ruff", "mypy", "yamllint", + "typing-extensions", { include-group = "tests" }, ] diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 851f310..d79f69e 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -102,6 +102,12 @@ def my_config(binder): else: from typing import _Union # noqa: ICN003, PLC2701 +if t.TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") +else: + P = object() logger = logging.getLogger("inject") @@ -639,26 +645,15 @@ def sign_up(name, email, cache, db): return _ParametersInjection(**args_to_classes) -# NOTE(pyctrl): only since 3.12 -# @t.overload -# def autoparams[T](fn: t.Callable[..., T]) -> t.Callable[..., T]: ... - - @t.overload -def autoparams(fn: t.Callable) -> t.Callable: ... - - -# NOTE(pyctrl): only since 3.12 -# @t.overload -# def autoparams[C: t.Callable](*selected: str) -> t.Callable[[C], C]: +def autoparams(fn: t.Callable[P, T]) -> t.Callable[P, T]: ... @t.overload -def autoparams(*selected: str) -> t.Callable: ... +def autoparams(*selected: str) -> t.Callable[[T], T]: ... -@t.no_type_check -def autoparams(*selected: str): +def autoparams(*selected: t.Callable[P, T] | str) -> t.Callable[..., T]: """ Return a decorator injecting args based on function type hints, only since 3.5. From 8da7ff3a6bb35b11e0226bd4ef98a00b6048eee5 Mon Sep 17 00:00:00 2001 From: Ivan Korobkov Date: Mon, 15 Jun 2026 10:53:05 +0300 Subject: [PATCH 18/53] Version 5.4.0 --- CHANGES.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fb5d8ac..9ce0965 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ python-inject changes ===================== +### 5.4.0 (2026-07-16) +- Fix `autoparams` type annotation, #134 +- Support all context managers, #133 + ### 5.3.0 (2025-06-20) - Handle autoparams usage without braces, #115. @@ -82,7 +86,7 @@ python-inject changes (thanks [@scharf](https://github.com/scharf)). ### 3.2.0 (2014-08-04) -- Added `inject.configure_once` and `inject.is_configured`, #11. +- Added `inject.configure_once` and `inject.is_configured`, #11. ### 3.1.1 (2014-03-14) - Switch from root logger to module logger, #8. @@ -93,7 +97,7 @@ python-inject changes ### 3.0.0 (2014-02-10) - Smaller, better, faster version with simpler and cleaner API. - + ### 2.0.0-alpha1 (2010-08-25) - Second version (never made it to stable). From d9518d10fb4553a5e3e971ee785da8583484c4cc Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Fri, 26 Jun 2026 19:49:42 +0200 Subject: [PATCH 19/53] Fix attr overload ordering for type inference --- src/inject/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index be272db..4df24f9 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -614,11 +614,11 @@ def attr() -> Injectable: ... @t.overload -def attr(cls: t.Hashable) -> Injectable: ... +def attr(cls: type[T]) -> T: ... @t.overload -def attr(cls: type[T]) -> T: ... +def attr(cls: t.Hashable) -> Injectable: ... def attr(cls=_MISSING): From e54ad9e6bd3811dcff234fa39c8321a32da0f0cb Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Fri, 26 Jun 2026 20:07:15 +0200 Subject: [PATCH 20/53] Add typing guard for attr overload inference --- .github/workflows/tests.yaml | 15 +++++++++++++++ pyproject.toml | 16 +++++++++++++++- typing_checks/__init__.py | 0 typing_checks/attr.py | 17 +++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 typing_checks/__init__.py create mode 100644 typing_checks/attr.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c4b9cad..45e7ac5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -53,6 +53,21 @@ jobs: # run: | # tox -e lint-mypy + types: + name: "Type checking (public API)" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.14" + - name: "Install tox" + run: | + pip install tox + - name: "Type checking (public API)" + run: | + tox -e lint-types + tests: name: "Run unit tests" runs-on: ubuntu-24.04 diff --git a/pyproject.toml b/pyproject.toml index f793b7a..86fa6c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ defineConstant = { DEBUG = true } exclude = [] executionEnvironments = [] ignore = [] -include = ["src/inject", "tests"] +include = ["src/inject", "tests", "typing_checks"] pythonPlatform = "Linux" pythonVersion = "3.11" reportMissingImports = true @@ -275,6 +275,7 @@ env_list = [ "fmt-py", "fmt-toml", "lint-py", + "lint-types", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", @@ -289,6 +290,7 @@ fmt = [ ] lint = [ "lint-py", + "lint-types", #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", @@ -325,6 +327,18 @@ commands = [ ], ] +[tool.tox.env.lint-types] +description = "Type-check the public-API typing guards" +deps = ["mypy"] +commands = [ + [ + "mypy", + { replace = "posargs", default = [ + "typing_checks", + ], extend = true }, + ], +] + [tool.tox.env.lint-mypy] description = "Type checking" deps = ["mypy"] diff --git a/typing_checks/__init__.py b/typing_checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/typing_checks/attr.py b/typing_checks/attr.py new file mode 100644 index 0000000..250b250 --- /dev/null +++ b/typing_checks/attr.py @@ -0,0 +1,17 @@ +"""Static-typing regression guard for ``inject.attr``.""" + +from typing_extensions import assert_type + +import inject + + +class _Service: + pass + + +class _Repository: + pass + + +assert_type(inject.attr(_Service), _Service) +assert_type(inject.attr(_Repository), _Repository) From 74bbe6e3669f6e3e09961953deefd5f2fbb0cdc7 Mon Sep 17 00:00:00 2001 From: Ivan Korobkov Date: Sat, 27 Jun 2026 11:58:10 +0300 Subject: [PATCH 21/53] Version 5.4.1 --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 9ce0965..0fc08e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ python-inject changes ===================== +### 5.4.1 (2026-07-27) +- Fix static typing of inject.attr(Cls), #136 + ### 5.4.0 (2026-07-16) - Fix `autoparams` type annotation, #134 - Support all context managers, #133 From e19a4f3f0851e34858e2bb2542cfabf0de391c69 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sun, 28 Jun 2026 23:45:14 +0200 Subject: [PATCH 22/53] chore: remove dead configuration --- .github/workflows/tests.yaml | 9 +------ pyproject.toml | 51 ------------------------------------ 2 files changed, 1 insertion(+), 59 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 45e7ac5..79eb503 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -8,7 +8,6 @@ name: tests jobs: misc: -# name: "Linting configs and git" name: "Linting configs" runs-on: ubuntu-latest steps: @@ -28,16 +27,10 @@ jobs: - name: "Lint YAMLs" run: | tox -e lint-yaml -# - name: "Lint git" -# run: | -# tox -e lint-git lint: -# name: "Linting and type checking" name: "Linting" runs-on: ubuntu-24.04 -# strategy: -# fail-fast: true steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -49,7 +42,7 @@ jobs: - name: "Lint formatting" run: | tox -e lint-py -# - name: "Type checking" +# - name: "Type checking" TODO(trin94): revisit linter config and re-enable # run: | # tox -e lint-mypy diff --git a/pyproject.toml b/pyproject.toml index 86fa6c8..f53f729 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -183,55 +183,6 @@ ban-relative-imports = "all" quote-style = "double" indent-style = "space" -#ignore = [ -# "B027", # Allow non-abstract empty methods in abstract base classes -# "FBT003", # Allow boolean positional values in function calls, like `dict.get(... True)` -# # Ignore checks for possible passwords -# "S105", -# "S106", -# "S107", -# # Ignore complexity -# "C901", -# "PLR0911", -# "PLR0912", -# "PLR0913", -# "PLR0915", -# "PLC1901", # empty string comparisons -# "PLW2901", # `for` loop variable overwritten -# "SIM114", # Combine `if` branches using logical `or` operator -#] -#select = [ -# "A", -# "B", -# "C", -# "DTZ", -# "E", -# "EM", -# "F", -# "FBT", -# "I", -# "ICN", -# "ISC", -# "N", -# "PLC", -# "PLE", -# "PLR", -# "PLW", -# "Q", -# "RUF", -# "S", -# "SIM", -# "T", -# "TID", -# "UP", -# "W", -# "YTT", -#] -#unfixable = [ -# "F401", # Don't touch unused imports -#] - - #### Pytest & Coverage #### [tool.pytest.ini_options] minversion = "8.4" @@ -279,7 +230,6 @@ env_list = [ #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", - #"lint-git", "coverage", ] @@ -294,7 +244,6 @@ lint = [ #"lint-mypy", # TODO(pyctrl): make it green & uncomment "lint-toml", "lint-yaml", - #"lint-git", ] # default env From c6d64f3380866b094c5888820811efaf280a8797 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sun, 28 Jun 2026 23:45:50 +0200 Subject: [PATCH 23/53] build: drop Python 3.9 --- .github/workflows/tests.yaml | 2 +- CHANGES.md | 3 +++ README.md | 4 ++-- pyproject.toml | 4 +--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 79eb503..70bc93b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/CHANGES.md b/CHANGES.md index 0fc08e7..e408a59 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ python-inject changes ===================== +### 5.5.0 (unreleased) +- Drop Python 3.9 support (end of life). Minimum supported version is now 3.10. + ### 5.4.1 (2026-07-27) - Fix static typing of inject.attr(Cls), #136 diff --git a/README.md b/README.md index aee6902..b352d9b 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@ Dependency injection the python way, the good way. * Transparently integrates into tests. * Autoparams leveraging type annotations. * Supports type hinting in Python 3.5+. -* Supports Python 3.9+ (`v5.*`), 3.5-3.8 (`v4.*`) and Python 2.7–3.5 (`v3.*`). * Supports context managers. ## Python Support | Python | Inject Version | |---------|----------------| -| 3.9+ | 5.0+ | +| 3.10+ | 5.5+ | +| 3.9 | 5.0 - 5.4 | | 3.6-3.8 | 4.1+, < 5.0 | | 3.5 | 4.0 | | < 3.5 | 3.* | diff --git a/pyproject.toml b/pyproject.toml index f53f729..01374f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -22,7 +21,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [] [project.urls] @@ -218,7 +217,6 @@ runner = "uv-venv-lock-runner" skip_missing_interpreters = true env_list = [ - "py39", "py310", "py311", "py312", From e85865fb334a7c78b37fa289bb740db3ee7ad5a8 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sun, 28 Jun 2026 23:46:02 +0200 Subject: [PATCH 24/53] build: add tox env for Python 3.14 --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 01374f6..5aee125 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -221,6 +221,7 @@ env_list = [ "py311", "py312", "py313", + "py314", "fmt-py", "fmt-toml", "lint-py", From bba23a378efa60300028acd2a778b63b5aecce01 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 19:36:12 +0200 Subject: [PATCH 25/53] build: set dependency lower bounds --- pyproject.toml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5aee125..6540045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,20 +32,19 @@ issues = "https://github.com/ivankorobkov/python-inject/issues" [dependency-groups] dev = [ - "ipython", - "ruff", - "mypy", - "yamllint", - "typing-extensions", + "ipython>=8.39.0", + "ruff>=0.15.20", + "mypy>=2.1.0", + "yamllint>=1.38.0", + "typing-extensions>=4.15.0", { include-group = "tests" }, ] tests = [ - "pytest", - "pytest-cov", + "pytest>=9.1.1", + "pytest-cov>=7.1.0", ] - [build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" From 8f6496e00157fa804e68f496b22b7a02de07b31f Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 21:03:04 +0200 Subject: [PATCH 26/53] build: adopt uv for dependency management and CI --- .github/workflows/tests.yaml | 75 +-- .gitignore | 4 + README.md | 3 + makefile | 21 +- pyproject.toml | 5 + uv.lock | 996 +++++++++++++++++++++++++++++++++++ 6 files changed, 1056 insertions(+), 48 deletions(-) create mode 100644 uv.lock diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 70bc93b..199c088 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -5,76 +5,81 @@ name: tests push: pull_request: +env: + UV_PYTHON_DOWNLOADS: never + UV_LOCKED: "true" + jobs: misc: name: "Linting configs" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: "Checkout" + uses: actions/checkout@v4 with: fetch-depth: 12 - - - uses: docker://docker.io/tamasfe/taplo:latest - name: "Lint TOMLs" + - name: "Setup Python" + uses: actions/setup-python@v5 + with: + python-version: "3.14" + - name: "Setup uv" + uses: astral-sh/setup-uv@v7 + - name: "Check uv.lock" + run: uv lock --check + - name: "Lint TOMLs" + uses: docker://docker.io/tamasfe/taplo:latest with: args: fmt --check --diff - - - uses: actions/setup-python@v5 - - name: "Install tox" - run: | - pip install tox - name: "Lint YAMLs" - run: | - tox -e lint-yaml + run: uv run tox -e lint-yaml lint: name: "Linting" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: "Checkout" + uses: actions/checkout@v4 + - name: "Setup Python" + uses: actions/setup-python@v5 with: python-version: "3.14" - - name: "Install tox" - run: | - pip install tox + - name: "Setup uv" + uses: astral-sh/setup-uv@v7 - name: "Lint formatting" - run: | - tox -e lint-py + run: uv run tox -e lint-py # - name: "Type checking" TODO(trin94): revisit linter config and re-enable -# run: | -# tox -e lint-mypy +# run: uv run tox -e lint-mypy types: name: "Type checking (public API)" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: "Checkout" + uses: actions/checkout@v4 + - name: "Setup Python" + uses: actions/setup-python@v5 with: python-version: "3.14" - - name: "Install tox" - run: | - pip install tox + - name: "Setup uv" + uses: astral-sh/setup-uv@v7 - name: "Type checking (public API)" - run: | - tox -e lint-types + run: uv run tox -e lint-types tests: name: "Run unit tests" runs-on: ubuntu-24.04 strategy: - fail-fast: true matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: "Checkout" + uses: actions/checkout@v4 + - name: "Setup Python" + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: "Installing tox" - run: pip install tox - - name: "Executing unit tests" - run: | - tox run -e ${{ matrix.python-version }} + - name: "Setup uv" + uses: astral-sh/setup-uv@v7 + - name: "Run tests" + run: uv run tox -e ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index 43bb7e6..ff9a5c0 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,10 @@ docs/_build/ # pyenv virtualenv .python-version +# uv +.venv/ +.uv-cache/ + # Generated files _version.py _version.pyi diff --git a/README.md b/README.md index b352d9b..bd7ae42 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,9 @@ def foo(user): ## License Apache License 2.0 +## Development +We use [uv](https://docs.astral.sh/uv/getting-started/installation/) and `make` for development. Install both, then run `make init` to get started. + ## Contributors * Ivan Korobkov [@ivankorobkov](https://github.com/ivankorobkov) * Jaime Wyant [@jaimewyant](https://github.com/jaimewyant) diff --git a/makefile b/makefile index bcba55f..6513daa 100644 --- a/makefile +++ b/makefile @@ -4,19 +4,16 @@ SHELL := bash MAKEFLAGS += --no-builtin-rules \ --warn-undefined-variables -.PHONY: dist pytest test +.PHONY: init dist upload clean pytest test -dist: - if ! python3 -m pip freeze | grep -q build; then python3 -m pip install --upgrade build; fi - python3 -m build --outdir=dist --sdist --wheel ./ +init: + uv sync -install_dev: - python3 -m pip install --upgrade pip - python3 -m pip install --upgrade --editable=./ +dist: + uv build upload: - if ! python3 -m pip freeze | grep -q twine; then python3 -m pip install --upgrade twine; fi - python3 -m twine upload dist/* + uv publish clean: rm -rf ./build/* @@ -25,9 +22,7 @@ clean: rm -rf ./.pytest_cache pytest: - if ! command -v pytest &>/dev/null; then python3 -m pip install --upgrade pytest; fi - pytest tests + uv run pytest tests test: - if ! command -v nosetests &>/dev/null; then python3 -m pip install --upgrade nose; fi - nosetests tests + uv run --with nose nosetests tests diff --git a/pyproject.toml b/pyproject.toml index 6540045..ec30984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ dev = [ "mypy>=2.1.0", "yamllint>=1.38.0", "typing-extensions>=4.15.0", + "tox>=4.56.1", + "tox-uv>=1.35.2", { include-group = "tests" }, ] @@ -45,6 +47,9 @@ tests = [ "pytest-cov>=7.1.0", ] +[tool.uv] +required-version = ">=0.11" + [build-system] requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..895a4f5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,996 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version < '3.11'", +] + +[[package]] +name = "ast-serialize" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "cachetools" +version = "7.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bd/b01188f0de73ee8b6597cf20c63fccd898ad31405772f15165cb61a62c00/coverage-7.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:360bec1f58e7243e3405d3bdf7a1a8115aa9b448d54dc7cd6f7b7e0e9406b62e", size = 220378, upload-time = "2026-06-22T23:07:38.925Z" }, + { url = "https://files.pythonhosted.org/packages/33/eb/f7aa3cb46500b709070c8d12335446971ec8b8c2ea155fea05d2000b4b1f/coverage-7.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed68faa5e85de2f3e400bc3f122e5c82735a58c8bb24b9f63a2215954ba17b2d", size = 220895, upload-time = "2026-06-22T23:07:41.536Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/b41b8499fc9060ca40ad2a197d301155be1ead398f0f0bfdb27b2b4a660f/coverage-7.14.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:830c1fca669c572dec37ce9c838224ee45aac5be0f6961edf871e82e49d6537c", size = 247631, upload-time = "2026-06-22T23:07:43.244Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e9ecea1307c6a549c223842cccbd5d55193cc27b82f26338782d4355047c/coverage-7.14.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a64caee2193563601dbaaa55fe2dcf597debef04a2f8f1fa8a07aa4bb7ac7a1e", size = 249460, upload-time = "2026-06-22T23:07:45.147Z" }, + { url = "https://files.pythonhosted.org/packages/59/cb/3821542809b7b726296fd364ed1c23d10a5770f1469957010c3b4bc5d408/coverage-7.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0096fd7559178f0cc9cf088f2dbd2a02ef85bacaa69732c633517286b4494610", size = 251324, upload-time = "2026-06-22T23:07:46.875Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/f34f66f0ff152189ccc7b3f0582cf7909e239cb3b8c214362ed2149719b8/coverage-7.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6197e5a00183c11a8ce7c6abd18be1a9189fd8399084ffc95196f4f0db4f2137", size = 253237, upload-time = "2026-06-22T23:07:48.352Z" }, + { url = "https://files.pythonhosted.org/packages/22/81/aa363fa95d14fc892bd5de80edadc8d7cce584a0f6376f6336e492618e67/coverage-7.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7dfe427045520d6abca33687dfef767b4f635015893a1816c5decb12eb72ce18", size = 248344, upload-time = "2026-06-22T23:07:49.896Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/dc8a149441a3fea611cbbaf46bb12099adbe08f69903df1794581b0504b8/coverage-7.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a3f142070eb7b82fc4085a55d887396f9c4e21250bccebe2ba22502c45b9647", size = 249365, upload-time = "2026-06-22T23:07:51.464Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a2/0004127deee122e020be24a4d86ce72fa14ae28198811b945aabf91293b5/coverage-7.14.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64b2055bb6e0dc945af35cdeceb3633e6ed9273475ef3af85592410fd6803803", size = 247369, upload-time = "2026-06-22T23:07:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/3654c004f4df4f0c5a9643d9abaed5b26e5d3c1d0ecabe788786cb425efa/coverage-7.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1551b4caac3e3ec9f2bfcec6bf3776e01c0edbdd2e240431a50ca1a1aac72c27", size = 251182, upload-time = "2026-06-22T23:07:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2f/7bdcdf1e7c4d0632648852768063c25582a0a747bb5f8036a04e211e7eb7/coverage-7.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:583d50d59142f8549470bd6390471d0fe8b8c8d69d6a0f28ac71e05380cef640", size = 247639, upload-time = "2026-06-22T23:07:56.254Z" }, + { url = "https://files.pythonhosted.org/packages/03/dc/0e01b071f69021d262a51ce39345dd6bc194465db0acfc7b34fd89e6b787/coverage-7.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0bb8a6bc7015efdf8a928753b25da1b9ca2d6f24ef04d2ee0688e486f32aae7", size = 248242, upload-time = "2026-06-22T23:07:57.692Z" }, + { url = "https://files.pythonhosted.org/packages/1c/51/08279e6ebe3479bf705db5fdc1a968e44ba1567e4cbc567f76b45f5e646e/coverage-7.14.3-cp310-cp310-win32.whl", hash = "sha256:d48400185564042287dc487c1f016a3397f18ab4f4c5d5ec36edc218f7ffa35b", size = 222431, upload-time = "2026-06-22T23:07:59.094Z" }, + { url = "https://files.pythonhosted.org/packages/40/2f/5c56670781fee5722ef0c415a74750c9a033bfacdb9d07b1493a0308108d/coverage-7.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:eadea7aba74e40adee867a8c0eec17b820b061d308a4b014f7a0e118c2b0aa61", size = 223059, upload-time = "2026-06-22T23:08:00.662Z" }, + { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, + { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, + { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, + { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, + { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, + { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, + { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, + { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, + { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, + { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, + { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, + { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "inject" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "tox" }, + { name = "tox-uv" }, + { name = "typing-extensions" }, + { name = "yamllint" }, +] +tests = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "ipython", specifier = ">=8.39.0" }, + { name = "mypy", specifier = ">=2.1.0" }, + { name = "pytest", specifier = ">=9.1.1" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "ruff", specifier = ">=0.15.20" }, + { name = "tox", specifier = ">=4.56.1" }, + { name = "tox-uv", specifier = ">=1.35.2" }, + { name = "typing-extensions", specifier = ">=4.15.0" }, + { name = "yamllint", specifier = ">=1.38.0" }, +] +tests = [ + { name = "pytest", specifier = ">=9.1.1" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "librt" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/10/37fd9e9ba96cb0bd742dfb20fc3d082e54bdbec759d7300df927f360ef07/librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", size = 141706, upload-time = "2026-05-10T18:15:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/1b1466f358e4a0b728051f69bc27e67b432c6eaa2e05b88db49d3785ae0d/librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", size = 142605, upload-time = "2026-05-10T18:15:18.148Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/ed26dd2f6bc9a0baf48306433e579e8d354d70b2bcb78134ed950a5d0e1e/librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", size = 476555, upload-time = "2026-05-10T18:15:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/11891191c0e0a3fd617724e891f6e67a71a7658974a892b9a9a97fdb2977/librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", size = 468434, upload-time = "2026-05-10T18:15:20.87Z" }, + { url = "https://files.pythonhosted.org/packages/6f/50/5ec949d7f9ce1a07af903aa3e13abb98b717923bdead6e719b2f824ccc07/librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", size = 496918, upload-time = "2026-05-10T18:15:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c4/177336c7524e34875a38bf668e88b193a6723a4eb4045d07f74df6e1506c/librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", size = 490334, upload-time = "2026-05-10T18:15:24.2Z" }, + { url = "https://files.pythonhosted.org/packages/13/1f/da3112f7569eda3b49f9a2629bae1fe059812b6085df16c885f6454dff49/librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", size = 511287, upload-time = "2026-05-10T18:15:26.226Z" }, + { url = "https://files.pythonhosted.org/packages/fa/94/03fec301522e172d105581431223be56b27594ff46440ebfbb658a3735d5/librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", size = 517202, upload-time = "2026-05-10T18:15:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6e/339f6e5a7b413ce014f1917a756dae630fe59cc99f34153205b1cb540901/librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", size = 497517, upload-time = "2026-05-10T18:15:29.614Z" }, + { url = "https://files.pythonhosted.org/packages/cd/43/acdd5ce317cb46e8253ca9bfbdb8b12e68a24d745949336a7f3d5fb79ba0/librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", size = 538878, upload-time = "2026-05-10T18:15:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/29/b5/7a25bb12e3172839f647f196b3e988318b7bb1ca7501732a225c4dce2ec0/librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", size = 100070, upload-time = "2026-05-10T18:15:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0d/ebbcf4d77999c02c937b05d2b90ff4cd4dcc7e9a365ba132329ac1fe7a0f/librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", size = 117918, upload-time = "2026-05-10T18:15:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, + { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, + { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, + { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, + { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, + { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, + { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, + { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, + { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, + { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, + { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, + { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, + { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, + { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, + { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, + { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, + { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, + { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, + { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" }, + { url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" }, + { url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" }, + { url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/62/0fe346fe380b1aafaf819c8cb195d3241bb4f355f908e6339814131a830b/pyproject_api-1.10.1.tar.gz", hash = "sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba", size = 23477, upload-time = "2026-05-28T14:22:14.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/d7/29e1e5e882f79133631f7bcace42d23db493f616463c157a1ab614bf69dd/pyproject_api-1.10.1-py3-none-any.whl", hash = "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a", size = 12992, upload-time = "2026-05-28T14:22:12.711Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tox" +version = "4.56.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "python-discovery" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli-w" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6a/d20d405fc6661902ff803a9fa048d8aae27597c3b5dc750369ded82d08f7/tox-4.56.1.tar.gz", hash = "sha256:db1c2610802553189cf40de251661d066a635ee0ed9bf2a60093b5f1a7f36ef8", size = 283155, upload-time = "2026-06-25T06:18:36.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/97/560a5dfde154619d9643b1e208119dddc29bbb35a38a4ce4d095c16cf8f0/tox-4.56.1-py3-none-any.whl", hash = "sha256:4d06b925c4dd67872099b39c5a46fba79a2169c5f6e32060f95a8b1181f0ef55", size = 216469, upload-time = "2026-06-25T06:18:35.229Z" }, +] + +[[package]] +name = "tox-uv" +version = "1.35.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tox-uv-bare" }, + { name = "uv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/dc/6e9994c799bdbb309f829dd6b8d98764dd0757302f3433c380438a3a127b/tox_uv-1.35.2-py3-none-any.whl", hash = "sha256:2d99b0e3c782ba49e7cbe521c8d344758595961b17a3633738d67096641c1bde", size = 6565, upload-time = "2026-05-05T01:34:16.07Z" }, +] + +[[package]] +name = "tox-uv-bare" +version = "1.35.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tox" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/cb/168dc1ccf24e4065a9a0a33df55709ed2b5eb73bd2b13ddd53187e5dffb8/tox_uv_bare-1.35.2.tar.gz", hash = "sha256:49e28a804c97f23ea17e25859960c0fa78f35bccb7e14344cfd840e89a9aade9", size = 32333, upload-time = "2026-05-05T01:34:18.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "uv" +version = "0.11.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/8e/2777bf40df3c634f2a522baaa2995d2bd5901461fc5d8730c04c39fa6c75/uv-0.11.25.tar.gz", hash = "sha256:458e731778e7b5cc870710397859c23e766703e7bc0695f23b3eb15080745ba6", size = 4329198, upload-time = "2026-06-27T00:49:00.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a7/dc836daca0db76178cfe4e40ee95fa2c5f96a3d7b3cda0bdde5291aef8b8/uv-0.11.25-py3-none-linux_armv6l.whl", hash = "sha256:d6f965a79fc7539a12139ce981caa0cbf7d9d3bd4ea3daadaf174ab4d7fb6e42", size = 25153689, upload-time = "2026-06-27T00:48:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/d60eabb616db232ed0e682c55d858d0980175a92336429c4e0ee82a160e6/uv-0.11.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:610650cbaa0a9b18015da39d2c28d736d287a5a124e49296d8fdef5e4022e980", size = 24149272, upload-time = "2026-06-27T00:48:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/ab/fd/9f72373e340c6abcd3eb9257d69b442434b65e90e1f7f53f333abdac11b2/uv-0.11.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f7a78fc8d0c5e764e9fa39c99066db47a0bc465b023feed90812e3c0a6b5eb0d", size = 22885453, upload-time = "2026-06-27T00:48:16.641Z" }, + { url = "https://files.pythonhosted.org/packages/ee/c1/7cbb3f6b4a021ce7c1c453d1412d3b4b68d7d7585d15d91a57e9ab0a317e/uv-0.11.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e3480640983e0b8e509eeb67882837e620bdd820f8776948a5f13ebbb4481d04", size = 24935172, upload-time = "2026-06-27T00:48:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/df/d4/862a534ab4aae28ad2c9145ea5af3021eefe225e4e1c983278bb34b53b9e/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b180b12237b4e04692491fc6796584a9a8bdf4c7332bd2a769caf096b97885d0", size = 24665125, upload-time = "2026-06-27T00:48:22.408Z" }, + { url = "https://files.pythonhosted.org/packages/91/2a/70ef2f2a212d89d23f90e1c8017c080fae93bec21cd9f14795498a6e6f89/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69d14ffd0a4b050f8a70f64aacb09b8dfdfb1cb30a6351fb17b48f273f95c58c", size = 24653836, upload-time = "2026-06-27T00:48:24.858Z" }, + { url = "https://files.pythonhosted.org/packages/44/2b/a7beea057778f092daa5aeda7aeb3f8bfa78f4a0622245adb449fdbb3d20/uv-0.11.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ef11d9967a38109e6e8e3d20d1f743fa08033c32bce274d6ccd9a9abb5d305", size = 26069304, upload-time = "2026-06-27T00:48:28.05Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e8/bb8ab595a27035b565cedd5d601ae2b85cf6aba6f68cd6a97eae88dd7250/uv-0.11.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3febca65ec5bc336ddaf7e4f724704f2c894c16839723df14865ee00b4acf38d", size = 26992784, upload-time = "2026-06-27T00:48:30.584Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/bbfa0653e992877e5ea78ab3186fb6a5a122ebef5913b6809ef6d06fcf9b/uv-0.11.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:850ba0018ff170c3a9baaf9b5fe8b23393b6b77ee4ea6b2e2315fdb8d7c388f7", size = 26151002, upload-time = "2026-06-27T00:48:33.739Z" }, + { url = "https://files.pythonhosted.org/packages/91/01/f5a01fd777ce501cc59eb71775f3bbacac258a65a64939f07804c2279c98/uv-0.11.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2bc05e17ae3e1f232abf93e7dcfb3b68702dfcde34a00c29cbce7e07d1ecbfb", size = 26334122, upload-time = "2026-06-27T00:48:36.247Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c4/f8b3b6b1bb48a452d98c02909e229f7ae317300618d6dc334c883742339e/uv-0.11.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:57fbd47e924242fd347d0c209d95711d8ea61db8d8780962d0f30ccde2c854a3", size = 25055505, upload-time = "2026-06-27T00:48:39.418Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/aece221faba22804d8b4f913903358dc2d45193babf2a44a49f79eadd25b/uv-0.11.25-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f42de9e7d63a28a4fe76a522077813656de38b5acda20b4db63857d260c1ff13", size = 25660097, upload-time = "2026-06-27T00:48:42.398Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7d/b835ed56eab281054efbfbcbbe1249621908c6e4c721a878904cbed2f418/uv-0.11.25-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2c1cfe97dce56c997dfa3214bdb8955b7b34cceea7505520185e22ad99c0eb6b", size = 25759374, upload-time = "2026-06-27T00:48:45.069Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fd/28c990fd18330aa01383bae738be113f4ac5d9d414f92790947326c6781c/uv-0.11.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:fbff70ae9fa4da9fb6823ae4fdaf77a65c9520e13b6d1d0241ba56e4b121b7aa", size = 25329343, upload-time = "2026-06-27T00:48:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/70/48/16ff9dbe5f308cb547e227971cbcaf3c905797da12a4116b620e2acc09cb/uv-0.11.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:41b37e724f41eb4c3794bbdd82ddeebb4b5850d4ada8cccb2906ef9e5aa0f83b", size = 26477125, upload-time = "2026-06-27T00:48:50.251Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8b/4146a55d3ee6b2b67c3dece3ef6be220c443abafc2dc56baa2f74d6b36a8/uv-0.11.25-py3-none-win32.whl", hash = "sha256:86d4759fec9b46f61944d6e9ef1f5eaa2c5fbe2db5ddb59492d9174b08fcf39c", size = 23896598, upload-time = "2026-06-27T00:48:53.01Z" }, + { url = "https://files.pythonhosted.org/packages/64/66/784f9fb457b640dbb96cdd175f4902235b42ffe740dbaee9ea5fc649d8b2/uv-0.11.25-py3-none-win_amd64.whl", hash = "sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859", size = 26862205, upload-time = "2026-06-27T00:48:55.667Z" }, + { url = "https://files.pythonhosted.org/packages/85/cb/d7bb121261db0b829f38b1e86b80d8103afd050b8a5c5c9fe4a988d2f1fc/uv-0.11.25-py3-none-win_arm64.whl", hash = "sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e", size = 25153588, upload-time = "2026-06-27T00:48:58.495Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, +] + +[[package]] +name = "yamllint" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathspec" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/a0/8fc2d68e132cf918f18273fdc8a1b8432b60d75ac12fdae4b0ef5c9d2e8d/yamllint-1.38.0.tar.gz", hash = "sha256:09e5f29531daab93366bb061e76019d5e91691ef0a40328f04c927387d1d364d", size = 142446, upload-time = "2026-01-13T07:47:53.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/92/aed08e68de6e6a3d7c2328ce7388072cd6affc26e2917197430b646aed02/yamllint-1.38.0-py3-none-any.whl", hash = "sha256:fc394a5b3be980a4062607b8fdddc0843f4fa394152b6da21722f5d59013c220", size = 68940, upload-time = "2026-01-13T07:47:51.343Z" }, +] From c1367a4ce01a9260f09d6db09d02cb66c3ce90dc Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 21:05:38 +0200 Subject: [PATCH 27/53] ci: remove Travis config --- .travis.yml | 15 --------------- README.md | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b46d518..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -dist: xenial -language: python -matrix: - include: - - python: "3.6" - - python: "3.7" - env: TOXENV=py37 - - python: "3.8" - env: TOXENV=py37 -install: - - python -m pip install --upgrade --editable=./ -script: - - nosetests test $EXTRA_ARGS - - if [ "$TOXENV" = "py37" ]; then nosetests test37; fi diff --git a/README.md b/README.md index bd7ae42..70d77af 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# python-inject [![Build Status](https://travis-ci.org/ivankorobkov/python-inject.svg?branch=master)](https://travis-ci.org/ivankorobkov/python-inject) +# python-inject [![Build Status](https://github.com/ivankorobkov/python-inject/actions/workflows/tests.yaml/badge.svg?branch=master)](https://github.com/ivankorobkov/python-inject/actions/workflows/tests.yaml) Dependency injection the python way, the good way. ## Key features From 80d82b4cc762dcebf5eee95ee91dcc8b1a3fe2ee Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 21:06:45 +0200 Subject: [PATCH 28/53] build: remove nosetests target --- makefile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/makefile b/makefile index 6513daa..a864df6 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ SHELL := bash MAKEFLAGS += --no-builtin-rules \ --warn-undefined-variables -.PHONY: init dist upload clean pytest test +.PHONY: init dist upload clean pytest init: uv sync @@ -23,6 +23,3 @@ clean: pytest: uv run pytest tests - -test: - uv run --with nose nosetests tests From 01e4bdb340563135503cda01e8ff2768f3f7f5e3 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 22:11:03 +0200 Subject: [PATCH 29/53] build: install tox env tools from the lock --- pyproject.toml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ec30984..317f74e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,15 +33,19 @@ issues = "https://github.com/ivankorobkov/python-inject/issues" [dependency-groups] dev = [ "ipython>=8.39.0", - "ruff>=0.15.20", - "mypy>=2.1.0", - "yamllint>=1.38.0", "typing-extensions>=4.15.0", - "tox>=4.56.1", - "tox-uv>=1.35.2", + { include-group = "tox" }, + { include-group = "ruff" }, + { include-group = "mypy" }, + { include-group = "yamllint" }, { include-group = "tests" }, ] +tox = ["tox>=4.56.1", "tox-uv>=1.35.2"] +ruff = ["ruff>=0.15.20"] +mypy = ["mypy>=2.1.0"] +yamllint = ["yamllint>=1.38.0"] + tests = [ "pytest>=9.1.1", "pytest-cov>=7.1.0", @@ -216,8 +220,6 @@ exclude_also = [ # Tox config [tool.tox] -requires = ["tox>=4.23", "tox-uv>=1.13"] -runner = "uv-venv-lock-runner" skip_missing_interpreters = true env_list = [ @@ -251,6 +253,7 @@ lint = [ # default env [tool.tox.env_run_base] +runner = "uv-venv-lock-runner" description = "Run unit tests with coverage report ({env_name})" use_develop = true dependency_groups = ["tests"] @@ -259,7 +262,7 @@ commands = [["pytest", { replace = "posargs", extend = true }]] # tox envs [tool.tox.env.lint-py] description = "Lint python files" -deps = ["ruff"] +dependency_groups = ["ruff"] skip_install = true commands = [ [ @@ -281,7 +284,7 @@ commands = [ [tool.tox.env.lint-types] description = "Type-check the public-API typing guards" -deps = ["mypy"] +dependency_groups = ["mypy"] commands = [ [ "mypy", @@ -293,7 +296,7 @@ commands = [ [tool.tox.env.lint-mypy] description = "Type checking" -deps = ["mypy"] +dependency_groups = ["mypy"] commands = [["mypy", { replace = "posargs", default = ["{tox_root}"], extend = true }]] [tool.tox.env.lint-toml] @@ -307,7 +310,7 @@ commands = [ [tool.tox.env.lint-yaml] description = "Lint YAML files" -deps = ["yamllint"] +dependency_groups = ["yamllint"] skip_install = true commands = [ [ @@ -321,7 +324,7 @@ commands = [ [tool.tox.env.fmt-py] description = "Format python files" -deps = ["ruff"] +dependency_groups = ["ruff"] skip_install = true commands = [ [ From c6f5527fd6b14fdaa255db10d341a1e731ff7066 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 22:11:24 +0200 Subject: [PATCH 30/53] build: drop unused ipython dev dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 317f74e..41fdc0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ issues = "https://github.com/ivankorobkov/python-inject/issues" [dependency-groups] dev = [ - "ipython>=8.39.0", "typing-extensions>=4.15.0", { include-group = "tox" }, { include-group = "ruff" }, From 3d004aee4e0b635fb7211636140a60580fcdf740 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 22:20:53 +0200 Subject: [PATCH 31/53] ci: harden the tests workflow --- .github/workflows/tests.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 199c088..597d7ab 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -28,7 +28,7 @@ jobs: - name: "Check uv.lock" run: uv lock --check - name: "Lint TOMLs" - uses: docker://docker.io/tamasfe/taplo:latest + uses: docker://docker.io/tamasfe/taplo:0.10.0 with: args: fmt --check --diff - name: "Lint YAMLs" @@ -70,6 +70,7 @@ jobs: name: "Run unit tests" runs-on: ubuntu-24.04 strategy: + fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: @@ -82,4 +83,6 @@ jobs: - name: "Setup uv" uses: astral-sh/setup-uv@v7 - name: "Run tests" - run: uv run tox -e ${{ matrix.python-version }} + run: >- + uv run tox -e ${{ matrix.python-version }} + --skip-missing-interpreters false From f93f28713e73e9c55d6098a0a88bb5bd395d448a Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 22:25:53 +0200 Subject: [PATCH 32/53] chore: tidy build config --- .gitignore | 1 - makefile | 4 +++- pyproject.toml | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ff9a5c0..be475c7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ pip-delete-this-directory.txt .tox/ .coverage .cache -nosetests.xml coverage.xml # Translations diff --git a/makefile b/makefile index a864df6..7320188 100644 --- a/makefile +++ b/makefile @@ -16,10 +16,12 @@ upload: uv publish clean: - rm -rf ./build/* rm -rf ./dist/* rm -rf ./.mypy_cache rm -rf ./.pytest_cache + rm -rf ./.ruff_cache + rm -rf ./.tox + rm -rf ./.uv-cache pytest: uv run pytest tests diff --git a/pyproject.toml b/pyproject.toml index 41fdc0d..732ac02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -262,7 +262,6 @@ commands = [["pytest", { replace = "posargs", extend = true }]] [tool.tox.env.lint-py] description = "Lint python files" dependency_groups = ["ruff"] -skip_install = true commands = [ [ "ruff", @@ -310,7 +309,6 @@ commands = [ [tool.tox.env.lint-yaml] description = "Lint YAML files" dependency_groups = ["yamllint"] -skip_install = true commands = [ [ "yamllint", @@ -324,7 +322,6 @@ commands = [ [tool.tox.env.fmt-py] description = "Format python files" dependency_groups = ["ruff"] -skip_install = true commands = [ [ "ruff", From 99cbb48858b8d1566357f205e45edf40a476d599 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 22:29:48 +0200 Subject: [PATCH 33/53] build: update uv.lock --- uv.lock | 252 +++++--------------------------------------------------- 1 file changed, 21 insertions(+), 231 deletions(-) diff --git a/uv.lock b/uv.lock index 895a4f5..99a86ad 100644 --- a/uv.lock +++ b/uv.lock @@ -3,8 +3,7 @@ revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.15'", - "python_full_version >= '3.11' and python_full_version < '3.15'", - "python_full_version < '3.11'", + "python_full_version < '3.15'", ] [[package]] @@ -47,15 +46,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, ] -[[package]] -name = "asttokens" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, -] - [[package]] name = "cachetools" version = "7.1.4" @@ -177,15 +167,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "decorator" -version = "5.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, -] - [[package]] name = "distlib" version = "0.4.3" @@ -200,22 +181,13 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "executing" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, -] - [[package]] name = "filelock" version = "3.29.4" @@ -240,8 +212,6 @@ source = { editable = "." } [package.dev-dependencies] dev = [ - { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "mypy" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -251,16 +221,28 @@ dev = [ { name = "typing-extensions" }, { name = "yamllint" }, ] +mypy = [ + { name = "mypy" }, +] +ruff = [ + { name = "ruff" }, +] tests = [ { name = "pytest" }, { name = "pytest-cov" }, ] +tox = [ + { name = "tox" }, + { name = "tox-uv" }, +] +yamllint = [ + { name = "yamllint" }, +] [package.metadata] [package.metadata.requires-dev] dev = [ - { name = "ipython", specifier = ">=8.39.0" }, { name = "mypy", specifier = ">=2.1.0" }, { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-cov", specifier = ">=7.1.0" }, @@ -270,86 +252,17 @@ dev = [ { name = "typing-extensions", specifier = ">=4.15.0" }, { name = "yamllint", specifier = ">=1.38.0" }, ] +mypy = [{ name = "mypy", specifier = ">=2.1.0" }] +ruff = [{ name = "ruff", specifier = ">=0.15.20" }] tests = [ { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-cov", specifier = ">=7.1.0" }, ] - -[[package]] -name = "ipython" -version = "8.39.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.11'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, -] - -[[package]] -name = "ipython" -version = "9.15.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.15'", - "python_full_version >= '3.11' and python_full_version < '3.15'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, -] - -[[package]] -name = "ipython-pygments-lexers" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, -] - -[[package]] -name = "jedi" -version = "0.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "parso" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +tox = [ + { name = "tox", specifier = ">=4.56.1" }, + { name = "tox-uv", specifier = ">=1.35.2" }, ] +yamllint = [{ name = "yamllint", specifier = ">=1.38.0" }] [[package]] name = "librt" @@ -436,18 +349,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] -[[package]] -name = "matplotlib-inline" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, -] - [[package]] name = "mypy" version = "2.1.0" @@ -525,15 +426,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "parso" -version = "0.8.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, -] - [[package]] name = "pathspec" version = "1.1.1" @@ -543,18 +435,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, -] - [[package]] name = "platformdirs" version = "4.10.0" @@ -573,64 +453,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - -[[package]] -name = "ptyprocess" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -787,20 +609,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] -[[package]] -name = "stack-data" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "asttokens" }, - { name = "executing" }, - { name = "pure-eval" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, -] - [[package]] name = "tomli" version = "2.4.1" @@ -913,15 +721,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, ] -[[package]] -name = "traitlets" -version = "5.15.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -973,15 +772,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, ] -[[package]] -name = "wcwidth" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, -] - [[package]] name = "yamllint" version = "1.38.0" From ae0eb3d4a02b3397af55f64f9fa64daadd12325c Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Mon, 29 Jun 2026 23:33:19 +0200 Subject: [PATCH 34/53] ci: update github actions and add dependabot --- .github/dependabot.yml | 8 ++++++++ .github/workflows/tests.yaml | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f0e6100 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +--- +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 597d7ab..a8e2e5a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,15 +16,15 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout" - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 12 - name: "Setup Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.14" - name: "Setup uv" - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.2.0 - name: "Check uv.lock" run: uv lock --check - name: "Lint TOMLs" @@ -39,13 +39,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout" - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: "Setup Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.14" - name: "Setup uv" - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.2.0 - name: "Lint formatting" run: uv run tox -e lint-py # - name: "Type checking" TODO(trin94): revisit linter config and re-enable @@ -56,13 +56,13 @@ jobs: runs-on: ubuntu-24.04 steps: - name: "Checkout" - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: "Setup Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.14" - name: "Setup uv" - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.2.0 - name: "Type checking (public API)" run: uv run tox -e lint-types @@ -75,13 +75,13 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - name: "Checkout" - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: "Setup Python" - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: "Setup uv" - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.2.0 - name: "Run tests" run: >- uv run tox -e ${{ matrix.python-version }} From d56ee97cc73c2eaa0c1992f1ff29702795a72746 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Wed, 1 Jul 2026 22:09:11 +0200 Subject: [PATCH 35/53] build: replace make and tox with just --- .github/workflows/tests.yaml | 28 +++--- .gitignore | 1 - Justfile | 87 ++++++++++++++++++ README.md | 11 ++- makefile | 27 ------ pyproject.toml | 147 +---------------------------- uv.lock | 174 ----------------------------------- 7 files changed, 115 insertions(+), 360 deletions(-) create mode 100644 Justfile delete mode 100644 makefile diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a8e2e5a..2515989 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,14 +25,14 @@ jobs: python-version: "3.14" - name: "Setup uv" uses: astral-sh/setup-uv@v8.2.0 + - name: "Setup just" + uses: extractions/setup-just@v4 - name: "Check uv.lock" - run: uv lock --check + run: just lock-check - name: "Lint TOMLs" - uses: docker://docker.io/tamasfe/taplo:0.10.0 - with: - args: fmt --check --diff + run: just lint-toml - name: "Lint YAMLs" - run: uv run tox -e lint-yaml + run: just lint-yaml lint: name: "Linting" @@ -46,10 +46,10 @@ jobs: python-version: "3.14" - name: "Setup uv" uses: astral-sh/setup-uv@v8.2.0 + - name: "Setup just" + uses: extractions/setup-just@v4 - name: "Lint formatting" - run: uv run tox -e lint-py -# - name: "Type checking" TODO(trin94): revisit linter config and re-enable -# run: uv run tox -e lint-mypy + run: just lint types: name: "Type checking (public API)" @@ -63,8 +63,12 @@ jobs: python-version: "3.14" - name: "Setup uv" uses: astral-sh/setup-uv@v8.2.0 + - name: "Setup just" + uses: extractions/setup-just@v4 - name: "Type checking (public API)" - run: uv run tox -e lint-types + run: just public-typing +# - name: "Type checking (full)" TODO: re-enable once green +# run: just typing tests: name: "Run unit tests" @@ -82,7 +86,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: "Setup uv" uses: astral-sh/setup-uv@v8.2.0 + - name: "Setup just" + uses: extractions/setup-just@v4 - name: "Run tests" - run: >- - uv run tox -e ${{ matrix.python-version }} - --skip-missing-interpreters false + run: just test ${{ matrix.python-version }} diff --git a/.gitignore b/.gitignore index be475c7..077efa8 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports -.tox/ .coverage .cache coverage.xml diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..de8206b --- /dev/null +++ b/Justfile @@ -0,0 +1,87 @@ +set shell := ["bash", "-euc"] + +python_oldest := "3.10" # Oldest supported version +python_latest := "3.14" # Newest supported version +python := "3.14" # Default version for dev loop +python_versions := "3.10 3.11 3.12 3.13 3.14" + +taplo := "uvx taplo@0.9.3" + +@_default: + just --list --unsorted + +fmt: + uv run --only-group ruff ruff format . + uv run --only-group ruff ruff check --fix --show-fixes . + {{ taplo }} format + just --fmt + +# Dev loop +dev: fmt lint test + +# Full check +check: lock-check lint lint-toml lint-yaml public-typing typing test-all + +# Install locked dependencies +[group('project')] +init: + uv sync + +[group('project')] +dist: + uv build + +[group('project')] +publish: + uv publish + +[group('project')] +clean: + rm -rf dist .mypy_cache .pytest_cache .ruff_cache .uv-cache + +[group('test')] +test version=python *args: + uv run --python {{ version }} --no-default-groups --group tests pytest {{ args }} + +# Test all supported Python versions +[group('test')] +test-all: + for v in {{ python_versions }}; do just test "$v"; done + +[group('test')] +coverage version=python: + uv run --python {{ version }} --no-default-groups --group tests \ + pytest --cov=. --cov-branch --cov-report=term-missing:skip-covered + +# Type check our code +[group('typing')] +typing version=python_oldest: + # --python-version is a mypy target flag, not a host interpreter + uv run --no-default-groups --group mypy mypy --python-version {{ version }} . + +# Type check as if we're a consumer of the library +[group('typing')] +public-typing version=python_oldest: + uv run --no-default-groups --group mypy mypy --python-version {{ version }} typing_checks + +[group('typing')] +public-typing-all: + for v in {{ python_oldest }} {{ python_latest }}; do just public-typing "$v"; done + +[group('lint')] +lint: + uv run --only-group ruff ruff format --diff . + uv run --only-group ruff ruff check . + +[group('lint')] +lint-toml: + {{ taplo }} lint + {{ taplo }} format --check --diff + +[group('lint')] +lint-yaml: + uv run --only-group yamllint yamllint --strict . + +[group('lint')] +lock-check: + uv lock --check diff --git a/README.md b/README.md index 70d77af..f95d9df 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,16 @@ def foo(user): Apache License 2.0 ## Development -We use [uv](https://docs.astral.sh/uv/getting-started/installation/) and `make` for development. Install both, then run `make init` to get started. +We use [uv](https://docs.astral.sh/uv/getting-started/installation/) and +[just](https://github.com/casey/just#installation) for development. Install both, +then run `just init` to set up the environment. + +Useful recipes: + +* `just` lists all recipes +* `just dev` formats, lints, and tests on the default interpreter (the fast loop) +* `just test 3.12` runs the tests against a specific Python version +* `just check` runs the full gate before pushing ## Contributors * Ivan Korobkov [@ivankorobkov](https://github.com/ivankorobkov) diff --git a/makefile b/makefile deleted file mode 100644 index 7320188..0000000 --- a/makefile +++ /dev/null @@ -1,27 +0,0 @@ -SHELL := bash -.DELETE_ON_ERROR: -.SHELLFLAGS := -ceu -MAKEFLAGS += --no-builtin-rules \ - --warn-undefined-variables - -.PHONY: init dist upload clean pytest - -init: - uv sync - -dist: - uv build - -upload: - uv publish - -clean: - rm -rf ./dist/* - rm -rf ./.mypy_cache - rm -rf ./.pytest_cache - rm -rf ./.ruff_cache - rm -rf ./.tox - rm -rf ./.uv-cache - -pytest: - uv run pytest tests diff --git a/pyproject.toml b/pyproject.toml index 732ac02..53c6021 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,14 +33,12 @@ issues = "https://github.com/ivankorobkov/python-inject/issues" [dependency-groups] dev = [ "typing-extensions>=4.15.0", - { include-group = "tox" }, { include-group = "ruff" }, { include-group = "mypy" }, { include-group = "yamllint" }, { include-group = "tests" }, ] -tox = ["tox>=4.56.1", "tox-uv>=1.35.2"] ruff = ["ruff>=0.15.20"] mypy = ["mypy>=2.1.0"] yamllint = ["yamllint>=1.38.0"] @@ -88,7 +86,7 @@ disallow_untyped_decorators = true disallow_untyped_defs = true ignore_missing_imports = true no_implicit_optional = true -python_version = "3.11" +python_version = "3.10" warn_redundant_casts = true warn_return_any = true warn_unused_configs = true @@ -202,7 +200,7 @@ branch = true [tool.coverage.report] include_namespace_packages = true # Regexes for lines to exclude from consideration -omit = ["*/.venv/*", "*/.tox/*", "*/.uv-cache/*"] +omit = ["*/.venv/*", "*/.uv-cache/*"] exclude_also = [ # Don't complain if tests don't hit defensive assertion code: "raise NotImplementedError", @@ -215,144 +213,3 @@ exclude_also = [ "if TYPE_CHECKING:", ] - - -# Tox config -[tool.tox] -skip_missing_interpreters = true - -env_list = [ - "py310", - "py311", - "py312", - "py313", - "py314", - "fmt-py", - "fmt-toml", - "lint-py", - "lint-types", - #"lint-mypy", # TODO(pyctrl): make it green & uncomment - "lint-toml", - "lint-yaml", - "coverage", -] - -[tool.tox.labels] -fmt = [ - "fmt-py", - "fmt-toml", -] -lint = [ - "lint-py", - "lint-types", - #"lint-mypy", # TODO(pyctrl): make it green & uncomment - "lint-toml", - "lint-yaml", -] - -# default env -[tool.tox.env_run_base] -runner = "uv-venv-lock-runner" -description = "Run unit tests with coverage report ({env_name})" -use_develop = true -dependency_groups = ["tests"] -commands = [["pytest", { replace = "posargs", extend = true }]] - -# tox envs -[tool.tox.env.lint-py] -description = "Lint python files" -dependency_groups = ["ruff"] -commands = [ - [ - "ruff", - "format", - "--diff", - { replace = "posargs", default = [ - "{tox_root}", - ], extend = true }, - ], - [ - "ruff", - "check", - { replace = "posargs", default = [ - "{tox_root}", - ], extend = true }, - ], -] - -[tool.tox.env.lint-types] -description = "Type-check the public-API typing guards" -dependency_groups = ["mypy"] -commands = [ - [ - "mypy", - { replace = "posargs", default = [ - "typing_checks", - ], extend = true }, - ], -] - -[tool.tox.env.lint-mypy] -description = "Type checking" -dependency_groups = ["mypy"] -commands = [["mypy", { replace = "posargs", default = ["{tox_root}"], extend = true }]] - -[tool.tox.env.lint-toml] -description = "Lint TOML files" -allowlist_externals = ["taplo"] -skip_install = true -commands = [ - ["taplo", "lint", { replace = "posargs", extend = true }], - ["taplo", "format", "--check", "--diff", { replace = "posargs", extend = true }], -] - -[tool.tox.env.lint-yaml] -description = "Lint YAML files" -dependency_groups = ["yamllint"] -commands = [ - [ - "yamllint", - "--strict", - { replace = "posargs", default = [ - "{tox_root}", - ], extend = true }, - ], -] - -[tool.tox.env.fmt-py] -description = "Format python files" -dependency_groups = ["ruff"] -commands = [ - [ - "ruff", - "format", - { replace = "posargs", default = ["{tox_root}"], extend = true }, - ], - [ - "ruff", - "check", - "--fix", - "--show-fixes", - { replace = "posargs", default = ["{tox_root}"], extend = true }, - ], -] - -[tool.tox.env.fmt-toml] -description = "Format TOML files" -allowlist_externals = ["taplo"] -skip_install = true -commands = [["taplo", "format", { replace = "posargs", extend = true }]] - -[tool.tox.env.coverage] -description = "run coverage" -use_develop = true -dependency_groups = ["tests"] -commands = [ - [ - "pytest", - "--cov=.", - "--cov-branch", - "--cov-report=term-missing:skip-covered", - { replace = "posargs", default = ["--cov-report=xml:coverage.xml"], extend = true }, - ], -] diff --git a/uv.lock b/uv.lock index 99a86ad..38a7e3a 100644 --- a/uv.lock +++ b/uv.lock @@ -46,15 +46,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, ] -[[package]] -name = "cachetools" -version = "7.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -167,15 +158,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "distlib" -version = "0.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.1" @@ -188,15 +170,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "filelock" -version = "3.29.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" @@ -216,8 +189,6 @@ dev = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, - { name = "tox" }, - { name = "tox-uv" }, { name = "typing-extensions" }, { name = "yamllint" }, ] @@ -231,10 +202,6 @@ tests = [ { name = "pytest" }, { name = "pytest-cov" }, ] -tox = [ - { name = "tox" }, - { name = "tox-uv" }, -] yamllint = [ { name = "yamllint" }, ] @@ -247,8 +214,6 @@ dev = [ { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "ruff", specifier = ">=0.15.20" }, - { name = "tox", specifier = ">=4.56.1" }, - { name = "tox-uv", specifier = ">=1.35.2" }, { name = "typing-extensions", specifier = ">=4.15.0" }, { name = "yamllint", specifier = ">=1.38.0" }, ] @@ -258,10 +223,6 @@ tests = [ { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-cov", specifier = ">=7.1.0" }, ] -tox = [ - { name = "tox", specifier = ">=4.56.1" }, - { name = "tox-uv", specifier = ">=1.35.2" }, -] yamllint = [{ name = "yamllint", specifier = ">=1.38.0" }] [[package]] @@ -435,15 +396,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "platformdirs" -version = "4.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -462,19 +414,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pyproject-api" -version = "1.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/62/0fe346fe380b1aafaf819c8cb195d3241bb4f355f908e6339814131a830b/pyproject_api-1.10.1.tar.gz", hash = "sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba", size = 23477, upload-time = "2026-05-28T14:22:14.049Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/d7/29e1e5e882f79133631f7bcace42d23db493f616463c157a1ab614bf69dd/pyproject_api-1.10.1-py3-none-any.whl", hash = "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a", size = 12992, upload-time = "2026-05-28T14:22:12.711Z" }, -] - [[package]] name = "pytest" version = "9.1.1" @@ -507,19 +446,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] -[[package]] -name = "python-discovery" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -663,64 +589,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "tomli-w" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, -] - -[[package]] -name = "tox" -version = "4.56.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cachetools" }, - { name = "colorama" }, - { name = "filelock" }, - { name = "packaging" }, - { name = "platformdirs" }, - { name = "pluggy" }, - { name = "pyproject-api" }, - { name = "python-discovery" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tomli-w" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/6a/d20d405fc6661902ff803a9fa048d8aae27597c3b5dc750369ded82d08f7/tox-4.56.1.tar.gz", hash = "sha256:db1c2610802553189cf40de251661d066a635ee0ed9bf2a60093b5f1a7f36ef8", size = 283155, upload-time = "2026-06-25T06:18:36.802Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/97/560a5dfde154619d9643b1e208119dddc29bbb35a38a4ce4d095c16cf8f0/tox-4.56.1-py3-none-any.whl", hash = "sha256:4d06b925c4dd67872099b39c5a46fba79a2169c5f6e32060f95a8b1181f0ef55", size = 216469, upload-time = "2026-06-25T06:18:35.229Z" }, -] - -[[package]] -name = "tox-uv" -version = "1.35.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tox-uv-bare" }, - { name = "uv" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/dc/6e9994c799bdbb309f829dd6b8d98764dd0757302f3433c380438a3a127b/tox_uv-1.35.2-py3-none-any.whl", hash = "sha256:2d99b0e3c782ba49e7cbe521c8d344758595961b17a3633738d67096641c1bde", size = 6565, upload-time = "2026-05-05T01:34:16.07Z" }, -] - -[[package]] -name = "tox-uv-bare" -version = "1.35.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tox" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/cb/168dc1ccf24e4065a9a0a33df55709ed2b5eb73bd2b13ddd53187e5dffb8/tox_uv_bare-1.35.2.tar.gz", hash = "sha256:49e28a804c97f23ea17e25859960c0fa78f35bccb7e14344cfd840e89a9aade9", size = 32333, upload-time = "2026-05-05T01:34:18.916Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/53/4a33dc81da39db7b31e5622333df361e8fe055b7ec636bd5fea762c9182d/tox_uv_bare-1.35.2-py3-none-any.whl", hash = "sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53", size = 22307, upload-time = "2026-05-05T01:34:17.681Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -730,48 +598,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] -[[package]] -name = "uv" -version = "0.11.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/8e/2777bf40df3c634f2a522baaa2995d2bd5901461fc5d8730c04c39fa6c75/uv-0.11.25.tar.gz", hash = "sha256:458e731778e7b5cc870710397859c23e766703e7bc0695f23b3eb15080745ba6", size = 4329198, upload-time = "2026-06-27T00:49:00.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/a7/dc836daca0db76178cfe4e40ee95fa2c5f96a3d7b3cda0bdde5291aef8b8/uv-0.11.25-py3-none-linux_armv6l.whl", hash = "sha256:d6f965a79fc7539a12139ce981caa0cbf7d9d3bd4ea3daadaf174ab4d7fb6e42", size = 25153689, upload-time = "2026-06-27T00:48:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/d60eabb616db232ed0e682c55d858d0980175a92336429c4e0ee82a160e6/uv-0.11.25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:610650cbaa0a9b18015da39d2c28d736d287a5a124e49296d8fdef5e4022e980", size = 24149272, upload-time = "2026-06-27T00:48:13.917Z" }, - { url = "https://files.pythonhosted.org/packages/ab/fd/9f72373e340c6abcd3eb9257d69b442434b65e90e1f7f53f333abdac11b2/uv-0.11.25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f7a78fc8d0c5e764e9fa39c99066db47a0bc465b023feed90812e3c0a6b5eb0d", size = 22885453, upload-time = "2026-06-27T00:48:16.641Z" }, - { url = "https://files.pythonhosted.org/packages/ee/c1/7cbb3f6b4a021ce7c1c453d1412d3b4b68d7d7585d15d91a57e9ab0a317e/uv-0.11.25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e3480640983e0b8e509eeb67882837e620bdd820f8776948a5f13ebbb4481d04", size = 24935172, upload-time = "2026-06-27T00:48:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/df/d4/862a534ab4aae28ad2c9145ea5af3021eefe225e4e1c983278bb34b53b9e/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:b180b12237b4e04692491fc6796584a9a8bdf4c7332bd2a769caf096b97885d0", size = 24665125, upload-time = "2026-06-27T00:48:22.408Z" }, - { url = "https://files.pythonhosted.org/packages/91/2a/70ef2f2a212d89d23f90e1c8017c080fae93bec21cd9f14795498a6e6f89/uv-0.11.25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69d14ffd0a4b050f8a70f64aacb09b8dfdfb1cb30a6351fb17b48f273f95c58c", size = 24653836, upload-time = "2026-06-27T00:48:24.858Z" }, - { url = "https://files.pythonhosted.org/packages/44/2b/a7beea057778f092daa5aeda7aeb3f8bfa78f4a0622245adb449fdbb3d20/uv-0.11.25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ef11d9967a38109e6e8e3d20d1f743fa08033c32bce274d6ccd9a9abb5d305", size = 26069304, upload-time = "2026-06-27T00:48:28.05Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e8/bb8ab595a27035b565cedd5d601ae2b85cf6aba6f68cd6a97eae88dd7250/uv-0.11.25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3febca65ec5bc336ddaf7e4f724704f2c894c16839723df14865ee00b4acf38d", size = 26992784, upload-time = "2026-06-27T00:48:30.584Z" }, - { url = "https://files.pythonhosted.org/packages/17/90/bbfa0653e992877e5ea78ab3186fb6a5a122ebef5913b6809ef6d06fcf9b/uv-0.11.25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:850ba0018ff170c3a9baaf9b5fe8b23393b6b77ee4ea6b2e2315fdb8d7c388f7", size = 26151002, upload-time = "2026-06-27T00:48:33.739Z" }, - { url = "https://files.pythonhosted.org/packages/91/01/f5a01fd777ce501cc59eb71775f3bbacac258a65a64939f07804c2279c98/uv-0.11.25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2bc05e17ae3e1f232abf93e7dcfb3b68702dfcde34a00c29cbce7e07d1ecbfb", size = 26334122, upload-time = "2026-06-27T00:48:36.247Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c4/f8b3b6b1bb48a452d98c02909e229f7ae317300618d6dc334c883742339e/uv-0.11.25-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:57fbd47e924242fd347d0c209d95711d8ea61db8d8780962d0f30ccde2c854a3", size = 25055505, upload-time = "2026-06-27T00:48:39.418Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7d/aece221faba22804d8b4f913903358dc2d45193babf2a44a49f79eadd25b/uv-0.11.25-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:f42de9e7d63a28a4fe76a522077813656de38b5acda20b4db63857d260c1ff13", size = 25660097, upload-time = "2026-06-27T00:48:42.398Z" }, - { url = "https://files.pythonhosted.org/packages/d3/7d/b835ed56eab281054efbfbcbbe1249621908c6e4c721a878904cbed2f418/uv-0.11.25-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2c1cfe97dce56c997dfa3214bdb8955b7b34cceea7505520185e22ad99c0eb6b", size = 25759374, upload-time = "2026-06-27T00:48:45.069Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fd/28c990fd18330aa01383bae738be113f4ac5d9d414f92790947326c6781c/uv-0.11.25-py3-none-musllinux_1_1_i686.whl", hash = "sha256:fbff70ae9fa4da9fb6823ae4fdaf77a65c9520e13b6d1d0241ba56e4b121b7aa", size = 25329343, upload-time = "2026-06-27T00:48:47.718Z" }, - { url = "https://files.pythonhosted.org/packages/70/48/16ff9dbe5f308cb547e227971cbcaf3c905797da12a4116b620e2acc09cb/uv-0.11.25-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:41b37e724f41eb4c3794bbdd82ddeebb4b5850d4ada8cccb2906ef9e5aa0f83b", size = 26477125, upload-time = "2026-06-27T00:48:50.251Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8b/4146a55d3ee6b2b67c3dece3ef6be220c443abafc2dc56baa2f74d6b36a8/uv-0.11.25-py3-none-win32.whl", hash = "sha256:86d4759fec9b46f61944d6e9ef1f5eaa2c5fbe2db5ddb59492d9174b08fcf39c", size = 23896598, upload-time = "2026-06-27T00:48:53.01Z" }, - { url = "https://files.pythonhosted.org/packages/64/66/784f9fb457b640dbb96cdd175f4902235b42ffe740dbaee9ea5fc649d8b2/uv-0.11.25-py3-none-win_amd64.whl", hash = "sha256:560b0fbaa6356af533923a349658c21d4f410d16e835787d8a05da451d4ee859", size = 26862205, upload-time = "2026-06-27T00:48:55.667Z" }, - { url = "https://files.pythonhosted.org/packages/85/cb/d7bb121261db0b829f38b1e86b80d8103afd050b8a5c5c9fe4a988d2f1fc/uv-0.11.25-py3-none-win_arm64.whl", hash = "sha256:79f166cd1b84f855e9d2768221d59b403869648289fd884d58ad4299edfb4d9e", size = 25153588, upload-time = "2026-06-27T00:48:58.495Z" }, -] - -[[package]] -name = "virtualenv" -version = "21.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "python-discovery" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, -] - [[package]] name = "yamllint" version = "1.38.0" From 569796a4875a2ed677c7863431d3bfe2195b684e Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Wed, 1 Jul 2026 22:34:22 +0200 Subject: [PATCH 36/53] fix: resolve ruff violations RUF067 fires on every top-level statement because the whole public API intentionally lives in inject/__init__.py, so it is turned off project-wide rather than per file. ISC004 caught a real bug: a stray trailing comma made the runtime binding error message a one-element tuple instead of a string, so the raised exception showed the tuple instead of the text. Dropping the comma restores the intended message. D420 reorders the get_injector_or_die docstring so Returns comes before Raises. RUF075 wraps the yields in the test context-manager fixtures in try/finally so their cleanup still runs when the caller raises. The now-unused `# noqa: RUF029` on the async fixtures is dropped too; that removal is a ruff auto-fix. --- pyproject.toml | 11 ++++++----- src/inject/__init__.py | 8 ++++---- tests/test_context_manager.py | 34 ++++++++++++++++++++++------------ 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 53c6021..e41361b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,11 +116,12 @@ extend-exclude = [".git", ".venv", "docs"] preview = true extend-select = ["ALL"] extend-ignore = [ - "D10", # missing documentation - "D203", # 1 of conflicting code-styles - "D212", # 1 of conflicting code-styles - "C408", # allow `dict()` instead of literal - "TD003", # don't require issue link + "D10", # missing documentation + "D203", # 1 of conflicting code-styles + "D212", # 1 of conflicting code-styles + "C408", # allow `dict()` instead of literal + "TD003", # don't require issue link + "RUF067", # __init__ modules intentionally contain code # Completely disable "FIX", "CPY", diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 4df24f9..7a75423 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -282,7 +282,7 @@ def get_instance(self, cls: Binding) -> Injectable: if not callable(cls): msg = ( "Cannot create a runtime binding, the key is not callable," - f" key={cls}", + f" key={cls}" ) raise InjectorException(msg) @@ -717,12 +717,12 @@ def get_injector_or_die() -> Injector: """ Return the current injector or raise an InjectorException. - Raises: - InjectorException: If injector is not configured. - Returns: Configured injector. + Raises: + InjectorException: If injector is not configured. + """ injector = _INJECTOR if not injector: diff --git a/tests/test_context_manager.py b/tests/test_context_manager.py index 9b19bc8..a0ce041 100644 --- a/tests/test_context_manager.py +++ b/tests/test_context_manager.py @@ -24,36 +24,46 @@ class MockFoo(Destroyable): ... @contextlib.contextmanager def get_file_sync(): obj = MockFile() - yield obj - obj.destroy() + try: + yield obj + finally: + obj.destroy() @contextlib.contextmanager def get_conn_sync(): obj = MockConnection() - yield obj - obj.destroy() + try: + yield obj + finally: + obj.destroy() @contextlib.contextmanager def get_foo_sync(): obj = MockFoo() - yield obj - obj.destroy() + try: + yield obj + finally: + obj.destroy() @contextlib.asynccontextmanager -async def get_file_async(): # noqa: RUF029 +async def get_file_async(): obj = MockFile() - yield obj - obj.destroy() + try: + yield obj + finally: + obj.destroy() @contextlib.asynccontextmanager -async def get_conn_async(): # noqa: RUF029 +async def get_conn_async(): obj = MockConnection() - yield obj - obj.destroy() + try: + yield obj + finally: + obj.destroy() class TestContextManagerFunctional(BaseTestInject): From f9b55812338253c0bc648cabcf92c3568d7d3eb8 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Wed, 1 Jul 2026 22:36:11 +0200 Subject: [PATCH 37/53] ci: use ubuntu-latest for all runners --- .github/workflows/tests.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2515989..202aa1c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,7 +36,7 @@ jobs: lint: name: "Linting" - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: "Checkout" uses: actions/checkout@v7 @@ -53,7 +53,7 @@ jobs: types: name: "Type checking (public API)" - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: "Checkout" uses: actions/checkout@v7 @@ -72,7 +72,7 @@ jobs: tests: name: "Run unit tests" - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: From bf36feeb9763fb958449ec1395c1c17b02e28c71 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Wed, 1 Jul 2026 22:39:20 +0200 Subject: [PATCH 38/53] ci: re-enable full mypy --- .github/workflows/tests.yaml | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 202aa1c..27c8f87 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -51,7 +51,24 @@ jobs: - name: "Lint formatting" run: just lint - types: + typing: + name: "Type checking (library code)" + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@v7 + - name: "Setup Python" + uses: actions/setup-python@v6 + with: + python-version: "3.14" + - name: "Setup uv" + uses: astral-sh/setup-uv@v8.2.0 + - name: "Setup just" + uses: extractions/setup-just@v4 + - name: "Type checking (library code)" + run: just typing + + public-typing: name: "Type checking (public API)" runs-on: ubuntu-latest steps: @@ -66,9 +83,7 @@ jobs: - name: "Setup just" uses: extractions/setup-just@v4 - name: "Type checking (public API)" - run: just public-typing -# - name: "Type checking (full)" TODO: re-enable once green -# run: just typing + run: just public-typing-all tests: name: "Run unit tests" From 2cbe764872f9fe3d969c9d74d5416fb219cc9c6f Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:34:57 +0200 Subject: [PATCH 39/53] chore: drop PEP604/560 version checks now that 3.10 is baseline --- src/inject/__init__.py | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 7a75423..01f68b5 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -80,28 +80,16 @@ def my_config(binder): import functools import inspect import logging -import sys import threading import typing as t +from types import UnionType +from typing import _GenericAlias # noqa: ICN003, PLC2701 from inject._version import __version__ as __version__ -# PEP 604 -_HAS_PEP604_SUPPORT = sys.version_info[:3] >= (3, 10, 0) -# PEP 560 -_HAS_PEP560_SUPPORT = _HAS_PEP604_SUPPORT or sys.version_info[:3] >= (3, 7, 0) - _RETURN = "return" _MISSING = object() -if _HAS_PEP604_SUPPORT: - from types import UnionType - from typing import _GenericAlias # noqa: ICN003 -elif _HAS_PEP560_SUPPORT: - from typing import _GenericAlias # noqa: ICN003, PLC2701 -else: - from typing import _Union # noqa: ICN003, PLC2701 - if t.TYPE_CHECKING: from typing_extensions import ParamSpec @@ -206,7 +194,7 @@ def _check_class(self, cls: Binding) -> None: if not self.allow_override and cls in self._bindings: raise InjectorException(f"Duplicate binding, key={cls}") - if self._is_forward_str(cls): + if isinstance(cls, str): ref = t.ForwardRef(cls) if not self.allow_override and ref in self._bindings: msg = f'Duplicate forward binding, i.e. "int" and int, key={cls}' @@ -214,8 +202,6 @@ def _check_class(self, cls: Binding) -> None: def _maybe_bind_forward(self, cls: Binding, binding: t.Any) -> None: # noqa: ANN401 """Bind a string forward reference.""" - if not _HAS_PEP560_SUPPORT: - return if not isinstance(cls, str): return @@ -223,10 +209,6 @@ def _maybe_bind_forward(self, cls: Binding, binding: t.Any) -> None: # noqa: AN self._bindings[ref] = binding logger.debug('Bound forward ref "%s"', cls) - @staticmethod - def _is_forward_str(kls: Binding) -> bool: - return _HAS_PEP560_SUPPORT and isinstance(kls, str) - class Injector: _bindings: dict[Binding, Constructor] @@ -662,7 +644,7 @@ def autoparams(*selected: str) -> t.Callable[[T], T]: ... def autoparams(*selected: t.Callable[P, T] | str) -> t.Callable[..., T]: """ - Return a decorator injecting args based on function type hints, only since 3.5. + Return a decorator injecting args based on function type hints. For example:: @@ -750,17 +732,11 @@ def _is_union_type(typ: type) -> bool: Source: https://github.com/ilevkivskyi/typing_inspect/blob/master/typing_inspect.py """ - if _HAS_PEP604_SUPPORT: - return ( - typ is t.Union - or isinstance(typ, UnionType) - or (isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union) - ) - if _HAS_PEP560_SUPPORT: - return typ is t.Union or ( - isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union - ) - return type(typ) is _Union + return ( + typ is t.Union + or isinstance(typ, UnionType) + or (isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union) + ) def _unwrap_cls_annotation(cls: type, attr_name: str) -> type: From 1119df2ef0ced64638c88fe741bf4c84b3bfceff Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:35:42 +0200 Subject: [PATCH 40/53] refactor: stop using private typing internals for union check --- src/inject/__init__.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 01f68b5..e50bb27 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -83,7 +83,6 @@ def my_config(binder): import threading import typing as t from types import UnionType -from typing import _GenericAlias # noqa: ICN003, PLC2701 from inject._version import __version__ as __version__ @@ -729,14 +728,8 @@ def _is_union_type(typ: type) -> bool: is_union_type(Union) == True is_union_type(Union[int, int]) == False is_union_type(Union[T, int]) == True - - Source: https://github.com/ilevkivskyi/typing_inspect/blob/master/typing_inspect.py """ - return ( - typ is t.Union - or isinstance(typ, UnionType) - or (isinstance(typ, _GenericAlias) and typ.__origin__ is t.Union) - ) + return typ is t.Union or isinstance(typ, UnionType) or t.get_origin(typ) is t.Union def _unwrap_cls_annotation(cls: type, attr_name: str) -> type: From 6a2bedc0389ade9525d4447772e3790666acc18d Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:36:20 +0200 Subject: [PATCH 41/53] refactor: drop typing_extensions ParamSpec fallback --- src/inject/__init__.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index e50bb27..672e581 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -89,13 +89,6 @@ def my_config(binder): _RETURN = "return" _MISSING = object() -if t.TYPE_CHECKING: - from typing_extensions import ParamSpec - - P = ParamSpec("P") -else: - P = object() - logger = logging.getLogger("inject") _INJECTOR = None # Shared injector instance. @@ -104,6 +97,7 @@ def my_config(binder): Injectable = t.Union[object, t.Any] T = t.TypeVar("T", bound=Injectable) +P = t.ParamSpec("P") Binding = t.Union[type[Injectable], t.Hashable] Constructor = t.Callable[ [], From c692867b69dc75d2fbde3514de1620bbad67a00c Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:37:16 +0200 Subject: [PATCH 42/53] refactor: use PEP 604 unions in inject/__init__.py --- src/inject/__init__.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 672e581..48b3b7e 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -95,19 +95,18 @@ def my_config(binder): _INJECTOR_LOCK = threading.RLock() # Guards injector initialization. _BINDING_LOCK = threading.RLock() # Guards runtime bindings. -Injectable = t.Union[object, t.Any] +Injectable = object | t.Any T = t.TypeVar("T", bound=Injectable) P = t.ParamSpec("P") -Binding = t.Union[type[Injectable], t.Hashable] +Binding = type[Injectable] | t.Hashable Constructor = t.Callable[ [], - t.Union[ - Injectable, - contextlib.AbstractContextManager[Injectable], - contextlib.AbstractAsyncContextManager[Injectable], - ], + Injectable + | contextlib.AbstractContextManager[Injectable] + | contextlib.AbstractAsyncContextManager[Injectable], ] Provider = Constructor +# `t.Optional` because PEP 604 unions don't support forward references. BinderCallable = t.Callable[["Binder"], t.Optional["Binder"]] @@ -208,7 +207,7 @@ class Injector: def __init__( self, - config: t.Optional[BinderCallable] = None, + config: BinderCallable | None = None, # TODO(pyctrl): force following flags to be kwargs bind_in_runtime: bool = True, # noqa: FBT001, FBT002 allow_override: bool = False, # noqa: FBT001, FBT002 @@ -278,7 +277,7 @@ class InjectorException(Exception): class _ConstructorBinding(t.Generic[T]): - _instance: t.Optional[T] + _instance: T | None def __init__(self, constructor: t.Callable[[], T]) -> None: self._constructor = constructor @@ -333,7 +332,7 @@ def __call__(self) -> T: # - `attr` implementation is inherited from `property` # - `attr` class member is not annotated class _AttributeInjection(property): - def __init__(self, cls: t.Union[type[T], t.Hashable]) -> None: + def __init__(self, cls: type[T] | t.Hashable) -> None: self._cls = cls super().__init__( fget=lambda _: instance(self._cls), @@ -348,13 +347,13 @@ def __set_name__(self, owner: type[T], name: str) -> None: class _ParameterInjection(t.Generic[T]): __slots__ = ("_cls", "_name") - def __init__(self, name: str, cls: t.Optional[Binding] = None) -> None: + def __init__(self, name: str, cls: Binding | None = None) -> None: self._name = name self._cls = cls def __call__( - self, func: t.Callable[..., t.Union[T, t.Awaitable[T]]] - ) -> t.Callable[..., t.Union[T, t.Awaitable[T]]]: + self, func: t.Callable[..., T | t.Awaitable[T]] + ) -> t.Callable[..., T | t.Awaitable[T]]: if inspect.iscoroutinefunction(func): @functools.wraps(func) @@ -419,8 +418,8 @@ async def _aggregate_async_stack( kwargs.update(executed_kwargs) def __call__( - self, func: t.Callable[..., t.Union[t.Awaitable[T], T]] - ) -> t.Callable[..., t.Union[t.Awaitable[T], T]]: + self, func: t.Callable[..., t.Awaitable[T] | T] + ) -> t.Callable[..., t.Awaitable[T] | T]: arg_names = inspect.getfullargspec(func).args params_to_provide = self._params @@ -470,7 +469,7 @@ def injection_wrapper(*args: t.Any, **kwargs: t.Any) -> T: # noqa: ANN401 def configure( - config: t.Optional[BinderCallable] = None, + config: BinderCallable | None = None, # TODO(pyctrl): force following flags to be kwargs bind_in_runtime: bool = True, # noqa: FBT001, FBT002 allow_override: bool = False, # noqa: FBT001, FBT002 @@ -509,7 +508,7 @@ def configure( def configure_once( - config: t.Optional[BinderCallable] = None, + config: BinderCallable | None = None, # TODO(pyctrl): force following flags to be kwargs bind_in_runtime: bool = True, # noqa: FBT001, FBT002 allow_override: bool = False, # noqa: FBT001, FBT002 @@ -529,7 +528,7 @@ def configure_once( def clear_and_configure( - config: t.Optional[BinderCallable] = None, + config: BinderCallable | None = None, # TODO(pyctrl): force following flags to be kwargs bind_in_runtime: bool = True, # noqa: FBT001, FBT002 allow_override: bool = False, # noqa: FBT001, FBT002 @@ -605,7 +604,7 @@ def attr(cls=_MISSING): attr_dc = attr -def param(name: str, cls: t.Optional[Binding] = None) -> t.Callable: +def param(name: str, cls: Binding | None = None) -> t.Callable: """ Return a decorator which injects an arg into a function. @@ -683,7 +682,7 @@ def autoparams_decorator(fn: t.Callable[..., T]) -> t.Callable[..., T]: return autoparams_decorator -def get_injector() -> t.Optional[Injector]: +def get_injector() -> Injector | None: """Return the current injector or None.""" return _INJECTOR From 8434fa48a5f5197b50021af47a4f35c529ea0636 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:38:02 +0200 Subject: [PATCH 43/53] test: restore PEP 604 union in autoparams test --- tests/test_autoparams.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_autoparams.py b/tests/test_autoparams.py index 03775a2..0353f61 100644 --- a/tests/test_autoparams.py +++ b/tests/test_autoparams.py @@ -1,4 +1,3 @@ -import sys import typing as t import inject @@ -226,11 +225,8 @@ def config(binder): self.assertRaises(TypeError, test_func, a=1, c=3) def test_autoparams_only_selected_with_optional_pep604_union(self): - if not sys.version_info[:3] >= (3, 10, 0): - return - @inject.autoparams("a", "c") - def test_func(a: A, b: B, *, c: t.Optional[C] = None): + def test_func(a: A, b: B, *, c: C | None = None): return a, b, c def config(binder): From af6c2a153372b114e350bbd4c45b93697f545238 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:46:46 +0200 Subject: [PATCH 44/53] chore: drop pyupgrade ruff ignores for typing rules --- pyproject.toml | 5 ----- tests/test_autoparams.py | 11 ++++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e41361b..13e544f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,11 +147,6 @@ extend-ignore = [ "ISC002", "N818", # Exception name should be named with an Error suffix "TRY003", - "UP006", - "UP007", - "UP035", - "UP045", - "FA100", ] [tool.ruff.lint.extend-per-file-ignores] diff --git a/tests/test_autoparams.py b/tests/test_autoparams.py index 0353f61..5e405b9 100644 --- a/tests/test_autoparams.py +++ b/tests/test_autoparams.py @@ -23,7 +23,7 @@ def _get_decorator(): def test_autoparams_by_class(self): @self._get_decorator() - def test_func(val: t.Optional[int] = None): + def test_func(val: int | None = None): return val inject.configure(lambda binder: binder.bind(int, 123)) @@ -102,7 +102,7 @@ def config(binder): def test_autoparams_on_method(self): class Test: @self._get_decorator() - def func(self, a=1, b: B = None, *, c: t.Optional[C] = None): + def func(self, a=1, b: B = None, *, c: C | None = None): return self, a, b, c def config(binder): @@ -128,7 +128,7 @@ class Test: # note inject must be *before* classmethod! @classmethod @self._get_decorator() - def func(cls, a=1, b: B = None, *, c: t.Optional[C] = None): + def func(cls, a=1, b: B = None, *, c: C | None = None): return cls, a, b, c def config(binder): @@ -153,7 +153,7 @@ class Test: # note inject must be *before* classmethod! @classmethod @self._get_decorator() - def func(cls, a=1, b: B = None, *, c: t.Optional[C] = None): + def func(cls, a=1, b: B = None, *, c: C | None = None): return cls, a, b, c def config(binder): @@ -211,7 +211,8 @@ def config(binder): def test_autoparams_only_selected_with_optional(self): @inject.autoparams("a", "c") - def test_func(a: A, b: B, *, c: t.Optional[C] = None): + # `t.Optional` coverage is the point + def test_func(a: A, b: B, *, c: t.Optional[C] = None): # noqa: UP045 return a, b, c def config(binder): From 563f172f1ffe78a4a896eb5f760484f126fbb738 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:49:30 +0200 Subject: [PATCH 45/53] docs: drop redundant `object` base in examples Python 3 does not require inheriting from object anymore. --- README.md | 12 ++++++------ src/inject/__init__.py | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f95d9df..8154c97 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ def bar(foo, cache=None): # `inject.attr` creates properties (descriptors) which request dependencies on access. -class User(object): +class User: cache = inject.attr(Cache) def __init__(self, id): @@ -233,16 +233,16 @@ def config(binder): For example, only the `Config` class binding is present, other bindings are runtime: ```python -class Config(object): +class Config: pass -class Cache(object): +class Cache: config = inject.attr(Config) -class Db(object): +class Db: config = inject.attr(Config) -class User(object): +class User: cache = inject.attr(Cache) db = inject.attr(Db) @@ -292,7 +292,7 @@ import inject import threading # Given a user class. -class User(object): +class User: pass # Create a thread-local current user storage. diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 48b3b7e..597dc43 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -11,7 +11,7 @@ def my_config(binder): inject.configure(my_config) - Use `inject.instance`, `inject.attr` or `inject.param` to inject dependencies:: - class User(object): + class User: cache = inject.attr(Cache) @classmethod @@ -47,23 +47,23 @@ def bar(foo, cache=None): Runtime bindings greatly reduce the required configuration by automatically creating singletons on first access. For example, below only the Config class requires binding configuration, all other classes are runtime bindings:: - class Cache(object): + class Cache: config = inject.attr(Config) def __init__(self): self._redis = connect(self.config.redis_address) - class Db(object): + class Db: pass - class UserRepo(object): + class UserRepo: cache = inject.attr(Cache) db = inject.attr(Db) def load(self, user_id): return cache.load('user', user_id) or db.load('user', user_id) - class Config(object): + class Config: def __init__(self, redis_address): self.redis_address = redis_address From bf2c5c7a2f98a70d934d55570fce403ced57b76f Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 12:58:17 +0200 Subject: [PATCH 46/53] fix: type the injection decorator signatures properly Incomplete signatures were behind four of the remaining mypy errors. --- src/inject/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 597dc43..93a5937 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -108,10 +108,13 @@ def my_config(binder): Provider = Constructor # `t.Optional` because PEP 604 unions don't support forward references. BinderCallable = t.Callable[["Binder"], t.Optional["Binder"]] +_InjectionDecorator = t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]] class ConstructorTypeError(TypeError): - def __init__(self, constructor: t.Callable, previous_error: TypeError) -> None: + def __init__( + self, constructor: t.Callable[..., t.Any], previous_error: TypeError + ) -> None: super().__init__(f"{constructor} raised an error: {previous_error}") @@ -595,7 +598,7 @@ def attr(cls: type[T]) -> T: ... def attr(cls: t.Hashable) -> Injectable: ... -def attr(cls=_MISSING): +def attr(cls: type[T] | t.Hashable = _MISSING) -> Injectable: """Return an attribute injection (descriptor).""" return _AttributeInjection(cls) @@ -604,7 +607,7 @@ def attr(cls=_MISSING): attr_dc = attr -def param(name: str, cls: Binding | None = None) -> t.Callable: +def param(name: str, cls: Binding | None = None) -> _InjectionDecorator: """ Return a decorator which injects an arg into a function. @@ -613,7 +616,7 @@ def param(name: str, cls: Binding | None = None) -> t.Callable: return _ParameterInjection(name, cls) -def params(**args_to_classes: Binding) -> t.Callable: +def params(**args_to_classes: Binding) -> _InjectionDecorator: """ Return a decorator which injects args into a function. From 2f7ebc38455903b81546bf3c211e3d431f1ad7f5 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 13:02:41 +0200 Subject: [PATCH 47/53] fix: correct get_instance overload for mypy The first overload returned T without taking it as a parameter, which mypy rejected and made the second overload unreachable. --- src/inject/__init__.py | 6 +----- typing_checks/get_instance.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 typing_checks/get_instance.py diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 93a5937..2112fe0 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -223,12 +223,8 @@ def __init__( else: self._bindings = {} - # NOTE(pyctrl): only since 3.12 - # @t.overload - # def get_instance(self, cls: type[T]) -> T: ... - @t.overload - def get_instance(self, cls: Binding) -> T: ... + def get_instance(self, cls: type[T]) -> T: ... @t.overload def get_instance(self, cls: t.Hashable) -> Injectable: ... diff --git a/typing_checks/get_instance.py b/typing_checks/get_instance.py new file mode 100644 index 0000000..8940187 --- /dev/null +++ b/typing_checks/get_instance.py @@ -0,0 +1,15 @@ +"""Static-typing regression guard for ``inject.Injector.get_instance``.""" + +from typing_extensions import assert_type + +import inject + + +class _Service: + pass + + +injector = inject.Injector() + +assert_type(injector.get_instance(_Service), _Service) +assert_type(injector.get_instance("service"), inject.Injectable) From 3060200ac09906848ce8162c44c05126e14cd648 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 13:06:59 +0200 Subject: [PATCH 48/53] fix: align autoparams overloads with mypy The `fn` overload can only be passed positionally by the real implementation, and the wrapper can return an awaitable, so the declared types now match runtime behavior instead of fighting mypy. --- src/inject/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 2112fe0..590d970 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -626,14 +626,14 @@ def sign_up(name, email, cache, db): @t.overload -def autoparams(fn: t.Callable[P, T]) -> t.Callable[P, T]: ... +def autoparams(fn: t.Callable[P, T], /) -> t.Callable[P, T]: ... @t.overload def autoparams(*selected: str) -> t.Callable[[T], T]: ... -def autoparams(*selected: t.Callable[P, T] | str) -> t.Callable[..., T]: +def autoparams(*selected: t.Callable[..., t.Any] | str) -> t.Callable[..., t.Any]: """ Return a decorator injecting args based on function type hints. @@ -654,7 +654,9 @@ def sign_up(name, email, cache: RedisCache, db: DbInterface): """ only_these: set[str] = set() - def autoparams_decorator(fn: t.Callable[..., T]) -> t.Callable[..., T]: + def autoparams_decorator( + fn: t.Callable[..., T], + ) -> t.Callable[..., t.Awaitable[T] | T]: if inspect.isclass(fn): types = t.get_type_hints(fn.__init__) else: @@ -677,7 +679,7 @@ def autoparams_decorator(fn: t.Callable[..., T]) -> t.Callable[..., T]: if len(selected) == 1 and callable(target): return autoparams_decorator(target) - only_these.update(selected) + only_these.update(s for s in selected if isinstance(s, str)) return autoparams_decorator From e2dc3e28266ac63faf7cb7a1cc060953b1e9d57e Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 13:08:38 +0200 Subject: [PATCH 49/53] fix: use typing.get_args instead of __args__ mypy flags __args__ as private since type doesn't declare it, so use the public get_args API instead. --- src/inject/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/inject/__init__.py b/src/inject/__init__.py index 590d970..d2ea31e 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -710,7 +710,8 @@ def _unwrap_union_arg(typ: type) -> type: """Return the first type A in typing.Union[A, B] or typ if not Union.""" if not _is_union_type(typ): return typ - return typ.__args__[0] + args: tuple[type, ...] = t.get_args(typ) + return args[0] def _is_union_type(typ: type) -> bool: From c21c9c29e50e97b2ca7cf8f2d425e9487d32e28b Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 13:13:30 +0200 Subject: [PATCH 50/53] chore: enable mypy strict mode Strict mode implies these flags so they can be removed, and it exposed one real typing issue that needed fixing. --- pyproject.toml | 14 +------------- src/inject/__init__.py | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 13e544f..f05fc27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,21 +77,9 @@ profile = "black" [tool.mypy] -check_untyped_defs = true -disallow_any_generics = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true ignore_missing_imports = true -no_implicit_optional = true python_version = "3.10" -warn_redundant_casts = true -warn_return_any = true -warn_unused_configs = true -warn_unused_ignores = true -#strict = true # TODO(pyctrl): improve typings and enable strict mode +strict = true exclude = ["tests", ".venv"] diff --git a/src/inject/__init__.py b/src/inject/__init__.py index d2ea31e..43d3c3b 100644 --- a/src/inject/__init__.py +++ b/src/inject/__init__.py @@ -714,7 +714,7 @@ def _unwrap_union_arg(typ: type) -> type: return args[0] -def _is_union_type(typ: type) -> bool: +def _is_union_type(typ: object) -> bool: """ Test if the type is a union type. From 19de8a6fa7e48ea8865514b607fc47478245bb52 Mon Sep 17 00:00:00 2001 From: Elias Mueller Date: Sat, 4 Jul 2026 13:25:13 +0200 Subject: [PATCH 51/53] docs: fix missing dot in chained bind example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8154c97..9680aec 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ import inject inject.configure(lambda binder: \ binder.bind('host', 'localhost') \ - binder.bind('port', 1234)) + .bind('port', 1234)) ``` ## Why no scopes? From 42d79ecb3a4b3c2e18b6d26b9e4759b5832cf1e6 Mon Sep 17 00:00:00 2001 From: Ivan Korobkov Date: Mon, 13 Jul 2026 10:59:01 +0300 Subject: [PATCH 52/53] Version 5.5.0 --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e408a59..fc71c32 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,11 @@ python-inject changes ===================== -### 5.5.0 (unreleased) +### 5.5.0 (2026-08-13) - Drop Python 3.9 support (end of life). Minimum supported version is now 3.10. +- Adopt uv for dependencies and CI, #138. +- Migrate dev tooling from make + tox to just, #139. +- Fix mypy, #140. ### 5.4.1 (2026-07-27) - Fix static typing of inject.attr(Cls), #136 From b0d473209670f93fe114cbe2d08af5027cf3c5bb Mon Sep 17 00:00:00 2001 From: Ivan Korobkov Date: Mon, 13 Jul 2026 14:43:40 +0300 Subject: [PATCH 53/53] Fix release dates in CHANGES.md --- CHANGES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fc71c32..d1fd308 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,16 +1,16 @@ python-inject changes ===================== -### 5.5.0 (2026-08-13) +### 5.5.0 (2026-07-13) - Drop Python 3.9 support (end of life). Minimum supported version is now 3.10. - Adopt uv for dependencies and CI, #138. - Migrate dev tooling from make + tox to just, #139. - Fix mypy, #140. -### 5.4.1 (2026-07-27) +### 5.4.1 (2026-06-27) - Fix static typing of inject.attr(Cls), #136 -### 5.4.0 (2026-07-16) +### 5.4.0 (2026-06-16) - Fix `autoparams` type annotation, #134 - Support all context managers, #133