From 1448533d434fb51178a22124a0fbc067e277cf9c Mon Sep 17 00:00:00 2001 From: Elias Posen Date: Thu, 7 May 2026 17:52:30 -0400 Subject: [PATCH 1/2] add ty and fix type errrors --- pctx-py/Makefile | 26 ++++++++++++++----- pctx-py/pyproject.toml | 1 + pctx-py/src/pctx_client/_client.py | 16 ++++++------ pctx-py/src/pctx_client/_tool.py | 6 ++--- pctx-py/src/pctx_client/_websocket_client.py | 4 +-- .../src/pctx_client/descriptions/__init__.py | 7 ++--- pctx-py/src/pctx_client/models.py | 2 +- pctx-py/uv.lock | 26 +++++++++++++++++++ 8 files changed, 65 insertions(+), 23 deletions(-) diff --git a/pctx-py/Makefile b/pctx-py/Makefile index d355122f..6b039987 100644 --- a/pctx-py/Makefile +++ b/pctx-py/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install test test-integration format build docs +.PHONY: help install test test-integration fmt lint typecheck check build docs # Default target - show help when running just 'make' .DEFAULT_GOAL := help @@ -7,17 +7,20 @@ help: @echo "pctx-py dev scripts" @echo "" @echo "Available targets:" - @echo " make install - Install dev dependencies and all extras with uv" + @echo " make install - Install all dependency groups and all extras with uv" @echo " make test - Run Python client tests" @echo " make test-integration - Run Python client tests with integration testing" - @echo " make format - Format and lint Python code with ruff" + @echo " make fmt - Format and auto-fix lints with ruff" + @echo " make lint - Lint Python code with ruff" + @echo " make typecheck - Type-check Python code with ty" + @echo " make check - Run lint, typecheck, and tests" @echo " make build - Build Python package (resolves symlinks before build)" @echo " make docs - Build Python Sphinx documentation" @echo "" -# Install with dev deps and all extras +# Install with all dependency groups (dev, docs) and all extras install: - @uv sync --all-extras + @uv sync --all-extras --all-groups # Run Python client tests test: @@ -27,9 +30,20 @@ test: test-integration: @uv run pytest tests/ --integration -v -format: +fmt: @uv run ruff format . && uv run ruff check . --fix +# Lint with ruff (no auto-fix) +lint: + @uv run ruff check . + +# Type-check with ty +typecheck: + @uv run ty check src + +# Run lint, typecheck, and tests +check: lint typecheck test + # Build Python package (resolves _tool_descriptions/data symlink before build, restores after) build: @../scripts/build-python.sh diff --git a/pctx-py/pyproject.toml b/pctx-py/pyproject.toml index 489d7836..11de50b2 100644 --- a/pctx-py/pyproject.toml +++ b/pctx-py/pyproject.toml @@ -50,6 +50,7 @@ dev = [ "litellm>=1.80.8", "langchain-openai>=0.3.0", "mcp>=1.25.0", + "ty>=0.0.34", ] [tool.ruff.lint.pydocstyle] diff --git a/pctx-py/src/pctx_client/_client.py b/pctx-py/src/pctx_client/_client.py index 7b3f8b5a..92c61670 100644 --- a/pctx-py/src/pctx_client/_client.py +++ b/pctx-py/src/pctx_client/_client.py @@ -41,13 +41,13 @@ from crewai.tools import BaseTool as CrewAiBaseTool from langchain_core.tools import BaseTool as LangchainBaseTool from pydantic_ai.tools import Tool as PydanticAITool - from Stemmer import Stemmer + from Stemmer import Stemmer # ty: ignore[unresolved-import] except ImportError: pass try: from bm25s import BM25, tokenize - from Stemmer import Stemmer + from Stemmer import Stemmer # ty: ignore[unresolved-import] except ImportError: pass @@ -864,7 +864,7 @@ def claude_agent_sdk_tools( "Claude Agent SDK is not installed. Install it with: pip install pctx[claude]" ) from e - def _text_content_block(val: str) -> dict: + def _text_content_block(val: str) -> dict[str, Any]: return {"content": [{"type": "text", "text": val}]} # build all tools @@ -873,7 +873,7 @@ def _text_content_block(val: str) -> dict: get_tool_description("execute_bash", overrides=descriptions), ExecuteBashInput.model_json_schema(), ) - async def execute_bash(args: dict[str, Any]) -> str: + async def execute_bash(args: dict[str, Any]) -> dict[str, Any]: tool_input = ExecuteBashInput(**args) bash_out = await self.execute_bash(tool_input.command) return _text_content_block(bash_out.markdown()) @@ -885,7 +885,7 @@ async def execute_bash(args: dict[str, Any]) -> str: ), ExecuteTypescriptInput.model_json_schema(), ) - async def execute_typescript(args: dict[str, Any]) -> str: + async def execute_typescript(args: dict[str, Any]) -> dict[str, Any]: tool_input = ExecuteTypescriptInput(**args) exec_out = await self.execute_typescript( tool_input.code, disclosure=disclosure @@ -897,7 +897,7 @@ async def execute_typescript(args: dict[str, Any]) -> str: get_tool_description("list_functions", overrides=descriptions), {"type": "object"}, ) - async def list_functions(_args: dict[str, Any]) -> str: + async def list_functions(_args: dict[str, Any]) -> dict[str, Any]: listed = await self.list_functions() return _text_content_block(listed.code) @@ -906,7 +906,7 @@ async def list_functions(_args: dict[str, Any]) -> str: get_tool_description("get_function_details", overrides=descriptions), GetFunctionDetailsInput.model_json_schema(), ) - async def get_function_details(args: dict[str, Any]) -> str: + async def get_function_details(args: dict[str, Any]) -> dict[str, Any]: tool_input = GetFunctionDetailsInput(**args) details = await self.get_function_details(tool_input.functions) return _text_content_block(details.code) @@ -920,7 +920,7 @@ class SearchFunctionsInput(BaseModel): get_tool_description("search_functions", overrides=descriptions), SearchFunctionsInput.model_json_schema(), ) - async def search_functions(args: dict[str, Any]) -> str: + async def search_functions(args: dict[str, Any]) -> dict[str, Any]: print(f"Claude fn called search_functions: {args}") tool_input = SearchFunctionsInput(**args) functions = await self.search_functions(tool_input.query, tool_input.k) diff --git a/pctx-py/src/pctx_client/_tool.py b/pctx-py/src/pctx_client/_tool.py index e3d80900..8c90a213 100644 --- a/pctx-py/src/pctx_client/_tool.py +++ b/pctx-py/src/pctx_client/_tool.py @@ -52,8 +52,8 @@ class BaseTool(BaseModel): ), ) - _input_validator: Draft202012Validator | None = PrivateAttr(default=None) - _output_validator: Draft202012Validator | None = PrivateAttr(default=None) + _input_validator: Draft202012Validator | None = PrivateAttr(default=None) # ty: ignore[invalid-type-form] + _output_validator: Draft202012Validator | None = PrivateAttr(default=None) # ty: ignore[invalid-type-form] @model_validator(mode="after") def _compile_schema_validators(self) -> "BaseTool": @@ -130,7 +130,7 @@ def from_func( docstring = parse_docstring(textwrap.dedent(description or func.__doc__ or "")) - name_ = name or func.__name__ + name_ = name or func.__name__ # ty: ignore[unresolved-attribute] if input_schema is None: in_schema = infer_input_model(f"{name_}_Input", func, docstring=docstring) diff --git a/pctx-py/src/pctx_client/_websocket_client.py b/pctx-py/src/pctx_client/_websocket_client.py index 7fede708..f076228e 100644 --- a/pctx-py/src/pctx_client/_websocket_client.py +++ b/pctx-py/src/pctx_client/_websocket_client.py @@ -78,7 +78,7 @@ async def _connect(self, code_mode_session: str): try: headers = { "x-code-mode-session": code_mode_session, - "x-pctx-api-key": self._api_key, + "x-pctx-api-key": self._api_key or "", } self.ws = await websockets.connect(self.url, additional_headers=headers) except Exception as e: @@ -143,7 +143,7 @@ async def execute_typescript( request = ExecuteCodeRequest( id=request_id, method="execute_typescript", - params=ExecuteCodeParams(code=code, style=disclosure), + params=ExecuteCodeParams(code=code, disclosure=disclosure), ) try: diff --git a/pctx-py/src/pctx_client/descriptions/__init__.py b/pctx-py/src/pctx_client/descriptions/__init__.py index 9c77cc0a..856016aa 100644 --- a/pctx-py/src/pctx_client/descriptions/__init__.py +++ b/pctx-py/src/pctx_client/descriptions/__init__.py @@ -30,7 +30,7 @@ def get_tool_description( tool_name: ToolName, disclosure: ToolDisclosure | ToolDisclosureName = ToolDisclosure.CATALOG, version: int = 1, - overrides: dict[ToolName, str] = None, + overrides: dict[ToolName, str] | None = None, ) -> str: """Load a tool description string from the data directory. @@ -47,11 +47,12 @@ def get_tool_description( if overrides is not None and tool_name in overrides: return overrides[tool_name] + path_segment = tool_name if tool_name == "execute_typescript": - tool_name = f"{tool_name}_{disclosure.value}" + path_segment = f"{tool_name}_{disclosure.value}" return ( - files("pctx_client.descriptions.data.tools") / tool_name / f"v{version}.txt" + files("pctx_client.descriptions.data.tools") / path_segment / f"v{version}.txt" ).read_text(encoding="utf-8") diff --git a/pctx-py/src/pctx_client/models.py b/pctx-py/src/pctx_client/models.py index 1a6f0172..0b91248c 100644 --- a/pctx-py/src/pctx_client/models.py +++ b/pctx-py/src/pctx_client/models.py @@ -26,7 +26,7 @@ class ToolDisclosure(str, Enum): FS = "filesystem" # SIDECAR = "sidecar" # <--- not fully supported by session server yet - def contains_tool(self, tool_name: ToolName) -> bool: + def contains_tool(self, tool_name: str) -> bool: if self == ToolDisclosure.CATALOG: allowed = { "get_function_details", diff --git a/pctx-py/uv.lock b/pctx-py/uv.lock index d22a6030..c53f7092 100644 --- a/pctx-py/uv.lock +++ b/pctx-py/uv.lock @@ -3379,6 +3379,7 @@ dev = [ { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, + { name = "ty" }, ] docs = [ { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -3417,6 +3418,7 @@ dev = [ { name = "pytest", specifier = ">=8.0.0" }, { name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "ruff", specifier = ">=0.14.7" }, + { name = "ty", specifier = ">=0.0.34" }, ] docs = [ { name = "myst-parser" }, @@ -5605,6 +5607,30 @@ 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.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 = "typer" version = "0.23.1" From cbc739f388e12b0dff22648486d7fc95353e2c87 Mon Sep 17 00:00:00 2001 From: Elias Posen Date: Thu, 7 May 2026 17:57:48 -0400 Subject: [PATCH 2/2] typecheck in GH action --- .github/workflows/ci-python.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-python.yaml b/.github/workflows/ci-python.yaml index 9810f57b..dcf3f040 100644 --- a/.github/workflows/ci-python.yaml +++ b/.github/workflows/ci-python.yaml @@ -2,7 +2,7 @@ name: Python CI on: push: - branches: [main] + branches: [ main ] paths: - "pctx-py/**" - ".github/workflows/ci-python.yaml" @@ -21,7 +21,7 @@ defaults: working-directory: pctx-py jobs: - lint: + check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -37,19 +37,26 @@ jobs: python-version: "3.10" - name: Install dependencies - run: uv sync --group dev + id: install + run: uv sync --all-extras --all-groups - name: Check formatting + if: ${{ !cancelled() && steps.install.outcome == 'success' }} run: uv run ruff format --check . - name: Run linter + if: ${{ !cancelled() && steps.install.outcome == 'success' }} run: uv run ruff check . + - name: Type check + if: ${{ !cancelled() && steps.install.outcome == 'success' }} + run: uv run ty check src + test: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: [ "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v4