diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ba68c57..062b9bf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: timeout-minutes: 5 steps: - name: "Check out repository code" - uses: "actions/checkout@v5" + uses: "actions/checkout@v6" - name: Set up Python uses: actions/setup-python@v6 with: @@ -91,6 +91,8 @@ jobs: run: "uv run ruff check ." - name: "Linting: ruff format" run: "uv run ruff format --check --diff ." + - name: "Linting: ty check" + run: "uv run ty check ." markdown-lint: @@ -104,7 +106,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v6" - name: "Linting: markdownlint" - uses: DavidAnson/markdownlint-cli2-action@v21 + uses: DavidAnson/markdownlint-cli2-action@v22 with: config: .markdownlint.yaml globs: | @@ -138,7 +140,7 @@ jobs: timeout-minutes: 5 steps: - name: "Check out repository code" - uses: "actions/checkout@v5" + uses: "actions/checkout@v6" - name: "Set up Python" uses: "actions/setup-python@v6" with: diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 96bb84a7..e6bce2d3 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -42,7 +42,7 @@ jobs: python-version: "3.12" - name: Install UV - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: version: ${{ needs.prepare-environment.outputs.UV_VERSION }} @@ -52,7 +52,7 @@ jobs: submodules: true - name: Cache UV dependencies - uses: "actions/cache@v4" + uses: "actions/cache@v5" id: "cached-uv-dependencies" with: path: ".venv" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f0a088b..368254c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: python-version: "3.12" - name: Install UV - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v7 with: version: "${{ needs.prepare-environment.outputs.UV_VERSION }}" diff --git a/changelog/+1b40f022.housekeeping.md b/changelog/+1b40f022.housekeeping.md new file mode 100644 index 00000000..40a566c7 --- /dev/null +++ b/changelog/+1b40f022.housekeeping.md @@ -0,0 +1 @@ +Fixed Python 3.14 compatibility warnings. Testing now requires pytest>=9. diff --git a/infrahub_sdk/analyzer.py b/infrahub_sdk/analyzer.py index 09a5ce80..1f54c723 100644 --- a/infrahub_sdk/analyzer.py +++ b/infrahub_sdk/analyzer.py @@ -30,10 +30,10 @@ class GraphQLOperation(BaseModel): class GraphQLQueryAnalyzer: - def __init__(self, query: str, schema: GraphQLSchema | None = None) -> None: + def __init__(self, query: str, schema: GraphQLSchema | None = None, document: DocumentNode | None = None) -> None: self.query: str = query self.schema: GraphQLSchema | None = schema - self.document: DocumentNode = parse(self.query) + self.document: DocumentNode = document or parse(self.query) self._fields: dict | None = None @property diff --git a/infrahub_sdk/checks.py b/infrahub_sdk/checks.py index e0880da9..e1dfc404 100644 --- a/infrahub_sdk/checks.py +++ b/infrahub_sdk/checks.py @@ -1,7 +1,7 @@ from __future__ import annotations -import asyncio import importlib +import inspect import os import warnings from abc import abstractmethod @@ -160,7 +160,7 @@ async def run(self, data: dict | None = None) -> bool: data = await self.collect_data() unpacked = data.get("data") or data - if asyncio.iscoroutinefunction(self.validate): + if inspect.iscoroutinefunction(self.validate): await self.validate(data=unpacked) else: self.validate(data=unpacked) diff --git a/infrahub_sdk/ctl/cli_commands.py b/infrahub_sdk/ctl/cli_commands.py index 538bad6b..e76225e4 100644 --- a/infrahub_sdk/ctl/cli_commands.py +++ b/infrahub_sdk/ctl/cli_commands.py @@ -3,6 +3,7 @@ import asyncio import functools import importlib +import inspect import logging import platform import sys @@ -240,7 +241,7 @@ async def _run_transform( console.print("[yellow] you can specify a different branch with --branch") raise typer.Abort() - if asyncio.iscoroutinefunction(transform_func): + if inspect.iscoroutinefunction(transform_func): output = await transform_func(response) else: output = transform_func(response) diff --git a/infrahub_sdk/ctl/utils.py b/infrahub_sdk/ctl/utils.py index 9db07957..f87a81a1 100644 --- a/infrahub_sdk/ctl/utils.py +++ b/infrahub_sdk/ctl/utils.py @@ -1,6 +1,6 @@ from __future__ import annotations -import asyncio +import inspect import logging import traceback from collections.abc import Callable, Coroutine @@ -83,7 +83,7 @@ def catch_exception( console = Console() def decorator(func: Callable[..., T]) -> Callable[..., T | Coroutine[Any, Any, T]]: - if asyncio.iscoroutinefunction(func): + if inspect.iscoroutinefunction(func): @wraps(func) async def async_wrapper(*args: Any, **kwargs: Any) -> T: diff --git a/infrahub_sdk/transforms.py b/infrahub_sdk/transforms.py index 0c07e296..ee17605f 100644 --- a/infrahub_sdk/transforms.py +++ b/infrahub_sdk/transforms.py @@ -1,6 +1,6 @@ from __future__ import annotations -import asyncio +import inspect import os from abc import abstractmethod from typing import TYPE_CHECKING, Any @@ -75,7 +75,7 @@ async def run(self, data: dict | None = None) -> Any: unpacked = data.get("data") or data await self.process_nodes(data=unpacked) - if asyncio.iscoroutinefunction(self.transform): + if inspect.iscoroutinefunction(self.transform): return await self.transform(data=unpacked) return self.transform(data=unpacked) diff --git a/pyproject.toml b/pyproject.toml index 924d0ddd..a2fc1411 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,8 +70,8 @@ all = [ # Core optional dependencies tests = [ "infrahub-testcontainers>=1.5.1", - "pytest", - "pytest-asyncio<0.23", + "pytest>=9.0,<9.1", + "pytest-asyncio>=1.3,<1.4", "pytest-clarity>=1.0.1", "pytest-cov>=4.0.0", "pytest-httpx>=0.30", @@ -82,6 +82,7 @@ lint = [ "mypy==1.11.2", "ruff==0.14.5", "astroid>=3.1,<4.0", + "ty==0.0.4", ] types = [ "types-ujson", @@ -108,6 +109,7 @@ exclude_lines = ["if TYPE_CHECKING:", "raise NotImplementedError()"] [tool.pytest.ini_options] asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" testpaths = ["tests"] filterwarnings = [ "ignore:Module already imported so cannot be rewritten", @@ -115,6 +117,68 @@ filterwarnings = [ ] addopts = "-vs --cov-report term-missing --cov-report xml --dist loadscope" +[tool.ty] + +[tool.ty.environment] +python-version = "3.10" + +[[tool.ty.overrides]] +include = ["infrahub_sdk/**"] + +[tool.ty.overrides.rules] +################################################################################################## +# The ignored rules below should be removed once the code has been updated, they are included # +# like this so that we can reactivate them one by one. # +################################################################################################## +division-by-zero = "ignore" +invalid-argument-type = "ignore" +invalid-assignment = "ignore" +invalid-await = "ignore" +invalid-return-type = "ignore" +invalid-type-form = "ignore" +missing-argument = "ignore" +no-matching-overload = "ignore" +possibly-unresolved-reference = "ignore" +redundant-cast = "ignore" +too-many-positional-arguments = "ignore" +type-assertion-failure = "ignore" +unknown-argument = "ignore" +unresolved-attribute = "ignore" +unresolved-import = "ignore" +unsupported-operator = "ignore" + + +[[tool.ty.overrides]] +include = ["tests/**"] + +[tool.ty.overrides.rules] +################################################################################################## +# The ignored rules below should be removed once the code has been updated, they are included # +# like this so that we can reactivate them one by one. # +################################################################################################## +invalid-argument-type = "ignore" +invalid-assignment = "ignore" +invalid-method-override = "ignore" +invalid-return-type = "ignore" +no-matching-overload = "ignore" +non-subscriptable = "ignore" +not-iterable = "ignore" +possibly-missing-attribute = "ignore" +unresolved-attribute = "ignore" +unresolved-import = "ignore" + + +[[tool.ty.overrides]] +include = ["docs/**"] + +[tool.ty.overrides.rules] +################################################################################################## +# The ignored rules below should be removed once the code has been updated, they are included # +# like this so that we can reactivate them one by one. # +################################################################################################## +invalid-assignment = "ignore" + + [tool.mypy] pretty = true ignore_missing_imports = true diff --git a/tasks.py b/tasks.py index e746817e..e47bffce 100644 --- a/tasks.py +++ b/tasks.py @@ -173,6 +173,15 @@ def lint_mypy(context: Context) -> None: context.run(exec_cmd) +@task +def lint_ty(context: Context) -> None: + """Run ty type checker against all Python files.""" + print(" - Check code with ty") + exec_cmd = "uv run ty check ." + with context.cd(MAIN_DIRECTORY_PATH): + context.run(exec_cmd) + + @task def lint_ruff(context: Context) -> None: """Run Linter to check all Python files.""" @@ -220,6 +229,7 @@ def lint_all(context: Context) -> None: """Run all linters.""" lint_yaml(context) lint_ruff(context) + lint_ty(context) lint_mypy(context) lint_docs(context) diff --git a/tests/conftest.py b/tests/conftest.py index 953e9c6b..5d19956e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ -import asyncio import os from collections.abc import Generator import pytest +import pytest_asyncio from infrahub_sdk.ctl import config @@ -11,13 +11,11 @@ ENV_VARS_TO_CLEAN = ["INFRAHUB_ADDRESS", "INFRAHUB_TOKEN", "INFRAHUB_BRANCH", "INFRAHUB_USERNAME", "INFRAHUB_PASSWORD"] -@pytest.fixture(scope="session") -def event_loop() -> Generator[asyncio.AbstractEventLoop]: - """Overrides pytest default function scoped event loop""" - policy = asyncio.get_event_loop_policy() - loop = policy.new_event_loop() - yield loop - loop.close() +def pytest_collection_modifyitems(items) -> None: + pytest_asyncio_tests = (item for item in items if pytest_asyncio.is_async_test(item)) + session_scope_marker = pytest.mark.asyncio(loop_scope="session") + for async_test in pytest_asyncio_tests: + async_test.add_marker(session_scope_marker, append=False) @pytest.fixture(scope="session", autouse=True) diff --git a/uv.lock b/uv.lock index 3cf507d6..e2254991 100644 --- a/uv.lock +++ b/uv.lock @@ -124,6 +124,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483, upload-time = "2024-03-13T03:41:26.969Z" }, ] +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + [[package]] name = "black" version = "25.9.0" @@ -780,6 +789,7 @@ dev = [ { name = "requests" }, { name = "ruff" }, { name = "towncrier" }, + { name = "ty" }, { name = "types-python-slugify" }, { name = "types-pyyaml" }, { name = "types-ujson" }, @@ -789,6 +799,7 @@ lint = [ { name = "astroid" }, { name = "mypy" }, { name = "ruff" }, + { name = "ty" }, { name = "yamllint" }, ] tests = [ @@ -848,8 +859,8 @@ dev = [ { name = "ipython" }, { name = "mypy", specifier = "==1.11.2" }, { name = "pre-commit", specifier = ">=2.20.0" }, - { name = "pytest" }, - { name = "pytest-asyncio", specifier = "<0.23" }, + { name = "pytest", specifier = ">=9.0,<9.1" }, + { name = "pytest-asyncio", specifier = ">=1.3,<1.4" }, { name = "pytest-clarity", specifier = ">=1.0.1" }, { name = "pytest-cov", specifier = ">=4.0.0" }, { name = "pytest-httpx", specifier = ">=0.30" }, @@ -857,6 +868,7 @@ dev = [ { name = "requests" }, { name = "ruff", specifier = "==0.14.5" }, { name = "towncrier", specifier = ">=24.8.0" }, + { name = "ty", specifier = "==0.0.4" }, { name = "types-python-slugify", specifier = ">=8.0.0.3" }, { name = "types-pyyaml" }, { name = "types-ujson" }, @@ -866,12 +878,13 @@ lint = [ { name = "astroid", specifier = ">=3.1,<4.0" }, { name = "mypy", specifier = "==1.11.2" }, { name = "ruff", specifier = "==0.14.5" }, + { name = "ty", specifier = "==0.0.4" }, { name = "yamllint" }, ] tests = [ { name = "infrahub-testcontainers", specifier = ">=1.5.1" }, - { name = "pytest" }, - { name = "pytest-asyncio", specifier = "<0.23" }, + { name = "pytest", specifier = ">=9.0,<9.1" }, + { name = "pytest-asyncio", specifier = ">=1.3,<1.4" }, { name = "pytest-clarity", specifier = ">=1.0.1" }, { name = "pytest-cov", specifier = ">=4.0.0" }, { name = "pytest-httpx", specifier = ">=0.30" }, @@ -1972,7 +1985,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1983,21 +1996,23 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "0.21.2" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/53/57663d99acaac2fcdafdc697e52a9b1b7d6fcf36616281ff9768a44e7ff3/pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45", size = 30656, upload-time = "2024-04-29T13:23:24.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/ce/1e4b53c213dce25d6e8b163697fbce2d43799d76fa08eea6ad270451c370/pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b", size = 13368, upload-time = "2024-04-29T13:23:23.126Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] @@ -2027,15 +2042,15 @@ wheels = [ [[package]] name = "pytest-httpx" -version = "0.35.0" +version = "0.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/89/5b12b7b29e3d0af3a4b9c071ee92fa25a9017453731a38f08ba01c280f4c/pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f", size = 54146, upload-time = "2024-11-28T19:16:54.237Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/5574834da9499066fa1a5ea9c336f94dba2eae02298d36dab192fcf95c86/pytest_httpx-0.36.0.tar.gz", hash = "sha256:9edb66a5fd4388ce3c343189bc67e7e1cb50b07c2e3fc83b97d511975e8a831b", size = 56793, upload-time = "2025-12-02T16:34:57.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/ed/026d467c1853dd83102411a78126b4842618e86c895f93528b0528c7a620/pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744", size = 19442, upload-time = "2024-11-28T19:16:52.787Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d2/1eb1ea9c84f0d2033eb0b49675afdc71aa4ea801b74615f00f3c33b725e3/pytest_httpx-0.36.0-py3-none-any.whl", hash = "sha256:bd4c120bb80e142df856e825ec9f17981effb84d159f9fa29ed97e2357c3a9c8", size = 20229, upload-time = "2025-12-02T16:34:56.45Z" }, ] [[package]] @@ -2737,6 +2752,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] +[[package]] +name = "ty" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/d9/97d5808e851f790e58f8a54efb5c7b9f404640baf9e295f424846040b316/ty-0.0.4.tar.gz", hash = "sha256:2ea47a0089d74730658ec4e988c8ef476a1e9bd92df3e56709c4003c2895ff3b", size = 4780289, upload-time = "2025-12-19T00:13:53.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/94/b32a962243cc8a16e8dc74cf1fe75e8bb013d0e13e71bb540e2c86214b61/ty-0.0.4-py3-none-linux_armv6l.whl", hash = "sha256:5225da65a8d1defeb21ee9d74298b1b97c6cbab36e235a310c1430d9079e4b6a", size = 9762399, upload-time = "2025-12-19T00:14:11.261Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d2/7c76e0c22ddfc2fcd4a3458a65f87ce074070eb1c68c07ee475cc2b6ea68/ty-0.0.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f87770d7988f470b795a2043185082fa959dbe1979a11b4bfe20f1214d37bd6e", size = 9590410, upload-time = "2025-12-19T00:13:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/a5/84/de4b1fc85669faca3622071d5a3f3ec7bfb239971f368c28fae461d3398a/ty-0.0.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf68b8ea48674a289d733b4786aecc259242a2d9a920b3ec8583db18c67496a", size = 9131113, upload-time = "2025-12-19T00:14:08.593Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ff/b5bf385b6983be56a470856bbcbac1b7e816bcd765a7e9d39ab2399e387d/ty-0.0.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc396d76a57e527393cae4ee8faf23b93be3df9e93202f39925721a7a2bb7b8", size = 9599152, upload-time = "2025-12-19T00:13:40.484Z" }, + { url = "https://files.pythonhosted.org/packages/36/d6/9880ba106f2f20d13e6a5dca5d5ca44bfb3782936ee67ff635f89a2959c0/ty-0.0.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c893b968d2f9964a4d4db9992c9ba66b01f411b1f48dffcde08622e19cd6ab97", size = 9585368, upload-time = "2025-12-19T00:14:00.994Z" }, + { url = "https://files.pythonhosted.org/packages/3f/53/503cfc18bc4c7c4e02f89dd43debc41a6e343b41eb43df658dfb493a386d/ty-0.0.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:526c925b80d68a53c165044d2370fcfc0def1f119f7b7e483ee61d24da6fb891", size = 9998412, upload-time = "2025-12-19T00:14:18.653Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bd/dd2d3e29834da5add2eda0ab5b433171ce9ce9a248c364d2e237f82073d7/ty-0.0.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:857f605a7fa366b6c6e6f38abc311d0606be513c2bee8977b5c8fd4bde1a82d5", size = 10853890, upload-time = "2025-12-19T00:13:50.891Z" }, + { url = "https://files.pythonhosted.org/packages/07/fe/28ba3be1672e6b8df46e43de66a02dc076ffba7853d391a5466421886225/ty-0.0.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4cc981aa3ebdac2c233421b1e58c80b0df6a8e6e6fa8b9e69fbdfd2f82768af", size = 10587263, upload-time = "2025-12-19T00:14:21.577Z" }, + { url = "https://files.pythonhosted.org/packages/26/9c/bb598772043f686afe5bc26cb386020709c1a0bcc164bc22ad9da2b4f55d/ty-0.0.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b03b2708b0bf67c76424a860f848aebaa4772c05529170c3761bfcaea93ec199", size = 10401204, upload-time = "2025-12-19T00:13:43.453Z" }, + { url = "https://files.pythonhosted.org/packages/ac/18/71765e9d63669bf09461c3fea84a7a63232ccb0e83b84676f07b987fc217/ty-0.0.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:469890e885544beb129c21e2f8f15321f0573d094aec13da68593c5f86389ff9", size = 10129713, upload-time = "2025-12-19T00:14:13.725Z" }, + { url = "https://files.pythonhosted.org/packages/c3/2d/c03eba570aa85e9c361de5ed36d60b9ab139e93ee91057f455ab4af48e54/ty-0.0.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:abfd928d09567e12068aeca875e920def3badf1978896f474aa4b85b552703c4", size = 9586203, upload-time = "2025-12-19T00:14:03.423Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/8c3c82a8df69bd4417c77be4f895d043db26dd47bfcc90b33dc109cd0096/ty-0.0.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:44b8e94f9d64df12eae4cf8031c5ca9a4c610b57092b26ad3d68d91bcc7af122", size = 9608230, upload-time = "2025-12-19T00:13:58.252Z" }, + { url = "https://files.pythonhosted.org/packages/51/0c/d8ba3a85c089c246ef6bd49d0f0b40bc0f9209bb819e8c02ccbea5cb4d57/ty-0.0.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9d6a439813e21a06769daf858105818c385d88018929d4a56970d4ddd5cd3df2", size = 9725125, upload-time = "2025-12-19T00:14:05.996Z" }, + { url = "https://files.pythonhosted.org/packages/4d/38/e30f64ad1e40905c766576ec70cffc69163591a5842ce14652672f6ab394/ty-0.0.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c3cfcf26cfe6c828e91d7a529cc2dda37bc3b51ba06909c9be07002a6584af52", size = 10237174, upload-time = "2025-12-19T00:14:23.858Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d7/8d650aa0be8936dd3ed74e2b0655230e2904caa6077c30c16a089b523cff/ty-0.0.4-py3-none-win32.whl", hash = "sha256:58bbf70dd27af6b00dedbdebeec92d5993aa238664f96fa5c0064930f7a0d30b", size = 9188434, upload-time = "2025-12-19T00:13:45.875Z" }, + { url = "https://files.pythonhosted.org/packages/82/d7/9fc0c81cf0b0d281ac9c18bfbdb4d6bae2173503ba79e40b210ab41c2c8b/ty-0.0.4-py3-none-win_amd64.whl", hash = "sha256:7c2db0f96218f08c140bd9d3fcbb1b3c8c5c4f0c9b0a5624487f0a2bf4b76163", size = 10019313, upload-time = "2025-12-19T00:14:15.968Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b8/3e3246738eed1cd695c5964a401f3b9c757d20ac21fdae06281af9f40ef6/ty-0.0.4-py3-none-win_arm64.whl", hash = "sha256:69f14fc98e4a847afa9f8c5d5234d008820dbc09c7dcdb3ac1ba16628f5132df", size = 9561857, upload-time = "2025-12-19T00:13:48.382Z" }, +] + [[package]] name = "typer" version = "0.12.5"