diff --git a/README.md b/README.md index adf5518..0a0ee8c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ if __name__ == "__main__": * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md) * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md) * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md) -* [**Utils**](https://github.com/100nm/python-injection/tree/prod/documentation/utils.md) +* [**Loaders**](https://github.com/100nm/python-injection/tree/prod/documentation/loaders.md) +* [**Entrypoint**](https://github.com/100nm/python-injection/tree/prod/documentation/entrypoint.md) * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md) * [**Concrete example**](https://github.com/100nm/python-injection-example) diff --git a/documentation/basic-usage.md b/documentation/basic-usage.md index 6a68324..908c9eb 100644 --- a/documentation/basic-usage.md +++ b/documentation/basic-usage.md @@ -136,7 +136,7 @@ classes. > [!WARNING] > If the child class is in another file, make sure that file is imported before injection. -> [_See `load_packages` function._](utils.md#load_packages) +> [_See `load_packages` function._](loaders.md#load_packages) _Example with one class:_ diff --git a/documentation/entrypoint.md b/documentation/entrypoint.md new file mode 100644 index 0000000..e6cf814 --- /dev/null +++ b/documentation/entrypoint.md @@ -0,0 +1,81 @@ +# Entrypoint + +## What is it? + +_An entrypoint is the first function executed when a software component starts._ + +When using `python-injection`, you often need to perform several setup actions at the entrypoint _(such as injecting +dependencies, opening a scope, or importing Python modules)_. + +To solve this problem, the package provides an `Entrypoint` class, a builder-style utility that simplifies +entrypoint preparation. + +## Creating an entrypoint decorator + +`entrypoint_maker` allows you to define a custom decorator for your entrypoint functions. + +The function you decorate with `entrypoint_maker` serves to configure the `Entrypoint` instance. Its first parameter must +be the `Entrypoint` instance being built. You can inject dependencies into this setup function, but **only** `constants` +or `injectables`, because everything is not yet fully configured at this stage. + +**Instruction order matters**: each configuration step applies a decorator and returns a new `Entrypoint` instance. + +```python +# src/entrypoint.py + +import uvloop +from injection import adefine_scope +from injection.entrypoint import AsyncEntrypoint, Entrypoint, entrypointmaker +from injection.loaders import PythonModuleLoader + +@entrypointmaker +def entrypoint[**P, T](self: AsyncEntrypoint[P, T]) -> Entrypoint[P, T]: + import src + + loader = PythonModuleLoader.from_keywords("# Auto-import") + return ( + self.inject() + .decorate(adefine_scope("lifespan", kind="shared")) + .async_to_sync(uvloop.run) + .load_modules(loader, src) + ) +``` + +> [!IMPORTANT] +> **Typing rule** +> +> When creating a decorator for async entrypoints, make sure to type `self` as `AsyncEntrypoint`. +> For sync code, use `Entrypoint` instead. + +## Example of use + +Developing a CLI is a good example of using multiple entrypoints: + +```python +# src/cli.py + +from injection.entrypoint import autocall +from typer import Typer + +from src.entrypoint import entrypoint +from src.services.logger import AsyncLogger # project service, implementation not provided + +app = Typer() + +@app.command() +def hello(name: str) -> None: + @autocall # allows automatically calling the function + @entrypoint + async def _(logger: AsyncLogger) -> None: + await logger.info(f"Hello {name}!") + +@app.command() +def goodbye(name: str) -> None: + @autocall + @entrypoint + async def _(logger: AsyncLogger) -> None: + await logger.info(f"Goodbye {name}!") + +if __name__ == "__main__": + app() +``` \ No newline at end of file diff --git a/documentation/utils.md b/documentation/loaders.md similarity index 99% rename from documentation/utils.md rename to documentation/loaders.md index 7fdb4e3..a3bc5b8 100644 --- a/documentation/utils.md +++ b/documentation/loaders.md @@ -1,4 +1,4 @@ -# Utils +# Loaders ## PythonModuleLoader diff --git a/injection/entrypoint.py b/injection/entrypoint.py new file mode 100644 index 0000000..c497a86 --- /dev/null +++ b/injection/entrypoint.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import asyncio +from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Iterator +from contextlib import asynccontextmanager, contextmanager +from dataclasses import dataclass, field +from functools import wraps +from types import MethodType +from types import ModuleType as PythonModule +from typing import Any, Self, final, overload + +from injection import Module, mod +from injection.loaders import PythonModuleLoader + +__all__ = ("AsyncEntrypoint", "Entrypoint", "autocall", "entrypointmaker") + +type AsyncEntrypoint[**P, T] = Entrypoint[P, Coroutine[Any, Any, T]] +type EntrypointDecorator[**P, T1, T2] = Callable[[Callable[P, T1]], Callable[P, T2]] +type EntrypointSetupMethod[*Ts, **P, T1, T2] = Callable[ + [Entrypoint[P, T1], *Ts], + Entrypoint[P, T2], +] + + +def autocall[**P, T](wrapped: Callable[P, T] | None = None, /) -> Any: + def decorator(wp: Callable[P, T]) -> Callable[P, T]: + wp() # type: ignore[call-arg] + return wp + + return decorator(wrapped) if wrapped else decorator + + +@overload +def entrypointmaker[*Ts, **P, T1, T2]( + wrapped: EntrypointSetupMethod[*Ts, P, T1, T2], + /, + *, + module: Module | None = ..., +) -> EntrypointDecorator[P, T1, T2]: ... + + +@overload +def entrypointmaker[*Ts, **P, T1, T2]( + wrapped: None = ..., + /, + *, + module: Module | None = ..., +) -> Callable[ + [EntrypointSetupMethod[*Ts, P, T1, T2]], + EntrypointDecorator[P, T1, T2], +]: ... + + +def entrypointmaker[*Ts, **P, T1, T2]( + wrapped: EntrypointSetupMethod[*Ts, P, T1, T2] | None = None, + /, + *, + module: Module | None = None, +) -> Any: + def decorator( + wp: EntrypointSetupMethod[*Ts, P, T1, T2], + ) -> EntrypointDecorator[P, T1, T2]: + return Entrypoint._make_decorator(wp, module) + + return decorator(wrapped) if wrapped else decorator + + +@final +@dataclass(repr=False, eq=False, frozen=True, slots=True) +class Entrypoint[**P, T]: + function: Callable[P, T] + module: Module = field(default_factory=mod) + + def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T: + return self.function(*args, **kwargs) + + def async_to_sync[_T]( + self: AsyncEntrypoint[P, _T], + run: Callable[[Coroutine[Any, Any, _T]], _T] = asyncio.run, + /, + ) -> Entrypoint[P, _T]: + function = self.function + + @wraps(function) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> _T: + return run(function(*args, **kwargs)) + + return self.__recreate(wrapper) + + def decorate( + self, + decorator: Callable[[Callable[P, T]], Callable[P, T]], + /, + ) -> Self: + return self.__recreate(decorator(self.function)) + + def inject(self) -> Self: + return self.decorate(self.module.make_injected_function) + + def load_modules( + self, + /, + loader: PythonModuleLoader, + *packages: PythonModule | str, + ) -> Self: + return self.setup(lambda: loader.load(*packages)) + + def setup(self, function: Callable[..., Any], /) -> Self: + @contextmanager + def decorator() -> Iterator[Any]: + yield function() + + return self.decorate(decorator()) + + def async_setup[_T]( + self: AsyncEntrypoint[P, _T], + function: Callable[..., Awaitable[Any]], + /, + ) -> AsyncEntrypoint[P, _T]: + @asynccontextmanager + async def decorator() -> AsyncIterator[Any]: + yield await function() + + return self.decorate(decorator()) + + def __recreate[**_P, _T]( + self: Entrypoint[Any, Any], + function: Callable[_P, _T], + /, + ) -> Entrypoint[_P, _T]: + return type(self)(function, self.module) + + @classmethod + def _make_decorator[*Ts, _T]( + cls, + setup_method: EntrypointSetupMethod[*Ts, P, T, _T], + /, + module: Module | None = None, + ) -> EntrypointDecorator[P, T, _T]: + module = module or mod() + setup_method = module.make_injected_function(setup_method) + + def decorator(function: Callable[P, T]) -> Callable[P, _T]: + self = cls(function, module) + return MethodType(setup_method, self)().function + + return decorator diff --git a/injection/loaders.py b/injection/loaders.py index 2a07a84..77aeb2c 100644 --- a/injection/loaders.py +++ b/injection/loaders.py @@ -57,7 +57,7 @@ def modules(self) -> dict[str, PythonModule]: def load(self, *packages: PythonModule | str) -> Self: modules = itertools.chain.from_iterable( - self.__iter_modules(package) for package in packages + self.__iter_modules_from(package) for package in packages ) self.__modules.update(modules) return self @@ -67,7 +67,7 @@ def __is_already_loaded(self, module_name: str) -> bool: module_name in modules for modules in (self.__modules, self._sys_modules) ) - def __iter_modules( + def __iter_modules_from( self, package: PythonModule | str, ) -> Iterator[tuple[str, PythonModule | None]]: diff --git a/pyproject.toml b/pyproject.toml index 7c61d59..19ef709 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,11 @@ dev = [ "mypy", "ruff", ] +doc = [ + "fastapi", + "typer", + "uvloop", +] test = [ "fastapi", "httpx", @@ -119,5 +124,5 @@ ignore = ["N818"] fixable = ["ALL"] [tool.uv] -default-groups = ["bench", "dev", "test"] +default-groups = ["bench", "dev", "doc", "test"] package = true diff --git a/tests/utils/__init__.py b/tests/entrypoint/__init__.py similarity index 100% rename from tests/utils/__init__.py rename to tests/entrypoint/__init__.py diff --git a/tests/entrypoint/test_autocall.py b/tests/entrypoint/test_autocall.py new file mode 100644 index 0000000..1663267 --- /dev/null +++ b/tests/entrypoint/test_autocall.py @@ -0,0 +1,12 @@ +from injection.entrypoint import autocall + + +def test_autocall_with_success(): + count = 0 + + @autocall + def increment() -> None: + nonlocal count + count += 1 + + assert count == 1 diff --git a/tests/entrypoint/test_entrypoint.py b/tests/entrypoint/test_entrypoint.py new file mode 100644 index 0000000..da5acc5 --- /dev/null +++ b/tests/entrypoint/test_entrypoint.py @@ -0,0 +1,68 @@ +from collections.abc import Iterator +from contextlib import contextmanager + +from injection import injectable +from injection.entrypoint import Entrypoint + + +class TestEntrypoint: + def test_async_to_sync_with_success_return_entrypoint(self): + async def async_function() -> int: + return 42 + + entrypoint = Entrypoint(async_function).async_to_sync() + assert entrypoint() == 42 + + def test_decorate_with_success_return_entrypoint(self): + enter_count = 0 + exit_count = 0 + + @contextmanager + def decorator() -> Iterator[None]: + nonlocal enter_count, exit_count + enter_count += 1 + yield + exit_count += 1 + + def function(): + assert enter_count == exit_count + 1 + + entrypoint = Entrypoint(function).decorate(decorator()) + entrypoint() + assert enter_count == exit_count == 1 + + def test_inject_with_success_return_entrypoint(self): + @injectable + class Service: ... + + def function(service: Service) -> bool: + return isinstance(service, Service) + + entrypoint = Entrypoint(function).inject() + assert entrypoint() + + def test_setup_with_success_return_entrypoint(self): + count = 0 + + def increment() -> None: + nonlocal count + count += 1 + + def function(): ... + + entrypoint = Entrypoint(function).setup(increment) + entrypoint() + assert count == 1 + + def test_async_setup_with_success_return_entrypoint(self): + count = 0 + + async def increment() -> None: + nonlocal count + count += 1 + + async def function(): ... + + entrypoint = Entrypoint(function).async_setup(increment).async_to_sync() + entrypoint() + assert count == 1 diff --git a/tests/entrypoint/test_entrypointmaker.py b/tests/entrypoint/test_entrypointmaker.py new file mode 100644 index 0000000..8837e93 --- /dev/null +++ b/tests/entrypoint/test_entrypointmaker.py @@ -0,0 +1,19 @@ +from injection.entrypoint import Entrypoint, entrypointmaker + + +def test_entrypointmaker_with_success_return_entrypoint_decorator(): + count = 0 + + def increment() -> None: + nonlocal count + count += 1 + + @entrypointmaker + def entrypoint[**P, T](self: Entrypoint[P, T]) -> Entrypoint[P, T]: + return self.setup(increment) + + @entrypoint + def function(): ... + + function() + assert count == 1 diff --git a/tests/utils/package1/__init__.py b/tests/loaders/__init__.py similarity index 100% rename from tests/utils/package1/__init__.py rename to tests/loaders/__init__.py diff --git a/tests/utils/package1/excluded_package/__init__.py b/tests/loaders/package1/__init__.py similarity index 100% rename from tests/utils/package1/excluded_package/__init__.py rename to tests/loaders/package1/__init__.py diff --git a/tests/utils/package1/sub_package/__init__.py b/tests/loaders/package1/excluded_package/__init__.py similarity index 100% rename from tests/utils/package1/sub_package/__init__.py rename to tests/loaders/package1/excluded_package/__init__.py diff --git a/tests/utils/package1/excluded_package/module3.py b/tests/loaders/package1/excluded_package/module3.py similarity index 100% rename from tests/utils/package1/excluded_package/module3.py rename to tests/loaders/package1/excluded_package/module3.py diff --git a/tests/utils/package1/module1.py b/tests/loaders/package1/module1.py similarity index 100% rename from tests/utils/package1/module1.py rename to tests/loaders/package1/module1.py diff --git a/tests/utils/package1/module_suffix.py b/tests/loaders/package1/module_suffix.py similarity index 100% rename from tests/utils/package1/module_suffix.py rename to tests/loaders/package1/module_suffix.py diff --git a/tests/utils/package1/prefix_module.py b/tests/loaders/package1/prefix_module.py similarity index 100% rename from tests/utils/package1/prefix_module.py rename to tests/loaders/package1/prefix_module.py diff --git a/tests/utils/package2/__init__.py b/tests/loaders/package1/sub_package/__init__.py similarity index 100% rename from tests/utils/package2/__init__.py rename to tests/loaders/package1/sub_package/__init__.py diff --git a/tests/utils/package1/sub_package/module2.py b/tests/loaders/package1/sub_package/module2.py similarity index 100% rename from tests/utils/package1/sub_package/module2.py rename to tests/loaders/package1/sub_package/module2.py diff --git a/tests/utils/package2/sub_package/__init__.py b/tests/loaders/package2/__init__.py similarity index 100% rename from tests/utils/package2/sub_package/__init__.py rename to tests/loaders/package2/__init__.py diff --git a/tests/utils/package2/module.py b/tests/loaders/package2/module.py similarity index 100% rename from tests/utils/package2/module.py rename to tests/loaders/package2/module.py diff --git a/tests/loaders/package2/sub_package/__init__.py b/tests/loaders/package2/sub_package/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/package2/sub_package/injectable.py b/tests/loaders/package2/sub_package/injectable.py similarity index 100% rename from tests/utils/package2/sub_package/injectable.py rename to tests/loaders/package2/sub_package/injectable.py diff --git a/tests/utils/test_load_profile.py b/tests/loaders/test_load_profile.py similarity index 100% rename from tests/utils/test_load_profile.py rename to tests/loaders/test_load_profile.py diff --git a/tests/utils/test_python_module_loader.py b/tests/loaders/test_python_module_loader.py similarity index 53% rename from tests/utils/test_python_module_loader.py rename to tests/loaders/test_python_module_loader.py index f9fac11..6cd6581 100644 --- a/tests/utils/test_python_module_loader.py +++ b/tests/loaders/test_python_module_loader.py @@ -5,7 +5,7 @@ class TestPythonModuleLoader: def test_load_with_predicate(self): - from tests.utils import package1 + from tests.loaders import package1 loaded_modules = ( PythonModuleLoader(lambda name: ".excluded_package." not in name) @@ -13,74 +13,74 @@ def test_load_with_predicate(self): .modules ) - assert "tests.utils.package1.excluded_package.module3" not in loaded_modules + assert "tests.loaders.package1.excluded_package.module3" not in loaded_modules modules = ( - "tests.utils.package1.module1", - "tests.utils.package1.sub_package.module2", + "tests.loaders.package1.module1", + "tests.loaders.package1.sub_package.module2", ) for module in modules: assert module in loaded_modules def test_load_with_keywords(self): - from tests.utils import package2 + from tests.loaders import package2 loaded_modules = ( PythonModuleLoader.from_keywords("@injectable").load(package2).modules ) assert len(loaded_modules) == 1 - assert "tests.utils.package2.sub_package.injectable" in loaded_modules + assert "tests.loaders.package2.sub_package.injectable" in loaded_modules def test_load_with_startswith(self): - from tests.utils import package1 + from tests.loaders import package1 loaded_modules = PythonModuleLoader.startswith("prefix_").load(package1).modules assert len(loaded_modules) == 1 - assert "tests.utils.package1.prefix_module" in loaded_modules + assert "tests.loaders.package1.prefix_module" in loaded_modules def test_load_with_endswith(self): - from tests.utils import package1 + from tests.loaders import package1 loaded_modules = PythonModuleLoader.endswith("_suffix").load(package1).modules assert len(loaded_modules) == 1 - assert "tests.utils.package1.module_suffix" in loaded_modules + assert "tests.loaders.package1.module_suffix" in loaded_modules def test_load_packages_with_success(self): - from tests.utils import package1 + from tests.loaders import package1 loaded_modules = load_packages(package1) modules = ( - "tests.utils.package1.module1", - "tests.utils.package1.module_suffix", - "tests.utils.package1.prefix_module", - "tests.utils.package1.sub_package.module2", - "tests.utils.package1.excluded_package.module3", + "tests.loaders.package1.module1", + "tests.loaders.package1.module_suffix", + "tests.loaders.package1.prefix_module", + "tests.loaders.package1.sub_package.module2", + "tests.loaders.package1.excluded_package.module3", ) for module in modules: assert module in loaded_modules def test_load_packages_with_str(self): - loaded_modules = load_packages("tests.utils.package1") + loaded_modules = load_packages("tests.loaders.package1") modules = ( - "tests.utils.package1.module1", - "tests.utils.package1.module_suffix", - "tests.utils.package1.prefix_module", - "tests.utils.package1.sub_package.module2", - "tests.utils.package1.excluded_package.module3", + "tests.loaders.package1.module1", + "tests.loaders.package1.module_suffix", + "tests.loaders.package1.prefix_module", + "tests.loaders.package1.sub_package.module2", + "tests.loaders.package1.excluded_package.module3", ) for module in modules: assert module in loaded_modules def test_load_packages_with_module_raise_type_error(self): - from tests.utils.package1 import module1 + from tests.loaders.package1 import module1 with pytest.raises(TypeError): load_packages(module1) diff --git a/uv.lock b/uv.lock index 49064d6..55cbbdb 100644 --- a/uv.lock +++ b/uv.lock @@ -69,14 +69,14 @@ wheels = [ [[package]] name = "click" -version = "8.1.8" +version = "8.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, ] [[package]] @@ -452,11 +452,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] @@ -613,6 +613,11 @@ dev = [ { name = "mypy" }, { name = "ruff" }, ] +doc = [ + { name = "fastapi" }, + { name = "typer" }, + { name = "uvloop" }, +] test = [ { name = "fastapi" }, { name = "httpx" }, @@ -637,6 +642,11 @@ dev = [ { name = "mypy" }, { name = "ruff" }, ] +doc = [ + { name = "fastapi" }, + { name = "typer" }, + { name = "uvloop" }, +] test = [ { name = "fastapi" }, { name = "httpx" }, @@ -670,27 +680,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/f6/adcf73711f31c9f5393862b4281c875a462d9f639f4ccdf69dc368311c20/ruff-0.11.8.tar.gz", hash = "sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8", size = 4086399, upload-time = "2025-05-01T14:53:24.459Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/60/c6aa9062fa518a9f86cb0b85248245cddcd892a125ca00441df77d79ef88/ruff-0.11.8-py3-none-linux_armv6l.whl", hash = "sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3", size = 10272473, upload-time = "2025-05-01T14:52:37.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/0325e50d106dc87c00695f7bcd5044c6d252ed5120ebf423773e00270f50/ruff-0.11.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835", size = 11040862, upload-time = "2025-05-01T14:52:41.022Z" }, - { url = "https://files.pythonhosted.org/packages/e6/27/b87ea1a7be37fef0adbc7fd987abbf90b6607d96aa3fc67e2c5b858e1e53/ruff-0.11.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c", size = 10385273, upload-time = "2025-05-01T14:52:43.551Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f7/3346161570d789045ed47a86110183f6ac3af0e94e7fd682772d89f7f1a1/ruff-0.11.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c", size = 10578330, upload-time = "2025-05-01T14:52:45.48Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c3/327fb950b4763c7b3784f91d3038ef10c13b2d42322d4ade5ce13a2f9edb/ruff-0.11.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219", size = 10122223, upload-time = "2025-05-01T14:52:47.675Z" }, - { url = "https://files.pythonhosted.org/packages/de/c7/ba686bce9adfeb6c61cb1bbadc17d58110fe1d602f199d79d4c880170f19/ruff-0.11.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f", size = 11697353, upload-time = "2025-05-01T14:52:50.264Z" }, - { url = "https://files.pythonhosted.org/packages/53/8e/a4fb4a1ddde3c59e73996bb3ac51844ff93384d533629434b1def7a336b0/ruff-0.11.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474", size = 12375936, upload-time = "2025-05-01T14:52:52.394Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/9529cb1e2936e2479a51aeb011307e7229225df9ac64ae064d91ead54571/ruff-0.11.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38", size = 11850083, upload-time = "2025-05-01T14:52:55.424Z" }, - { url = "https://files.pythonhosted.org/packages/3e/94/8f7eac4c612673ae15a4ad2bc0ee62e03c68a2d4f458daae3de0e47c67ba/ruff-0.11.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458", size = 14005834, upload-time = "2025-05-01T14:52:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7c/6f63b46b2be870cbf3f54c9c4154d13fac4b8827f22fa05ac835c10835b2/ruff-0.11.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5", size = 11503713, upload-time = "2025-05-01T14:53:01.244Z" }, - { url = "https://files.pythonhosted.org/packages/3a/91/57de411b544b5fe072779678986a021d87c3ee5b89551f2ca41200c5d643/ruff-0.11.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948", size = 10457182, upload-time = "2025-05-01T14:53:03.726Z" }, - { url = "https://files.pythonhosted.org/packages/01/49/cfe73e0ce5ecdd3e6f1137bf1f1be03dcc819d1bfe5cff33deb40c5926db/ruff-0.11.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb", size = 10101027, upload-time = "2025-05-01T14:53:06.555Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/a5cfe47c62b3531675795f38a0ef1c52ff8de62eaddf370d46634391a3fb/ruff-0.11.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c", size = 11111298, upload-time = "2025-05-01T14:53:08.825Z" }, - { url = "https://files.pythonhosted.org/packages/36/98/f76225f87e88f7cb669ae92c062b11c0a1e91f32705f829bd426f8e48b7b/ruff-0.11.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304", size = 11566884, upload-time = "2025-05-01T14:53:11.626Z" }, - { url = "https://files.pythonhosted.org/packages/de/7e/fff70b02e57852fda17bd43f99dda37b9bcf3e1af3d97c5834ff48d04715/ruff-0.11.8-py3-none-win32.whl", hash = "sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2", size = 10451102, upload-time = "2025-05-01T14:53:14.303Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a9/eaa571eb70648c9bde3120a1d5892597de57766e376b831b06e7c1e43945/ruff-0.11.8-py3-none-win_amd64.whl", hash = "sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4", size = 11597410, upload-time = "2025-05-01T14:53:16.571Z" }, - { url = "https://files.pythonhosted.org/packages/cd/be/f6b790d6ae98f1f32c645f8540d5c96248b72343b0a56fab3a07f2941897/ruff-0.11.8-py3-none-win_arm64.whl", hash = "sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2", size = 10713129, upload-time = "2025-05-01T14:53:22.27Z" }, +version = "0.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, ] [[package]] @@ -765,11 +775,11 @@ wheels = [ [[package]] name = "trove-classifiers" -version = "2025.5.1.12" +version = "2025.5.9.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/81/a43db75173e49b1204d2a826b2a3a0e2dee45fb0d9abfac2ce1ff928eebf/trove_classifiers-2025.5.1.12.tar.gz", hash = "sha256:28d24c3d043dc6b0459813d6bf4a231e788509b55ee3d54ba08ce72638031182", size = 16876, upload-time = "2025-05-01T12:46:02.238Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/04/1cd43f72c241fedcf0d9a18d0783953ee301eac9e5d9db1df0f0f089d9af/trove_classifiers-2025.5.9.12.tar.gz", hash = "sha256:7ca7c8a7a76e2cd314468c677c69d12cc2357711fcab4a60f87994c1589e5cb5", size = 16940, upload-time = "2025-05-09T12:04:48.829Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/dd/915b914cfbaf91a46560083519be825ee65d3f955a7797ecf0ecf271559d/trove_classifiers-2025.5.1.12-py3-none-any.whl", hash = "sha256:9ed1030cfcc8d0eb944155f05b4add4efaceba4ba6aca3f9f348a21f1e700404", size = 14041, upload-time = "2025-05-01T12:46:00.556Z" }, + { url = "https://files.pythonhosted.org/packages/92/ef/c6deb083748be3bcad6f471b6ae983950c161890bf5ae1b2af80cc56c530/trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce", size = 14119, upload-time = "2025-05-09T12:04:46.38Z" }, ] [[package]] @@ -831,41 +841,61 @@ wheels = [ [[package]] name = "uv" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/d4/c1104ee4d8a69e4834888cd850eb4f9327c585e5e60da108fda788d3872d/uv-0.7.2.tar.gz", hash = "sha256:45e619bb076916b79df8c5ecc28d1be04d1ccd0b63b080c44ae973b8deb33b25", size = 3293566, upload-time = "2025-04-30T19:25:33.065Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/c3/68291a239dbedc0389fa5ce5b5b6c7c2a54c52bc11e9503276f376faa9e7/uv-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:e1e4394b54bc387f227ca1b2aa0348d35f6455b6168ca1826c1dc5f4fc3e8d20", size = 16590159, upload-time = "2025-04-30T19:24:45.58Z" }, - { url = "https://files.pythonhosted.org/packages/6c/ac/3c7e8df1d6bb84a805aa773ea4f6a006682f8241f331c9c359eb5310f042/uv-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c0edb194c35f1f12c75bec4fe2d7d4d09f0c2cec3a16102217a772620ce1d6e6", size = 16753976, upload-time = "2025-04-30T19:24:49.223Z" }, - { url = "https://files.pythonhosted.org/packages/42/ca/6a3f3c094794d482e3418f6a46c2753fa4f6ed2fe5b7ecf299db8cfed9ea/uv-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:be2e8d033936ba8ed9ccf85eb2d15c7a8db3bb3e9c4960bdf7c3c98034a6dbda", size = 15513631, upload-time = "2025-04-30T19:24:52.042Z" }, - { url = "https://files.pythonhosted.org/packages/1e/65/6fae29e0eb884fa1cab89b0fa865d409e0e2bcada8316cd50b4c81e8706c/uv-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a314a94b42bc6014f18c877f723292306b76c10b455c2b385728e1470e661ced", size = 15972100, upload-time = "2025-04-30T19:24:54.847Z" }, - { url = "https://files.pythonhosted.org/packages/a6/92/3d8da1efc7f3272ccc65c50cb13abd9e6a32246bb6c258175c68a91d0d80/uv-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4d1652fe3608fa564dbeaeb2465208f691ac04b57f655ebef62e9ec6d37103d", size = 16288666, upload-time = "2025-04-30T19:24:57.368Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/7d6a788c45d5e2686d01c4886ebb21149892a59bcfa15b66d0646e73aafa/uv-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48c115a3c13c3b29748e325093ee04fd48eaf91145bedc68727f78e6a1c34ab8", size = 17165785, upload-time = "2025-04-30T19:25:00.28Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9e/4d0a947ffa4b377c6e34935c23164c7914d7239154d254aa5938db6a7e83/uv-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c388172209ca5a47706666d570a45fef3dd39db9258682e10b2f62ca521f0e91", size = 18014800, upload-time = "2025-04-30T19:25:03.394Z" }, - { url = "https://files.pythonhosted.org/packages/c7/31/781288f9f53e1770128f7830841d7d269097ed70a4afa71578d45721bfa2/uv-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c97cc5e8029a8dc0e1fc39f15f746be931345bc0aeae85feceaa1828f0de87", size = 17745484, upload-time = "2025-04-30T19:25:06.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/04/030eec46217225b77ccff1f2808e64074873d86fe445be3784649506e65e/uv-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fa315366ee36ad1f734734f3153e2f334342900061fc0ed18b06f3b9bb2dfe2", size = 22103174, upload-time = "2025-04-30T19:25:09.57Z" }, - { url = "https://files.pythonhosted.org/packages/5c/07/9d85d0a9ddd49dbec18bde741ffb33d0c671a153461b094a9c73504e1b92/uv-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7236ec776c559fbc3ae4389b7cd506a2428ad9dd0402ac3d9446200ea3dc45f6", size = 17369922, upload-time = "2025-04-30T19:25:12.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/18/cfef0efe3c4ebdd81422f35215bb915fd599fc946b40306186d87e90678b/uv-0.7.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:78ec372b2f5c7ff8a034e16dd04bc579a62561a5eac4b6dfc96af60298a97d31", size = 16209878, upload-time = "2025-04-30T19:25:15.336Z" }, - { url = "https://files.pythonhosted.org/packages/31/ed/2ddd7547203ddd368b9ec56b245e09931f868daf2d2b0e29c0b69584466d/uv-0.7.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:28fd5d689ae4f8f16533f091a6dd63e1ddf3b7c782003ac8a18584ddb8823cbe", size = 16271878, upload-time = "2025-04-30T19:25:17.758Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9c/30a48a9d875b91b486286d1a4ccc081dad130acea0dca683c1786ddd7c84/uv-0.7.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:9aaacb143622cd437a446a4b316a546c02403b438cd7fd7556d62f47a9fd0a99", size = 16742005, upload-time = "2025-04-30T19:25:20.596Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b3/5550a721a1e8a99117d960f16c05ad8d39aff79a3fc1aadf2ed13da4385f/uv-0.7.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:81b86fff996c302be6aa1c1ac6eb72b97a7277c319e52c0def50d40b1ffaa617", size = 17443927, upload-time = "2025-04-30T19:25:23.134Z" }, - { url = "https://files.pythonhosted.org/packages/52/1f/71a7c3e9c79718647fea1e6fe85ccc82d2629cd858b437ae2081190045cc/uv-0.7.2-py3-none-win32.whl", hash = "sha256:19a64c38657c4fbe7c945055755500116fdaac8e121381a5245ea66823f8c500", size = 16869579, upload-time = "2025-04-30T19:25:25.745Z" }, - { url = "https://files.pythonhosted.org/packages/44/f0/4424cf64533b7576610f7de5c94183d810743b08e81072a2bb2d98316947/uv-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dc1ee6114c824f5880c584a96b2947a35817fdd3a0b752d1adbd926ae6872d1c", size = 18287842, upload-time = "2025-04-30T19:25:28.408Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5c/12ce48cab21fb0f9bde4ea0c19ec2ab88d4aa9a53e148a52cfb9a41578c9/uv-0.7.2-py3-none-win_arm64.whl", hash = "sha256:0445e56d3f9651ad84d5a7f16efabba83bf305b73594f1c1bc0659aeab952040", size = 16929582, upload-time = "2025-04-30T19:25:31.021Z" }, +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9e/4ea6d224f868badecd48b8fed17f83adb0ff62f75bc21785d91dee75c744/uv-0.7.3.tar.gz", hash = "sha256:863ceb63aefc7c2db9918313a1cb3c8bf3fc3d59b656b617db9e4abad90373f3", size = 3242256, upload-time = "2025-05-07T20:01:59.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/8b/09a9d9da09d90ec6829dc4b3e9b7ff99222b7f05bc5d292bc30b04b92209/uv-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:f37c8a6b172776fb5305afe0699907aff44a778669de7a8fbe5a9c09c1a88a97", size = 16673361, upload-time = "2025-05-07T20:01:04.641Z" }, + { url = "https://files.pythonhosted.org/packages/ba/de/794ea8c9729784c7626f05a98fe91b8367587f57f023cb95adcd8f8a9215/uv-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3e6e1fd5755d4ef4c6e1ce55bd2c6d9dec278a8bef5752703d702ce03704fe29", size = 16755964, upload-time = "2025-05-07T20:01:09.43Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/50922bfbe1631d022e0c6434ade17158b9b4e0bb7fccc77c928e32dd9021/uv-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:db8a5d5995b160158405379deadf0ffccf849a5e7ce048900b73517daf109e2c", size = 15577471, upload-time = "2025-05-07T20:01:12.235Z" }, + { url = "https://files.pythonhosted.org/packages/69/39/cba47262d9547695657885391b34e8732cb0c34b5b876b811851cd320f3a/uv-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:d246243f348796730e8ea9736ddd48702d4448d98af5e61693063ed616e30378", size = 16027456, upload-time = "2025-05-07T20:01:14.653Z" }, + { url = "https://files.pythonhosted.org/packages/e6/33/1acf89318fb987a6eb9989a6991b76b6c930b6a724ce5f1ed848519d6a5f/uv-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acef117a0c52299e60c6f7a3e60849050cd233704c561f688fac1100d113da2e", size = 16390903, upload-time = "2025-05-07T20:01:17.018Z" }, + { url = "https://files.pythonhosted.org/packages/ad/66/2fe8ec6e5390de4cfc6db312464b4f28e5b3d98d576adc42731c0aeb5073/uv-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90990e4c289feee24164c8e463fc0ebc9a336960119cd256acca7c1439f0f536", size = 17167937, upload-time = "2025-05-07T20:01:19.567Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8a/dc46e79f5fd068cb841a716a96f0344af62cf2deb2e78f57e0e147de26ac/uv-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4809e5f7f5b2d6423d6573fda5655389c955ca649499fe9750b61af95daf9b7d", size = 18077868, upload-time = "2025-05-07T20:01:22.447Z" }, + { url = "https://files.pythonhosted.org/packages/da/af/f7165a205ce8bb5e00f197d86a6fce4b4a317db0e471a31db9137ca1cc2d/uv-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acff7fba5ff40dcb5a42de496db92a3965edac7a3d687d9b013ba6e0336995df", size = 17793072, upload-time = "2025-05-07T20:01:25.942Z" }, + { url = "https://files.pythonhosted.org/packages/27/5e/2e9172ec3fa8acfa69642900d6eee8e5021f6c14d135edef524c674b4cfb/uv-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbb2d322d453e498e1431c51421cee597962ecd3f93fcef853b258e9c7e7636c", size = 22181943, upload-time = "2025-05-07T20:01:28.576Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b1/8af4ea6d09d05b9edead5e701dd91e04d55971483a7a644bab7a979bb46b/uv-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1414a026c153ae0731daed0812b17bf77d34eafedaeb3a5c72e08181aea116b", size = 17400777, upload-time = "2025-05-07T20:01:32.27Z" }, + { url = "https://files.pythonhosted.org/packages/09/ae/ccd123274ae59707e84fc5542776f89887818bad915167fbaeda65ebf52a/uv-0.7.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c976fce3d1068a1d007f50127cc7873d67643c1a60439564970f092d9be41877", size = 16306132, upload-time = "2025-05-07T20:01:36.572Z" }, + { url = "https://files.pythonhosted.org/packages/01/5c/99ef96ca53c74552b616bd341cd5d298bc8a603151343c409efeaf1552a0/uv-0.7.3-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:cc27207c35c959d2e0e873e86a80a2470a77b7a34a4512a831e8d4f7c87f4404", size = 16376728, upload-time = "2025-05-07T20:01:39.357Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/07f7e68f08e617d27ae9908a4e8deb756368b942319634956ed92d7cf35c/uv-0.7.3-py3-none-musllinux_1_1_i686.whl", hash = "sha256:5eb4872888a9fb10b62cc00be8e84822d63d3e622a5f340248e53ecf321dba96", size = 16707670, upload-time = "2025-05-07T20:01:46.816Z" }, + { url = "https://files.pythonhosted.org/packages/a9/73/385a5a55fccfebac84a88b629992e301c080640691f2e27a3e3ccee8315e/uv-0.7.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0646e463365e7277f22200ce2d43b7a44e5a3192320500b4983b4fe34d69a5fb", size = 17514613, upload-time = "2025-05-07T20:01:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/6a/97/1138bb26038805a14d930c7261faf363a5256757390b4be0aaf6e33a41c0/uv-0.7.3-py3-none-win32.whl", hash = "sha256:44e2f3fcbd1ab519bdb68986449b2e3103d2261be95f985cadcf7ec7c510b595", size = 16897117, upload-time = "2025-05-07T20:01:51.728Z" }, + { url = "https://files.pythonhosted.org/packages/64/1b/c9f0ad7c75bf0a04c52c7e766593f5e79b1ac7d97fa1cb34c6ce0cfe3746/uv-0.7.3-py3-none-win_amd64.whl", hash = "sha256:0a446d4e5b10ce8a793156a276727bb7affa96a85e80dc5ad34e0c2de7e71cc8", size = 18323992, upload-time = "2025-05-07T20:01:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/47/1b/7ca1b8ec4bcf1c807f61e6ced7ca704791843cf1297db5edb54db07bd1db/uv-0.7.3-py3-none-win_arm64.whl", hash = "sha256:cb2547fd1466698e9b4f11de5eef7055b8cbcc3c693d79f6d747e3f8e6be2ab7", size = 17017988, upload-time = "2025-05-07T20:01:57.283Z" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, + { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, + { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, ] [[package]] name = "virtualenv" -version = "20.31.1" +version = "20.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/07/655f4fb9592967f49197b00015bb5538d3ed1f8f96621a10bebc3bb822e2/virtualenv-20.31.1.tar.gz", hash = "sha256:65442939608aeebb9284cd30baca5865fcd9f12b58bb740a24b220030df46d26", size = 6076234, upload-time = "2025-05-05T22:45:39.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/67/7d7559264a6f8ec9ce4e397ddd9157a510be1e174dc98be898b6c18eeef4/virtualenv-20.31.1-py3-none-any.whl", hash = "sha256:f448cd2f1604c831afb9ea238021060be2c0edbcad8eb0a4e8b4e14ff11a5482", size = 6057843, upload-time = "2025-05-05T22:45:37.127Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, ] [[package]]