diff --git a/Makefile b/Makefile index ff5c5edd..e71d46d0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash -.PHONY: help develop install-dev install-deps fixup test test-core test-wheel lint pylint nox +.PHONY: help develop install-dev install-deps fixup test test-core test-wheel lint pylint typecheck nox develop: install-dev mise exec -- pre-commit install @@ -30,6 +30,9 @@ lint: pylint: mise exec -- $(MAKE) -C py pylint +typecheck: + mise exec -- $(MAKE) -C py typecheck + nox: test help: @@ -40,6 +43,7 @@ help: @echo " install-dev - Install pinned tools and create/update the repo env via mise" @echo " lint - Run pre-commit hooks plus Python SDK pylint via py/Makefile" @echo " pylint - Run Python SDK pylint only via py/Makefile" + @echo " typecheck - Run ty type checker on the Python SDK via py/Makefile" @echo " nox - Alias for test" @echo " test - Run the Python SDK nox matrix via py/Makefile" @echo " test-core - Run Python SDK core tests via py/Makefile" diff --git a/mise.toml b/mise.toml index f92d09ea..70077cfe 100644 --- a/mise.toml +++ b/mise.toml @@ -12,6 +12,7 @@ _.file = ".env" [tools] ruff = "0.12.7" +ty = "0.0.23" [hooks] postinstall = "make install-deps" diff --git a/py/Makefile b/py/Makefile index f692d4e0..61140584 100644 --- a/py/Makefile +++ b/py/Makefile @@ -2,7 +2,7 @@ PYTHON ?= python UV := $(PYTHON) -m uv UV_VERSION := $(shell awk '$$1=="uv" { print $$2 }' ../.tool-versions) -.PHONY: lint pylint test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev install-optional test-core _check-git-clean +.PHONY: lint pylint typecheck test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev install-optional test-core _check-git-clean clean: rm -rf build dist @@ -17,6 +17,9 @@ lint: fixup pylint: nox -s pylint +typecheck: + ty check + test: nox -x @@ -73,6 +76,7 @@ help: @echo " install-dev - Install package in development mode with all dependencies" @echo " lint - Run pylint checks" @echo " pylint - Run pylint without pre-commit hooks" + @echo " typecheck - Run ty type checker" @echo " test - Run all tests" @echo " test-core - Run core tests only" @echo " test-wheel - Run tests against built wheel" diff --git a/py/noxfile.py b/py/noxfile.py index be0e7798..cc03e9d6 100644 --- a/py/noxfile.py +++ b/py/noxfile.py @@ -315,6 +315,17 @@ def pylint(session): session.run("pylint", "--errors-only", *files) +@nox.session(venv_backend="none") +def typecheck(session): + """Run the ty type checker. + + ty is installed via mise (see mise.toml) and reads its configuration from + py/ty.toml. It does not need optional vendor packages because + allowed-unresolved-imports suppresses those diagnostics. + """ + session.run("ty", "check", *session.posargs, external=True) + + @nox.session() def test_latest_wrappers_novcr(session): """Run the latest wrapper tests without vcrpy.""" diff --git a/py/ty.toml b/py/ty.toml new file mode 100644 index 00000000..7e343684 --- /dev/null +++ b/py/ty.toml @@ -0,0 +1,65 @@ +[environment] +python-version = "3.10" +# Use the repo-level venv for third-party stubs/packages. +python = "../venv" + +[src] +# Focus on the SDK source tree; integration packages and examples have their +# own (often uninstalled) dependency surfaces and are better checked separately. +include = ["src/"] +exclude = [ + "**/__pycache__/", +] + +[rules] +# Start with errors-only to match the existing pylint --errors-only baseline. +# Warnings can be promoted to errors later as the codebase is cleaned up. + +[analysis] +# Optional provider packages are imported behind try/except guards. +# Suppress unresolved-import for all of them so ty can run without +# installing the full dependency surface (matching how test_core works). +# Use ** to match arbitrarily deep submodules (e.g. opentelemetry.sdk.trace). +allowed-unresolved-imports = [ + "agno.**", + "agents.**", + "anthropic.**", + "autoevals.**", + "boto3.**", + "botocore.**", + "braintrust_core.**", + "claude_agent_sdk.**", + "dspy.**", + "google.adk.**", + "google.genai.**", + "httpx.**", + "langchain.**", + "langchain_anthropic.**", + "langchain_core.**", + "langchain_openai.**", + "langsmith.**", + "litellm.**", + "logfire.**", + "mcp.**", + "openai.**", + "opentelemetry.**", + "orjson.**", + "pydantic_ai.**", + "starlette.**", + "temporalio.**", + "tenacity.**", + "uvicorn.**", + "aiohttp.**", + "anyio.**", +] + +[terminal] +output-format = "full" + +# Relax rules for test files – they use mocks, dynamic fixtures, etc. +[[overrides]] +include = ["src/**/test_*.py", "src/**/conftest.py"] +[overrides.rules] +invalid-argument-type = "warn" +unresolved-attribute = "warn" +call-non-callable = "warn"