diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index ff92327..f1fd95a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -45,7 +45,7 @@ uv run poe test-conformance We use: - **ruff** for linting and formatting -- **pyright** for type checking +- **ty** for type checking - **pytest** for testing The project follows strict type checking and formatting standards. diff --git a/README.md b/README.md index 6250fb8..59e66fa 100644 --- a/README.md +++ b/README.md @@ -398,7 +398,7 @@ service YourService { ## Development -We use `ruff` for linting and formatting, `pyright` for type checking, and `tombi` for TOML linting and formatting. +We use `ruff` for linting and formatting, `ty` for type checking, and `tombi` for TOML linting and formatting. We rely on the conformance test suit (in [./conformance](./conformance)) to verify behavior. diff --git a/conformance/test/__init__.py b/conformance/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/conformance/test/_util.py b/conformance/test/_util.py index faea3e2..7b221ae 100644 --- a/conformance/test/_util.py +++ b/conformance/test/_util.py @@ -28,7 +28,7 @@ def maybe_patch_args_with_debug(args: list[str]) -> list[str]: # This invokes internal methods from bundles provided by the IDE # and may not always work. try: - from pydevd import ( # pyright:ignore[reportMissingImports] - provided by IDE # noqa: PLC0415 + from pydevd import ( # ty: ignore[unresolved-import] - provided by IDE # noqa: PLC0415 _pydev_bundle, ) diff --git a/conformance/test/test_client.py b/conformance/test/test_client.py index 89395da..99e9c97 100644 --- a/conformance/test/test_client.py +++ b/conformance/test/test_client.py @@ -6,8 +6,7 @@ from typing import TYPE_CHECKING import pytest - -from ._util import VERSION_CONFORMANCE, coverage_env, maybe_patch_args_with_debug +from _util import VERSION_CONFORMANCE, coverage_env, maybe_patch_args_with_debug if TYPE_CHECKING: from coverage import Coverage diff --git a/conformance/test/test_server.py b/conformance/test/test_server.py index 20fec15..06f2ce5 100644 --- a/conformance/test/test_server.py +++ b/conformance/test/test_server.py @@ -7,8 +7,7 @@ from typing import TYPE_CHECKING import pytest - -from ._util import VERSION_CONFORMANCE, coverage_env, maybe_patch_args_with_debug +from _util import VERSION_CONFORMANCE, coverage_env, maybe_patch_args_with_debug if TYPE_CHECKING: from coverage import Coverage diff --git a/example/example/eliza_service_sync.py b/example/example/eliza_service_sync.py index 2477bee..38d2259 100644 --- a/example/example/eliza_service_sync.py +++ b/example/example/eliza_service_sync.py @@ -67,4 +67,4 @@ def health_check(): eliza_app = ElizaServiceWSGIApplication(DemoElizaServiceSync()) -app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {eliza_app.path: eliza_app}) +app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {eliza_app.path: eliza_app}) # ty: ignore[invalid-assignment] diff --git a/poe_tasks.toml b/poe_tasks.toml index 6b12387..7e7ae00 100644 --- a/poe_tasks.toml +++ b/poe_tasks.toml @@ -95,7 +95,7 @@ cmd = "tombi lint" [tasks.lint-types] help = "Apply type checking to Python files" -cmd = "pyright" +cmd = "ty check ." [tasks.test] help = "Run unit tests" diff --git a/pyproject.toml b/pyproject.toml index b280a42..ed2e470 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,10 +47,12 @@ dev = [ "gunicorn==25.3.0", "hypercorn==0.18.0", "poethepoet==0.45.0", - "pyright[nodejs]==1.1.409", "pyvoy==0.3.0", "ruff==0.15.12", "tombi==0.10.2", + "ty==0.0.34", + "types-grpcio==1.0.0.20260408", + "types-protobuf==7.34.1.20260503", "typing_extensions==4.15.0", "uvicorn==0.46.0", "zstandard==0.25.0", @@ -79,30 +81,6 @@ docs = ["mkdocstrings-python==2.0.3", "zensical==0.0.39"] requires = ["uv_build>=0.11.0,<0.12.0"] build-backend = "uv_build" -[tool.pyright] -exclude = [ - # Defaults. - "**/node_modules", - "**/__pycache__", - "**/.*", - ".venv", - - # Recent versions of typeshed fail on 5.x gencode. - # https://github.com/python/typeshed/pull/15677 - "**/*_pb.py", - - # Recent versions of typeshed have improved typing of functions that then expose - # type errors for accessing - "**/*_pb2.py", - - # GRPC python files don't typecheck on their own. - # See https://github.com/grpc/grpc/issues/39555 - "**/*_pb2_grpc.py", - - # TODO: Work out the import issues to allow it to work. - "conformance/**", -] - [tool.pytest] # Turn all warnings into errors filterwarnings = ["error"] @@ -250,6 +228,19 @@ typing-extensions = false required-imports = ["from __future__ import annotations"] split-on-trailing-comma = false +[tool.ty.src] +exclude = [ + # gRPC generated code references `grpc.experimental`, which is not exposed by the + # `grpc` package's public API. See https://github.com/grpc/grpc/issues/39555. + "**/*_pb2_grpc.py", +] + +[tool.ty.environment] +# Conformance test files import generated code as `from gen.connectrpc...` and +# sibling modules like `_util`/`_cov_embed`. Pytest and the launched server/client +# subprocesses put `conformance/test/` on sys.path; teach ty to do the same. +extra-paths = ["conformance/test"] + [tool.uv] constraint-dependencies = [ "coverage==7.13.2", diff --git a/src/connectrpc/_asyncio_timeout.py b/src/connectrpc/_asyncio_timeout.py index 5f60385..3c68bdc 100644 --- a/src/connectrpc/_asyncio_timeout.py +++ b/src/connectrpc/_asyncio_timeout.py @@ -119,8 +119,8 @@ async def __aexit__( raise TimeoutError from exc_val if exc_val is not None: self._insert_timeout_error(exc_val) - if _HAX_EXCEPTION_GROUP and isinstance(exc_val, ExceptionGroup): # pyright: ignore[reportUndefinedVariable] # noqa: F821 - for exc in exc_val.exceptions: # pyright: ignore[reportAttributeAccessIssue] + if _HAX_EXCEPTION_GROUP and isinstance(exc_val, ExceptionGroup): # noqa: F821 + for exc in exc_val.exceptions: self._insert_timeout_error(exc) elif self._state is _State.ENTERED: self._state = _State.EXITED diff --git a/src/connectrpc/_client_async.py b/src/connectrpc/_client_async.py index 32c44a0..a3c3a04 100644 --- a/src/connectrpc/_client_async.py +++ b/src/connectrpc/_client_async.py @@ -2,6 +2,7 @@ import asyncio import functools +import sys from asyncio import CancelledError, sleep, wait_for from typing import TYPE_CHECKING, Any, Protocol, TypeVar from urllib.parse import urlencode @@ -11,7 +12,6 @@ from pyqwest import Headers as HTTPHeaders from . import _client_shared -from ._asyncio_timeout import timeout as asyncio_timeout from ._codec import proto_binary_codec from ._compression import IdentityCompression, _gzip, resolve_compressions from ._interceptor_async import ( @@ -30,15 +30,12 @@ from .errors import ConnectError from .protocol import ProtocolType -try: - from asyncio import ( - timeout as asyncio_timeout, # pyright: ignore[reportAttributeAccessIssue] - ) -except ImportError: +if sys.version_info >= (3, 11): + from asyncio import timeout as asyncio_timeout +else: from ._asyncio_timeout import timeout as asyncio_timeout if TYPE_CHECKING: - import sys from collections.abc import AsyncIterator, Iterable, Mapping from types import TracebackType diff --git a/src/connectrpc/_codec.py b/src/connectrpc/_codec.py index 864cdb8..5c5b374 100644 --- a/src/connectrpc/_codec.py +++ b/src/connectrpc/_codec.py @@ -33,7 +33,7 @@ def decode(self, data: bytes | bytearray, message: U) -> U: class ProtoBinaryCodec(Codec[Message, V]): - """Codec for Protocol bytes | bytearrays binary format.""" + """Codec for the Protocol Buffers binary format.""" def name(self) -> str: return "proto" @@ -42,12 +42,16 @@ def encode(self, message: Message) -> bytes: return message.SerializeToString() def decode(self, data: bytes | bytearray, message: V) -> V: - message.ParseFromString(data) # pyright: ignore[reportArgumentType] - type is incorrect + # ParseFromString accepts the buffer protocol at runtime, but typeshed + # declares only `bytes`. Skipping the conversion avoids a copy on the + # streaming decode path (see _envelope.py). Tracked upstream in + # https://github.com/python/typeshed/issues/9006. + message.ParseFromString(data) # ty: ignore[invalid-argument-type] return message class ProtoJSONCodec(Codec[Message, V]): - """Codec for Protocol bytes | bytearrays JSON format.""" + """Codec for the Protocol Buffers JSON format.""" def __init__(self, name: str = "json") -> None: self._name = name @@ -59,13 +63,16 @@ def encode(self, message: Message) -> bytes: return MessageToJson(message).encode() def decode(self, data: bytes | bytearray, message: V) -> V: - MessageFromJson(data, message) # pyright: ignore[reportArgumentType] - type is incorrect + # google.protobuf.json_format.Parse accepts the buffer protocol at + # runtime, but typeshed declares only `bytes | str`. See + # ProtoBinaryCodec.decode for the upstream tracking issue. + MessageFromJson(data, message) # ty: ignore[invalid-argument-type] return message _proto_binary_codec = ProtoBinaryCodec() _proto_json_codec = ProtoJSONCodec() -_default_codecs = [_proto_binary_codec, _proto_json_codec] +_default_codecs: list[Codec] = [_proto_binary_codec, _proto_json_codec] def get_default_codecs() -> list[Codec]: diff --git a/src/connectrpc/_headers.py b/src/connectrpc/_headers.py index 9ffb7a1..092d8d7 100644 --- a/src/connectrpc/_headers.py +++ b/src/connectrpc/_headers.py @@ -9,6 +9,7 @@ Sequence, ValuesView, ) +from typing import cast class Headers(MutableMapping[str, str]): @@ -28,6 +29,10 @@ def __init__( ) -> None: self._extra = None if isinstance(items, Mapping): + # ty does not preserve type parameters when narrowing a union via + # `isinstance(x, Mapping)`, so we re-assert `Mapping[str, str]`. + # See https://github.com/astral-sh/ty/issues/456. + items = cast("Mapping[str, str]", items) self._store = {k.lower(): v for k, v in items.items()} else: self._store = {} diff --git a/src/connectrpc/_protocol_grpc.py b/src/connectrpc/_protocol_grpc.py index 0b98948..06afe35 100644 --- a/src/connectrpc/_protocol_grpc.py +++ b/src/connectrpc/_protocol_grpc.py @@ -181,7 +181,7 @@ def end(self, user_trailers: Headers, error: ConnectWireError | None) -> Headers class GRPCWebEnvelopeWriter(GRPCEnvelopeWriter): - def end(self, user_trailers: Headers, error: ConnectWireError | None) -> bytes: # pyright: ignore[reportIncompatibleMethodOverride] + def end(self, user_trailers: Headers, error: ConnectWireError | None) -> bytes: # ty: ignore[invalid-method-override] trailers = super().end(user_trailers, error) data = "".join(f"{k}: {v}\r\n" for k, v in trailers.allitems()).encode() if self._compression: diff --git a/uv.lock b/uv.lock index 120ddba..da7ee57 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 requires-python = ">=3.10" -resolution-markers = [ - "python_full_version >= '3.11'", - "python_full_version < '3.11'", -] [manifest] members = [ @@ -202,7 +198,6 @@ dev = [ { name = "opentelemetry-instrumentation-wsgi" }, { name = "opentelemetry-sdk" }, { name = "poethepoet" }, - { name = "pyright", extra = ["nodejs"] }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -210,6 +205,9 @@ dev = [ { name = "pyvoy" }, { name = "ruff" }, { name = "tombi" }, + { name = "ty" }, + { name = "types-grpcio" }, + { name = "types-protobuf" }, { name = "typing-extensions" }, { name = "uvicorn" }, { name = "zstandard" }, @@ -239,7 +237,6 @@ dev = [ { name = "opentelemetry-instrumentation-wsgi", specifier = "==0.62b1" }, { name = "opentelemetry-sdk", specifier = "==1.41.1" }, { name = "poethepoet", specifier = "==0.45.0" }, - { name = "pyright", extras = ["nodejs"], specifier = "==1.1.409" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -247,6 +244,9 @@ dev = [ { name = "pyvoy", specifier = "==0.3.0" }, { name = "ruff", specifier = "==0.15.12" }, { name = "tombi", specifier = "==0.10.2" }, + { name = "ty", specifier = "==0.0.34" }, + { name = "types-grpcio", specifier = "==1.0.0.20260408" }, + { name = "types-protobuf", specifier = "==7.34.1.20260503" }, { name = "typing-extensions", specifier = "==4.15.0" }, { name = "uvicorn", specifier = "==0.46.0" }, { name = "zstandard", specifier = "==0.25.0" }, @@ -414,7 +414,7 @@ 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 = [ @@ -990,31 +990,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] -[[package]] -name = "nodeenv" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, -] - -[[package]] -name = "nodejs-wheel-binaries" -version = "24.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/70/a1e4f4d5986768ab90cc860b1cc3660fd2ded74ca175a900a5c29f839c7d/nodejs_wheel_binaries-24.15.0.tar.gz", hash = "sha256:b43f5c4f6e5768d8845b2ae4682eb703a19bf7aadc84187e2d903ed3a611c859", size = 8057, upload-time = "2026-04-19T15:48:16.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/66/54051d14853d6ab4fb85f8be9b042b530be653357fb9a19557498bc91ab7/nodejs_wheel_binaries-24.15.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:a6232fa8b754220941f52388c8ead923f7c1c7fdf0ea0d98f657523bd9a81ef4", size = 55173485, upload-time = "2026-04-19T15:47:34.561Z" }, - { url = "https://files.pythonhosted.org/packages/ad/5f/66acada164da5ca10a0824db021aa7394ae18396c550cd9280e839a43126/nodejs_wheel_binaries-24.15.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:001a6b62c69d9109c1738163cca00608dd2722e8663af59300054ea02610972d", size = 55348100, upload-time = "2026-04-19T15:47:40.521Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2d/0cbd5ff40c9bb030ca1735d8f8793bd74f08a4cbd49100a1d19313ea57ab/nodejs_wheel_binaries-24.15.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:0fbc48765e60ed0ff30d43898dbf5cadbadf2e5f1e7f204afc2b01493b7ebce6", size = 59668206, upload-time = "2026-04-19T15:47:46.848Z" }, - { url = "https://files.pythonhosted.org/packages/da/d5/91ac63951ec75927a486b83b8cafe650e360fa70ac01dc94adfb32b93b97/nodejs_wheel_binaries-24.15.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:20ee0536809795da8a4942fc1ab4cbdebbcaaf29383eab67ba8874268fb00008", size = 60206736, upload-time = "2026-04-19T15:47:52.668Z" }, - { url = "https://files.pythonhosted.org/packages/db/72/dc22776974d928869c0c30d23ee98ed7df254243c2df68f09f5963e8e8b8/nodejs_wheel_binaries-24.15.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1fade6c214285e72472ca40a631e98ff36559671cd5eefc8bf009471d67f04b4", size = 61720456, upload-time = "2026-04-19T15:47:58.325Z" }, - { url = "https://files.pythonhosted.org/packages/01/0a/34461b9050cb45ee371dccdefc622aef6351506ea2691b08fc761ca67150/nodejs_wheel_binaries-24.15.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3984cb8d87766567aee67a49743227ab40ede6f47734ec990ff90e50b74e7740", size = 62326172, upload-time = "2026-04-19T15:48:04.094Z" }, - { url = "https://files.pythonhosted.org/packages/c9/17/09252bf35672dba926649d59dfe51443a0f6955ad13784e91131d5ec82a2/nodejs_wheel_binaries-24.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:a437601956b532dcb3082046e6978e622733f90edc0932cbb9adb3bb97a16501", size = 41543461, upload-time = "2026-04-19T15:48:09.332Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7e/b649777d148e1e0c2ce349156603cdb12f7ed99921b95d93717393650193/nodejs_wheel_binaries-24.15.0-py2.py3-none-win_arm64.whl", hash = "sha256:bdf4a431e08321a32efc604111c6f23941f87055d796a537e8c4110daecad23f", size = 39233248, upload-time = "2026-04-19T15:48:13.326Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.41.1" @@ -1171,11 +1146,11 @@ wheels = [ [[package]] name = "priority" -version = "1.3.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/96/7d0b024087062418dfe02a68cd6b195399266ac002fb517aad94cc93e076/priority-1.3.0.tar.gz", hash = "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe", size = 13827, upload-time = "2017-01-27T11:00:54.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/3c/eb7c35f4dcede96fca1842dac5f4f5d15511aa4b52f3a961219e68ae9204/priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0", size = 24792, upload-time = "2021-06-27T10:15:05.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/96/2f4b8da7be255cd41e825c398efd11a6706ff86e66ae198f012204aa2a4f/priority-1.3.0-py2.py3-none-any.whl", hash = "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb", size = 11720, upload-time = "2017-01-27T11:00:52.07Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/82c8074f7e84978129347c2c6ec8b6c59f3584ff1a20bc3c940a3e061790/priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa", size = 8946, upload-time = "2021-06-27T10:15:03.856Z" }, ] [[package]] @@ -1262,24 +1237,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/8c/7287ef3e34c5045fe1583385cf03a5a5681b7fae62676391444cfa44a4d1/pyqwest-0.5.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:469dfd7baaac6fd6565d396c8d05f19cb5ba279856af38b5f69ff8278e991c49", size = 4630018, upload-time = "2026-04-17T03:55:58.249Z" }, ] -[[package]] -name = "pyright" -version = "1.1.409" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nodeenv" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, -] - -[package.optional-dependencies] -nodejs = [ - { name = "nodejs-wheel-binaries" }, -] - [[package]] name = "pytest" version = "9.0.2" @@ -1525,8 +1482,8 @@ name = "taskgroup" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" } wheels = [ @@ -1609,6 +1566,48 @@ 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 = "ty" +version = "0.0.34" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/69/e24eefe2c35c0fdbdec9b60e162727af669bb76d64d993d982eb67b24c38/ty-0.0.34.tar.gz", hash = "sha256:a6efe66b0f13c03a65e6c72ec9abfe2792e2fd063c74fa67e2c4930e29d661be", size = 5585933, upload-time = "2026-05-01T23:06:46.388Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7b/8b85003d6639ef17a97dcbb31f4511cfe78f1c81a964470db100c8c883e7/ty-0.0.34-py3-none-linux_armv6l.whl", hash = "sha256:9ecc3d14f07a95a6ceb88e07f8e62358dbd37325d3d5bd56da7217ff1fef7fb8", size = 11067094, upload-time = "2026-05-01T23:06:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/d7/25/b0098f65b020b015c40567c763fc66fffbec88b2ba6f584bca1e92f05ebb/ty-0.0.34-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0dccffd8a9d02321cd2dee3249df205e26d62694e741f4eeca36b157fd8b419f", size = 10840909, upload-time = "2026-05-01T23:06:18.409Z" }, + { url = "https://files.pythonhosted.org/packages/e4/55/5e4adcf7d2a1006b844903b27cb81244a9b748d850433a46a6c21776c401/ty-0.0.34-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b0ea47a2998e167ab3b21d2f4b5309a9cf33c297809f6d7e3e753252223174d0", size = 10279378, upload-time = "2026-05-01T23:06:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/f537dca0db8fe2558e8ab04d8941d687b384fcc1df5eb9023b2db75ac26c/ty-0.0.34-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b37da00b41a118a459ae56d8947e70651073fb33ebfbceb820e4a10b22d5023", size = 10817423, upload-time = "2026-05-01T23:06:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c4/55a3ad1da2815af1009bdc1b8c90dc11a364cd314e4b48c5128ba9d38859/ty-0.0.34-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81cbbb93c2342fe3de43e625d3a9eb149633e9f485e816ebf6395d08685355d8", size = 10851826, upload-time = "2026-05-01T23:06:24.198Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/9c7606af22d73fb43ea4369472d9c66ece11231be73b0efe8e3c61655559/ty-0.0.34-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c5b4dea1594a021289e172582df9cde7089dce14b276fc650e7b212b1772e12", size = 11356318, upload-time = "2026-05-01T23:06:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/20/54/bb423f663721ab4138b216425c6b55eaefd3a068243b24d6d8fe988f4e13/ty-0.0.34-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:030fb00aa2d2a5b5ae9d9183d574e0c82dae80566700a7490c43669d8ece40cd", size = 11902968, upload-time = "2026-05-01T23:06:35.82Z" }, + { url = "https://files.pythonhosted.org/packages/b6/22/01122b21ab6b534a2f618c6bbe5f1f7f49fd56f4b2ec8887cd6d40d08fb3/ty-0.0.34-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ae9555e24e36c63a8218e037a5a63f15579eb6aa94f41017e57cd41d335cfb5", size = 11548860, upload-time = "2026-05-01T23:06:42.155Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/86008b1392ec64bed1957bbcc7aaa43b466b50dfc91bb131841c21d7c5c3/ty-0.0.34-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99eb23df9ed129fc26d1ab00d6f0b8dfe5253b09c2ac6abdb11523fa70d67f10", size = 11457097, upload-time = "2026-05-01T23:06:53.477Z" }, + { url = "https://files.pythonhosted.org/packages/92/3e/4558b2296963ba99c58d8409c57d7db4f3061b656c3613cb21c02c1ef4c2/ty-0.0.34-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85de45382016eceae69e104815eb2cfa200787df104002e262a86cbd43ed2c02", size = 10798192, upload-time = "2026-05-01T23:06:40.004Z" }, + { url = "https://files.pythonhosted.org/packages/76/bf/650d24402be2ef678528d60caac1d9477a40fc37e3792ecef07834fd7a4a/ty-0.0.34-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:14cb575fb8fa5131f5129d100cfe23c1575d23faf5dfc5158432749a3e38c9b5", size = 10890390, upload-time = "2026-05-01T23:06:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ef/ccd2ca13906079f7935fd7e067661b24233017f57d987d51d6a121d85bb5/ty-0.0.34-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c6fc0b69d8450e6910ba9db34572b959b81329a97ae273c391f70e9fb6c1aade", size = 11031564, upload-time = "2026-05-01T23:06:55.812Z" }, + { url = "https://files.pythonhosted.org/packages/ba/2d/d27b72005b6f43599e3bcabab0d7135ac0c230b7a307bb99f9eea02c1cda/ty-0.0.34-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:30dfcec2f0fde3993f4f912ed0e057dcbebc8615299f610a4c2ddb7b5a3e1e06", size = 11553430, upload-time = "2026-05-01T23:06:31.096Z" }, + { url = "https://files.pythonhosted.org/packages/a7/12/20812e1ad930b8d4af70eebf19ad23cff6e31efcfa613ef884531fcdbaa1/ty-0.0.34-py3-none-win32.whl", hash = "sha256:97b77ddf007271b812a313a8f0a14929bc5590958433e1fb83ef585676f53342", size = 10436048, upload-time = "2026-05-01T23:06:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/afa095c5987868fbda27c0f731146ac8e3d07b357adfa83daccaee5b1a16/ty-0.0.34-py3-none-win_amd64.whl", hash = "sha256:1f543968accb952705134028d1fda8656882787dbbc667ad4d6c3ba23791d604", size = 11462526, upload-time = "2026-05-01T23:06:28.514Z" }, + { url = "https://files.pythonhosted.org/packages/63/8f/bf041a06260d77662c0605e56dacfe90b786bf824cbe1aed238d15fe5e84/ty-0.0.34-py3-none-win_arm64.whl", hash = "sha256:ea09108cbcb16b6b06d7596312b433bf49681e78d30e4dc7fb3c1b248a95e09a", size = 10846945, upload-time = "2026-05-01T23:06:44.428Z" }, +] + +[[package]] +name = "types-grpcio" +version = "1.0.0.20260408" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/5d/e8af15d909e1f7bf46bac6f21e3faa0f16a92b4d5bd685d52e2c25ee26ca/types_grpcio-1.0.0.20260408.tar.gz", hash = "sha256:c8ebe07f91492a32e0f3a3810669d236828d5a2a1e540ab16cca8b5a46f8ee5d", size = 14551, upload-time = "2026-04-08T04:27:51.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/b2/965d99a23d94e4697a76c88c17afcaf8013332583087551697a391b972bd/types_grpcio-1.0.0.20260408-py3-none-any.whl", hash = "sha256:256f2738a4a3eb2ebabd6d027f4fc7eace024822afb3b629e82811e0b661fc35", size = 15209, upload-time = "2026-04-08T04:27:50.02Z" }, +] + +[[package]] +name = "types-protobuf" +version = "7.34.1.20260503" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/31/87969cb3e62287bde7598b78b3c098d2873d54f5fb5a7cfbcaa73b8c965e/types_protobuf-7.34.1.20260503.tar.gz", hash = "sha256:effbc819aa17e02448dde99f089c6794662d66f4b2797e922f185ffe0b24e766", size = 68830, upload-time = "2026-05-03T05:19:50.739Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/67/a33fb18090a927794a5ee4b1a30730b528ace0dad6b18932540d21258184/types_protobuf-7.34.1.20260503-py3-none-any.whl", hash = "sha256:75fd66121d56785c91828b8bf7b511f39ba847f11e682573e41847f01e9cd1de", size = 86019, upload-time = "2026-05-03T05:19:49.486Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"