diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1214005ea..96166038a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: - name: Run lints run: ./scripts/lint + - name: Run type checking + run: uv run ty check + build: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork timeout-minutes: 10 diff --git a/pyproject.toml b/pyproject.toml index 84012faed..e216cf03f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ dev = [ "pytest-xdist>=3.6.1", "uuid-utils>=0.11.0", "pytest-cov>=7.0.0", + "ty>=0.0.1a32", ] docs = [ "furo>=2025.9.25", @@ -285,3 +286,23 @@ known-first-party = ["runloop_api_client", "tests"] "scripts/**.py" = ["T201", "T203"] "tests/**.py" = ["T201", "T203"] "examples/**.py" = ["T201", "T203"] + +[tool.ty.src] +# External libraries with known stub issues +exclude = [ + "tests", + "src/runloop_api_client/resources", + "src/runloop_api_client/_utils", +] + +[tool.ty.rules] +invalid-assignment = "ignore" +call-non-callable = "ignore" +invalid-argument-type = "ignore" +unresolved-attribute = "ignore" +not-iterable = "ignore" +non-subscriptable = "ignore" +invalid-return-type = "ignore" +unresolved-import = "ignore" +possibly-missing-attribute = "ignore" +invalid-parameter-default = "ignore" diff --git a/src/runloop_api_client/_base_client.py b/src/runloop_api_client/_base_client.py index f639d4201..9086e0a98 100644 --- a/src/runloop_api_client/_base_client.py +++ b/src/runloop_api_client/_base_client.py @@ -303,10 +303,7 @@ def _parser(resp: AsyncPageT) -> AsyncPageT: async def __aiter__(self) -> AsyncIterator[_T]: # https://github.com/microsoft/pyright/issues/3464 - page = cast( - AsyncPageT, - await self, # type: ignore - ) + page = await self async for item in page: yield item @@ -590,7 +587,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques return cast_to # make a copy of the headers so we don't mutate user-input - headers = dict(options.headers) + headers = dict(options.headers) # type: ignore[no-matching-overload] # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` diff --git a/src/runloop_api_client/_compat.py b/src/runloop_api_client/_compat.py index bdef67f04..59128c69a 100644 --- a/src/runloop_api_client/_compat.py +++ b/src/runloop_api_client/_compat.py @@ -149,14 +149,11 @@ def model_dump( # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, ) - return cast( - "dict[str, Any]", - model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + return model.dict( exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, - ), - ) + ) def model_parse(model: type[_ModelT], data: Any) -> _ModelT: diff --git a/src/runloop_api_client/_models.py b/src/runloop_api_client/_models.py index 3fcf41040..7ad519e79 100644 --- a/src/runloop_api_client/_models.py +++ b/src/runloop_api_client/_models.py @@ -85,7 +85,6 @@ class BaseModel(pydantic.BaseModel): if PYDANTIC_V1: @property - @override def model_fields_set(self) -> set[str]: # a forwards-compat shim for pydantic v2 return self.__fields_set__ # type: ignore @@ -250,7 +249,6 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # a specific pydantic version as some users may not know which # pydantic version they are currently using - @override def model_dump( self, *, @@ -321,7 +319,6 @@ def model_dump( return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped - @override def model_dump_json( self, *, @@ -499,7 +496,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any] if is_union(origin): try: - return validate_type(type_=cast("type[object]", original_type or type_), value=value) + return validate_type(type_=original_type or type_, value=value) except Exception: pass diff --git a/src/runloop_api_client/_response.py b/src/runloop_api_client/_response.py index 31591fad8..98ca92bdb 100644 --- a/src/runloop_api_client/_response.py +++ b/src/runloop_api_client/_response.py @@ -679,7 +679,7 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[As make_request = func(*args, **kwargs) - return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + return AsyncResponseContextManager(make_request) return wrapped @@ -729,7 +729,7 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_A make_request = func(*args, **kwargs) - return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + return AsyncResponseContextManager(make_request) return wrapped @@ -809,7 +809,7 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: kwargs["extra_headers"] = extra_headers - return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + return func(*args, **kwargs) return wrapped diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py index 38dfda8f9..5f6c28989 100644 --- a/src/runloop_api_client/sdk/async_.py +++ b/src/runloop_api_client/sdk/async_.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio -from typing import Dict, Mapping, Optional +from typing import Dict, List, Mapping, Optional from pathlib import Path from datetime import timedelta from typing_extensions import Unpack @@ -150,7 +150,7 @@ def from_id(self, devbox_id: str) -> AsyncDevbox: async def list( self, **params: Unpack[SDKDevboxListParams], - ) -> list[AsyncDevbox]: + ) -> List[AsyncDevbox]: """List devboxes accessible to the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxListParams` for available parameters @@ -186,7 +186,7 @@ def __init__(self, client: AsyncRunloop) -> None: async def list( self, **params: Unpack[SDKDiskSnapshotListParams], - ) -> list[AsyncSnapshot]: + ) -> List[AsyncSnapshot]: """List snapshots created from devboxes. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDiskSnapshotListParams` for available parameters @@ -277,7 +277,7 @@ def from_id(self, blueprint_id: str) -> AsyncBlueprint: async def list( self, **params: Unpack[SDKBlueprintListParams], - ) -> list[AsyncBlueprint]: + ) -> List[AsyncBlueprint]: """List available blueprints. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBlueprintListParams` for available parameters @@ -338,7 +338,7 @@ def from_id(self, object_id: str) -> AsyncStorageObject: async def list( self, **params: Unpack[SDKObjectListParams], - ) -> list[AsyncStorageObject]: + ) -> List[AsyncStorageObject]: """List storage objects owned by the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKObjectListParams` for available parameters @@ -541,12 +541,12 @@ def from_id(self, scorer_id: str) -> AsyncScorer: """ return AsyncScorer(self._client, scorer_id) - async def list(self, **params: Unpack[SDKScorerListParams]) -> list[AsyncScorer]: + async def list(self, **params: Unpack[SDKScorerListParams]) -> List[AsyncScorer]: """List all scorers, optionally filtered by parameters. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerListParams` for available parameters :return: List of scorers - :rtype: list[AsyncScorer] + :rtype: List[AsyncScorer] """ page = await self._client.scenarios.scorers.list(**params) return [AsyncScorer(self._client, item.id) async for item in page] @@ -599,7 +599,7 @@ async def create_from_npm( package_name: str, npm_version: Optional[str] = None, registry_url: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> AsyncAgent: """Create an agent from an NPM package. @@ -641,7 +641,7 @@ async def create_from_pip( package_name: str, pip_version: Optional[str] = None, registry_url: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> AsyncAgent: """Create an agent from a Pip package. @@ -682,7 +682,7 @@ async def create_from_git( *, repository: str, ref: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> AsyncAgent: """Create an agent from a Git repository. @@ -718,7 +718,7 @@ async def create_from_object( self, *, object_id: str, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> AsyncAgent: """Create an agent from a storage object. @@ -759,7 +759,7 @@ def from_id(self, agent_id: str) -> AsyncAgent: async def list( self, **params: Unpack[SDKAgentListParams], - ) -> list[AsyncAgent]: + ) -> List[AsyncAgent]: """List agents accessible to the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListParams` for available parameters @@ -800,7 +800,7 @@ def from_id(self, scenario_id: str) -> AsyncScenario: """ return AsyncScenario(self._client, scenario_id) - async def list(self, **params: Unpack[SDKScenarioListParams]) -> list[AsyncScenario]: + async def list(self, **params: Unpack[SDKScenarioListParams]) -> List[AsyncScenario]: """List all scenarios, optionally filtered by parameters. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py index 6d8bdf2c7..1c2fcb52c 100644 --- a/src/runloop_api_client/sdk/sync.py +++ b/src/runloop_api_client/sdk/sync.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Mapping, Optional +from typing import Dict, List, Mapping, Optional from pathlib import Path from datetime import timedelta from typing_extensions import Unpack @@ -149,7 +149,7 @@ def from_id(self, devbox_id: str) -> Devbox: def list( self, **params: Unpack[SDKDevboxListParams], - ) -> list[Devbox]: + ) -> List[Devbox]: """List devboxes accessible to the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDevboxListParams` for available parameters @@ -185,7 +185,7 @@ def __init__(self, client: Runloop) -> None: def list( self, **params: Unpack[SDKDiskSnapshotListParams], - ) -> list[Snapshot]: + ) -> List[Snapshot]: """List snapshots created from devboxes. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKDiskSnapshotListParams` for available parameters @@ -275,7 +275,7 @@ def from_id(self, blueprint_id: str) -> Blueprint: def list( self, **params: Unpack[SDKBlueprintListParams], - ) -> list[Blueprint]: + ) -> List[Blueprint]: """List available blueprints. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKBlueprintListParams` for available parameters @@ -336,7 +336,7 @@ def from_id(self, object_id: str) -> StorageObject: def list( self, **params: Unpack[SDKObjectListParams], - ) -> list[StorageObject]: + ) -> List[StorageObject]: """List storage objects owned by the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKObjectListParams` for available parameters @@ -536,7 +536,7 @@ def from_id(self, scorer_id: str) -> Scorer: """ return Scorer(self._client, scorer_id) - def list(self, **params: Unpack[SDKScorerListParams]) -> list[Scorer]: + def list(self, **params: Unpack[SDKScorerListParams]) -> List[Scorer]: """List all scorers, optionally filtered by parameters. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerListParams` for available parameters @@ -594,7 +594,7 @@ def create_from_npm( package_name: str, npm_version: Optional[str] = None, registry_url: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> Agent: """Create an agent from an NPM package. @@ -641,7 +641,7 @@ def create_from_pip( package_name: str, pip_version: Optional[str] = None, registry_url: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> Agent: """Create an agent from a Pip package. @@ -687,7 +687,7 @@ def create_from_git( *, repository: str, ref: Optional[str] = None, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> Agent: """Create an agent from a Git repository. @@ -731,7 +731,7 @@ def create_from_object( self, *, object_id: str, - agent_setup: Optional[list[str]] = None, + agent_setup: Optional[List[str]] = None, **params: Unpack[SDKAgentCreateParams], ) -> Agent: """Create an agent from a storage object. @@ -780,7 +780,7 @@ def from_id(self, agent_id: str) -> Agent: def list( self, **params: Unpack[SDKAgentListParams], - ) -> list[Agent]: + ) -> List[Agent]: """List agents accessible to the caller. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKAgentListParams` for available parameters @@ -821,7 +821,7 @@ def from_id(self, scenario_id: str) -> Scenario: """ return Scenario(self._client, scenario_id) - def list(self, **params: Unpack[SDKScenarioListParams]) -> list[Scenario]: + def list(self, **params: Unpack[SDKScenarioListParams]) -> List[Scenario]: """List all scenarios, optionally filtered by parameters. :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters diff --git a/uv.lock b/uv.lock index ff0202dfd..a34b23dfe 100644 --- a/uv.lock +++ b/uv.lock @@ -2181,7 +2181,7 @@ wheels = [ [[package]] name = "runloop-api-client" -version = "1.0.0" +version = "1.1.0" source = { editable = "." } dependencies = [ { name = "anyio" }, @@ -2217,6 +2217,7 @@ dev = [ { name = "ruff" }, { name = "time-machine", version = "2.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "time-machine", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ty" }, { name = "uuid-utils" }, ] docs = [ @@ -2262,6 +2263,7 @@ dev = [ { name = "rich", specifier = ">=13.7.1" }, { name = "ruff" }, { name = "time-machine" }, + { name = "ty", specifier = ">=0.0.1a32" }, { name = "uuid-utils", specifier = ">=0.11.0" }, ] docs = [ @@ -2902,6 +2904,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] +[[package]] +name = "ty" +version = "0.0.1a32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/92/8da015685fb83734a2a83de02080e64d182509de77fa9bcf3eed12eeab4b/ty-0.0.1a32.tar.gz", hash = "sha256:12f62e8a3dd0eaeb9557d74b1c32f0616ae40eae10a4f411e1e2a73225f67ff2", size = 4689151, upload-time = "2025-12-05T21:04:26.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e6/fdc35c9ba047f16afdfedf36fb51c221e0190ccde9f70ee28e77084d6612/ty-0.0.1a32-py3-none-linux_armv6l.whl", hash = "sha256:ffe595eaf616f06f58f951766477830a55c2502d2c9f77dde8f60d9a836e0645", size = 9673128, upload-time = "2025-12-05T21:04:17.702Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/eaff31048e2f309f37478f7d715c8de9f9bab03cba4758da27b9311147af/ty-0.0.1a32-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:07f1dce88ad6028fb14665aefe4e6697012c34bd48edd37d02b7eb6a833dbf62", size = 9434094, upload-time = "2025-12-05T21:04:03.383Z" }, + { url = "https://files.pythonhosted.org/packages/67/d4/ea8ed57d11b81c459f23561fd6bfb0f54a8d4120cf72541e3bdf71d46202/ty-0.0.1a32-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8fab7ed12528c77ddd600a9638ca859156a53c20f1e381353fa87a255bd397eb", size = 8980296, upload-time = "2025-12-05T21:04:28.912Z" }, + { url = "https://files.pythonhosted.org/packages/49/02/3ce98bbfbb3916678d717ee69358d38a404ca9a39391dda8874b66dd5ee7/ty-0.0.1a32-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace395280fc21e25eff0a53cfbd68170f90a4b8ef2f85dfabe1ecbca2ced456b", size = 9263054, upload-time = "2025-12-05T21:04:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/b7/be/a639638bcd1664de2d70a87da6c4fe0e3272a60b7fa3f0c108a956a456bd/ty-0.0.1a32-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bcbeed7f5ed8e3c1c7e525fce541e7b943ac04ee7fe369a926551b5e50ea4a8", size = 9451396, upload-time = "2025-12-05T21:04:01.265Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a4/2bcf54e842a3d10dc14b369f28a3bab530c5d7ddba624e910b212bda93ee/ty-0.0.1a32-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60ff2e4493f90f81a260205d87719bb1d3420928a1e4a2a7454af7cbdfed2047", size = 9862726, upload-time = "2025-12-05T21:04:08.806Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c7/19e6719496e59f2f082f34bcac312698366cf50879fdcc3ef76298bfe6a0/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:53cad50a59a0d943b06872e0b10f9f2b564805c2ea93f64c7798852bc1901954", size = 10475051, upload-time = "2025-12-05T21:04:31.059Z" }, + { url = "https://files.pythonhosted.org/packages/88/77/bdf0ddb066d2b62f141d058f8a33bb7c8628cdbb8bfa75b20e296b79fb4e/ty-0.0.1a32-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:343d43cdc1d7f649ea2baa64ac2b479da3d679239b94509f1df12f7211561ea9", size = 10232712, upload-time = "2025-12-05T21:04:19.849Z" }, + { url = "https://files.pythonhosted.org/packages/ed/07/f73260a461762a581a007015c1019d40658828ce41576f8c1db88dee574d/ty-0.0.1a32-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f45483e4a84bcf622413712164ea687ce323a9f7013b9e7977c5d623ed937ca9", size = 10237705, upload-time = "2025-12-05T21:04:35.366Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/dbb92206cf2f798d8c51ea16504e8afb90a139d0ff105c31cec9a1db29f9/ty-0.0.1a32-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d452f30d47002a6bafc36d1b6aee42c321e9ec9f7f43a04a2ee7d48c208b86c", size = 9766469, upload-time = "2025-12-05T21:04:22.236Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5e/143d93bd143abcebcbaa98c8aeec78898553d62d0a5a432cd79e0cf5bd6d/ty-0.0.1a32-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:86c4e31737fe954637890cef1f3e1b479ffb20e836cac3b76050bdbe80005010", size = 9238592, upload-time = "2025-12-05T21:04:11.33Z" }, + { url = "https://files.pythonhosted.org/packages/21/b8/225230ae097ed88f3c92ad974dd77f8e4f86f2594d9cd0c729da39769878/ty-0.0.1a32-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:daf15fa03bc39a76a0fbc9c2d81d79d528f584e3fbe08d71981e3f7912db91d6", size = 9502161, upload-time = "2025-12-05T21:04:37.642Z" }, + { url = "https://files.pythonhosted.org/packages/85/13/cc89955c9637f25f3aca2dd7749c6008639ef036f0b9bea3e9d89e892ff9/ty-0.0.1a32-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6128f6bab5c6dab3d08689fed1d529dc34f50f221f89c8e16064ed0c549dad7a", size = 9603058, upload-time = "2025-12-05T21:04:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/46/77/1fe2793c8065a02d1f70ca7da1b87db49ca621bcbbdb79a18ad79d5d0ab2/ty-0.0.1a32-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:55aab688be1b46776a5a458a1993cae0da7725932c45393399c479c2fa979337", size = 9879903, upload-time = "2025-12-05T21:04:13.567Z" }, + { url = "https://files.pythonhosted.org/packages/fc/47/fd58e80a3e42310b4b649340d5d97403fe796146cae8678b3a031a414b8e/ty-0.0.1a32-py3-none-win32.whl", hash = "sha256:f55ec25088a09236ad1578b656a07fa009c3a353f5923486905ba48175d142a6", size = 9077703, upload-time = "2025-12-05T21:04:15.849Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/209c417c69317339ea8e9b3277fd98364a0e97dd1ffd3585e143ec7b4e57/ty-0.0.1a32-py3-none-win_amd64.whl", hash = "sha256:ed8d5cbd4e47dfed86aaa27e243008aa4e82b6a5434f3ab95c26d3ee5874d9d7", size = 9922426, upload-time = "2025-12-05T21:04:33.289Z" }, + { url = "https://files.pythonhosted.org/packages/e0/1c/350fd851fb91244f8c80cec218009cbee7564d76c14e2f423b47e69a5cbc/ty-0.0.1a32-py3-none-win_arm64.whl", hash = "sha256:dbb25f9b513d34cee8ce419514eaef03313f45c3f7ab4eb6e6d427ea1f6854af", size = 9453761, upload-time = "2025-12-05T21:04:24.502Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"