diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f1df769..947d343 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -54,3 +54,5 @@ jobs: git diff --exit-code - name: Formatting check run: uv run --frozen ruff format --check + - name: Typing check + run: uv run --frozen ty check diff --git a/packages/bot/src/automa/bot/_base_client.py b/packages/bot/src/automa/bot/_base_client.py index e3035aa..404e7fa 100644 --- a/packages/bot/src/automa/bot/_base_client.py +++ b/packages/bot/src/automa/bot/_base_client.py @@ -10,11 +10,13 @@ Iterator, TypeVar, Union, + cast, ) from httpx import URL, AsyncClient, Client, Response +from httpx._types import HeaderTypes -from ._types import Headers, RequestOptions +from ._types import RequestOptions _T = TypeVar("_T") @@ -48,7 +50,7 @@ def base_url(self, url: str | URL) -> None: ) @property - def default_headers(self) -> Headers: + def default_headers(self) -> HeaderTypes: return { "Accept": "application/json", "Content-Type": "application/json", @@ -109,7 +111,7 @@ def get( self, path: str, *, - options: RequestOptions = {}, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: return self.method("get", path, options=options) @@ -118,7 +120,7 @@ def post( path: str, *, body: Any | None = None, - options: RequestOptions = {}, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: return self.method( "post", @@ -130,7 +132,11 @@ def post( ) def method( - self, method: str, path: str, *, options: RequestOptions = {} + self, + method: str, + path: str, + *, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: options["headers"] = {**self.default_headers, **options.get("headers", {})} @@ -144,7 +150,11 @@ def method( @contextmanager def stream( - self, method: str, path: str, *, options: RequestOptions = {} + self, + method: str, + path: str, + *, + options: RequestOptions = cast(RequestOptions, {}), ) -> Iterator[Response]: options["headers"] = {**self.default_headers, **options.get("headers", {})} @@ -208,7 +218,7 @@ async def get( self, path: str, *, - options: RequestOptions = {}, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: return await self.method("get", path, options=options) @@ -217,7 +227,7 @@ async def post( path: str, *, body: Any | None = None, - options: RequestOptions = {}, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: return await self.method( "post", @@ -233,7 +243,7 @@ async def method( method: str, path: str, *, - options: RequestOptions = {}, + options: RequestOptions = cast(RequestOptions, {}), ) -> Response: options["headers"] = {**self.default_headers, **options.get("headers", {})} @@ -247,7 +257,11 @@ async def method( @asynccontextmanager async def stream( - self, method: str, path: str, *, options: RequestOptions = {} + self, + method: str, + path: str, + *, + options: RequestOptions = cast(RequestOptions, {}), ) -> AsyncIterator[Response]: options["headers"] = {**self.default_headers, **options.get("headers", {})} diff --git a/packages/bot/src/automa/bot/_client.py b/packages/bot/src/automa/bot/_client.py index bfce4e2..0744b6d 100644 --- a/packages/bot/src/automa/bot/_client.py +++ b/packages/bot/src/automa/bot/_client.py @@ -3,11 +3,11 @@ import os from httpx import URL +from httpx._types import HeaderTypes from typing_extensions import override from ._base_client import ( AsyncAPIClient, - Headers, SyncAPIClient, ) from .resources import code @@ -19,7 +19,7 @@ class Automa(SyncAPIClient): - _default_headers: Headers + _default_headers: HeaderTypes code: code.CodeResource @@ -27,7 +27,7 @@ def __init__( self, *, base_url: str | URL | None = None, - default_headers: Headers | None = None, + default_headers: HeaderTypes | None = None, ) -> None: """Construct a new synchronous Automa client instance.""" if base_url is None: @@ -53,7 +53,7 @@ def default_headers(self) -> dict[str, str]: class AsyncAutoma(AsyncAPIClient): - _default_headers: Headers + _default_headers: HeaderTypes code: code.AsyncCodeResource @@ -61,7 +61,7 @@ def __init__( self, *, base_url: str | URL | None = None, - default_headers: Headers | None = None, + default_headers: HeaderTypes | None = None, ) -> None: """Construct a new async AsyncAutoma client instance.""" if base_url is None: @@ -79,7 +79,7 @@ def __init__( @property @override - def default_headers(self) -> Headers: + def default_headers(self) -> HeaderTypes: return { **super().default_headers, **self._default_headers, diff --git a/packages/bot/src/automa/bot/_types.py b/packages/bot/src/automa/bot/_types.py index e471163..b75b3a1 100644 --- a/packages/bot/src/automa/bot/_types.py +++ b/packages/bot/src/automa/bot/_types.py @@ -1,9 +1,9 @@ from __future__ import annotations from enum import Enum -from typing import Any, Dict, Literal, Mapping, NotRequired, TypedDict, Union +from typing import Any, Dict, Literal, NotRequired, TypedDict, Union -from httpx._types import QueryParamTypes, RequestExtensions +from httpx._types import HeaderTypes, QueryParamTypes, RequestExtensions class Omit: @@ -27,15 +27,11 @@ def __bool__(self) -> Literal[False]: return False -Headers = Mapping[str, Union[str, Omit]] - - class RequestOptions(TypedDict, total=False): json: Any | None - headers: Headers | None + headers: HeaderTypes | None params: QueryParamTypes | None extensions: RequestExtensions | None - stream: bool | None class ProposalTaskItem(TypedDict): diff --git a/packages/bot/src/automa/bot/resources/code.py b/packages/bot/src/automa/bot/resources/code.py index 827bc5f..5287e91 100644 --- a/packages/bot/src/automa/bot/resources/code.py +++ b/packages/bot/src/automa/bot/resources/code.py @@ -8,7 +8,7 @@ from os.path import join from pathlib import Path from shutil import rmtree -from typing import NotRequired, TypedDict +from typing import NotRequired, TypedDict, cast from .._resource import AsyncAPIResource, SyncAPIResource from .._types import RequestOptions @@ -146,7 +146,10 @@ def cleanup(self, body: CodeCleanupParams) -> None: pass def download( - self, body: CodeDownloadParams, *, options: RequestOptions = {} + self, + body: CodeDownloadParams, + *, + options: RequestOptions = cast(RequestOptions, {}), ) -> CodeFolder: token = None path = self._path(body["task"]) @@ -191,7 +194,12 @@ def download( return CodeFolder(path) - def propose(self, body: CodeProposeParams, *, options: RequestOptions = {}): + def propose( + self, + body: CodeProposeParams, + *, + options: RequestOptions = cast(RequestOptions, {}), + ): path = self._path(body["task"]) token = self._read_token(path) base_commit = self._read_base_commit(path) @@ -228,7 +236,10 @@ async def cleanup(self, body: CodeCleanupParams) -> None: pass async def download( - self, body: CodeDownloadParams, *, options: RequestOptions = {} + self, + body: CodeDownloadParams, + *, + options: RequestOptions = cast(RequestOptions, {}), ) -> CodeFolder: token = None path = self._path(body["task"]) @@ -276,7 +287,12 @@ async def download( return CodeFolder(path) - async def propose(self, body: CodeProposeParams, *, options: RequestOptions = {}): + async def propose( + self, + body: CodeProposeParams, + *, + options: RequestOptions = cast(RequestOptions, {}), + ): path = self._path(body["task"]) token = await to_thread(self._read_token, path) base_commit = await to_thread(self._read_base_commit, path) diff --git a/packages/bot/tests/test_webhook.py b/packages/bot/tests/test_webhook.py index de3f618..33d9a61 100644 --- a/packages/bot/tests/test_webhook.py +++ b/packages/bot/tests/test_webhook.py @@ -2,7 +2,7 @@ def test_returns_false_if_secret_is_not_a_string(): - result = verify_webhook(1, "signature", "{}") + result = verify_webhook(1, "signature", "{}") # ty:ignore[invalid-argument-type] assert result is False @@ -14,7 +14,7 @@ def test_returns_false_if_secret_is_empty(): def test_returns_false_if_signature_is_not_a_string(): - result = verify_webhook("secret", 1, "{}") + result = verify_webhook("secret", 1, "{}") # ty:ignore[invalid-argument-type] assert result is False diff --git a/pyproject.toml b/pyproject.toml index 83bc971..6b305b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ members = ["packages/bot"] [dependency-groups] -dev = ["pytest-asyncio~=0.26.0", "pytest-cov~=6.0.0", "pytest~=8.3.5", "ruff~=0.9"] +dev = ["pytest-asyncio~=0.26.0", "pytest-cov~=6.0.0", "pytest~=8.3.5", "ruff~=0.9", "ty~=0.0.8"] [tool.pytest.ini_options] asyncio_default_fixture_loop_scope = "function" diff --git a/uv.lock b/uv.lock index caf831a..42302ce 100644 --- a/uv.lock +++ b/uv.lock @@ -13,6 +13,7 @@ dev = [ { name = "pytest-asyncio", specifier = "~=0.26.0" }, { name = "pytest-cov", specifier = "~=6.0.0" }, { name = "ruff", specifier = "~=0.9" }, + { name = "ty", specifier = "~=0.0.8" }, ] [[package]] @@ -305,6 +306,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "ty" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/9d/59e955cc39206a0d58df5374808785c45ec2a8a2a230eb1638fbb4fe5c5d/ty-0.0.8.tar.gz", hash = "sha256:352ac93d6e0050763be57ad1e02087f454a842887e618ec14ac2103feac48676", size = 4828477, upload-time = "2025-12-29T13:50:07.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/2b/dd61f7e50a69c72f72c625d026e9ab64a0db62b2dd32e7426b520e2429c6/ty-0.0.8-py3-none-linux_armv6l.whl", hash = "sha256:a289d033c5576fa3b4a582b37d63395edf971cdbf70d2d2e6b8c95638d1a4fcd", size = 9853417, upload-time = "2025-12-29T13:50:08.979Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/3f1d3c64a049a388e199de4493689a51fc6aa5ff9884c03dea52b4966657/ty-0.0.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:788ea97dc8153a94e476c4d57b2551a9458f79c187c4aba48fcb81f05372924a", size = 9657890, upload-time = "2025-12-29T13:50:27.867Z" }, + { url = "https://files.pythonhosted.org/packages/71/d1/08ac676bd536de3c2baba0deb60e67b3196683a2fabebfd35659d794b5e9/ty-0.0.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1b5f1f3d3e230f35a29e520be7c3d90194a5229f755b721e9092879c00842d31", size = 9180129, upload-time = "2025-12-29T13:50:22.842Z" }, + { url = "https://files.pythonhosted.org/packages/af/93/610000e2cfeea1875900f73a375ba917624b0a008d4b8a6c18c894c8dbbc/ty-0.0.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6da9ed377fbbcec0a3b60b2ca5fd30496e15068f47cef2344ba87923e78ba996", size = 9683517, upload-time = "2025-12-29T13:50:18.658Z" }, + { url = "https://files.pythonhosted.org/packages/05/04/bef50ba7d8580b0140be597de5cc0ba9a63abe50d3f65560235f23658762/ty-0.0.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7d0a2bdce5e701d19eb8d46d9da0fe31340f079cecb7c438f5ac6897c73fc5ba", size = 9676279, upload-time = "2025-12-29T13:50:25.207Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b9/2aff1ef1f41b25898bc963173ae67fc8f04ca666ac9439a9c4e78d5cc0ff/ty-0.0.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef9078799d26d3cc65366e02392e2b78f64f72911b599e80a8497d2ec3117ddb", size = 10073015, upload-time = "2025-12-29T13:50:35.422Z" }, + { url = "https://files.pythonhosted.org/packages/df/0e/9feb6794b6ff0a157c3e6a8eb6365cbfa3adb9c0f7976e2abdc48615dd72/ty-0.0.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:54814ac39b4ab67cf111fc0a236818155cf49828976152378347a7678d30ee89", size = 10961649, upload-time = "2025-12-29T13:49:58.717Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3b/faf7328b14f00408f4f65c9d01efe52e11b9bcc4a79e06187b370457b004/ty-0.0.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4baf0a80398e8b6c68fa36ff85045a50ede1906cd4edb41fb4fab46d471f1d4", size = 10676190, upload-time = "2025-12-29T13:50:01.11Z" }, + { url = "https://files.pythonhosted.org/packages/64/a5/cfeca780de7eeab7852c911c06a84615a174d23e9ae08aae42a645771094/ty-0.0.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac8e23c3faefc579686799ef1649af8d158653169ad5c3a7df56b152781eeb67", size = 10438641, upload-time = "2025-12-29T13:50:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8d/8667c7e0ac9f13c461ded487c8d7350f440cd39ba866d0160a8e1b1efd6c/ty-0.0.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b558a647a073d0c25540aaa10f8947de826cb8757d034dd61ecf50ab8dbd77bf", size = 10214082, upload-time = "2025-12-29T13:50:31.531Z" }, + { url = "https://files.pythonhosted.org/packages/f8/11/e563229870e2c1d089e7e715c6c3b7605a34436dddf6f58e9205823020c2/ty-0.0.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8c0104327bf480508bd81f320e22074477df159d9eff85207df39e9c62ad5e96", size = 9664364, upload-time = "2025-12-29T13:50:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/05b79b778bf5237bcd7ee08763b226130aa8da872cbb151c8cfa2e886203/ty-0.0.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:496f1cb87261dd1a036a5609da80ee13de2e6ee4718a661bfa2afb91352fe528", size = 9679440, upload-time = "2025-12-29T13:50:11.289Z" }, + { url = "https://files.pythonhosted.org/packages/12/b5/23ba887769c4a7b8abfd1b6395947dc3dcc87533fbf86379d3a57f87ae8f/ty-0.0.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2c488031f92a075ae39d13ac6295fdce2141164ec38c5d47aa8dc24ee3afa37e", size = 9808201, upload-time = "2025-12-29T13:50:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/f8/90/5a82ac0a0707db55376922aed80cd5fca6b2e6d6e9bcd8c286e6b43b4084/ty-0.0.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90d6f08c5982fa3e802b8918a32e326153519077b827f91c66eea4913a86756a", size = 10313262, upload-time = "2025-12-29T13:50:03.306Z" }, + { url = "https://files.pythonhosted.org/packages/14/f7/ff97f37f0a75db9495ddbc47738ec4339837867c4bfa145bdcfbd0d1eb2f/ty-0.0.8-py3-none-win32.whl", hash = "sha256:d7f460ad6fc9325e9cc8ea898949bbd88141b4609d1088d7ede02ce2ef06e776", size = 9254675, upload-time = "2025-12-29T13:50:33.35Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/eba5d83015e04630002209e3590c310a0ff1d26e1815af204a322617a42e/ty-0.0.8-py3-none-win_amd64.whl", hash = "sha256:1641fb8dedc3d2da43279d21c3c7c1f80d84eae5c264a1e8daa544458e433c19", size = 10131382, upload-time = "2025-12-29T13:50:13.719Z" }, + { url = "https://files.pythonhosted.org/packages/38/1c/0d8454ff0f0f258737ecfe84f6e508729191d29663b404832f98fa5626b7/ty-0.0.8-py3-none-win_arm64.whl", hash = "sha256:ec74f022f315bede478ecae1277a01ab618e6500c1d68450d7883f5cd6ed554a", size = 9636374, upload-time = "2025-12-29T13:50:16.344Z" }, +] + [[package]] name = "typing-extensions" version = "4.13.0"