Skip to content

Commit 0475b16

Browse files
authored
ty: type checking as a first-class gate (#126)
[tool.ty] in root pyproject.toml; ty installed in the devcontainer via `uv tool install` alongside ruff; new `ty` CI job runs `uv sync` then `uvx ty@<pin> check`. post-create.sh runs `uv sync` so the editor's ty extension has a .venv to resolve against. Renovate's workflow-yaml regex widened to track the TY_VERSION env-var pin. README updated with the new CI row, on-save row, and recommended extension.
1 parent 80cae2f commit 0475b16

8 files changed

Lines changed: 66 additions & 2 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ COPY --from=ghcr.io/astral-sh/uv:0.11.21 /uv /uvx /usr/local/bin/
2222
# Renovate's regex manager tracks RUFF_VERSION via the comment above the ARG.
2323
# renovate: datasource=pypi depName=ruff
2424
ARG RUFF_VERSION=0.15.17
25+
# ty: static type checker. Same `uv tool install` pattern as ruff so the
26+
# binary lands on PATH for CI, pre-commit, and the `astral-sh.ty` editor
27+
# extension. UV_TOOL_{BIN_DIR,DIR} are shared across both installs.
28+
# renovate: datasource=pypi depName=ty
29+
ARG TY_VERSION=0.0.1a25
2530
ENV UV_TOOL_BIN_DIR=/usr/local/bin \
2631
UV_TOOL_DIR=/usr/local/share/uv-tools
27-
RUN uv tool install --no-cache "ruff==${RUFF_VERSION}"
32+
RUN uv tool install --no-cache "ruff==${RUFF_VERSION}" \
33+
&& uv tool install --no-cache "ty==${TY_VERSION}"
2834

2935
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
3036
build-essential \

.devcontainer/devcontainer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"customizations": {
5555
"vscode": {
5656
"extensions": [
57+
"astral-sh.ty",
5758
"bazelbuild.vscode-bazel",
5859
"charliermarsh.ruff",
5960
"cnshenj.vscode-task-manager",

.devcontainer/post-create.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ fi
4848
# Full path: postCreate may not see remoteEnv's PATH yet.
4949
"$HOME/.local/bin/pre-commit" install
5050

51+
# Materialize the uv workspace's .venv so the ty editor extension (and any
52+
# CLI `ty check` run) can resolve third-party imports. Without it, ty has no
53+
# search path beyond first-party + stdlib and every non-stdlib import is
54+
# flagged. Idempotent; subsequent rebuilds are no-ops if the lockfile is
55+
# unchanged.
56+
uv sync
57+
5158
# Warm Bazel: fetches the registered Go SDK, rules_go, gazelle, etc.
5259
bazel version
5360

.github/workflows/ci.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,28 @@ jobs:
118118
args: format --check
119119
- run: ruff check
120120

121+
# ty: Astral's static type checker. Config lives in `[tool.ty]` in
122+
# //:pyproject.toml — same single-source-of-truth posture as ruff. No
123+
# dedicated GitHub Action exists yet (ty is still alpha), so we install uv
124+
# and run `uvx ty@<pin> check`. The pin is Renovate-tracked via the comment
125+
# above TY_VERSION.
126+
ty:
127+
name: ty
128+
runs-on: ubuntu-latest
129+
steps:
130+
- uses: actions/checkout@v6
131+
- uses: astral-sh/setup-uv@v6
132+
# `uv sync` materializes `.venv` from uv.lock so ty can resolve
133+
# third-party imports (e.g. the smoke target's `requests`). Without it,
134+
# ty's only search paths are first-party + stdlib and any non-stdlib
135+
# import fails with `unresolved-import`.
136+
- run: uv sync
137+
- name: ty check
138+
env:
139+
# renovate: datasource=pypi depName=ty
140+
TY_VERSION: "0.0.1a25"
141+
run: uvx "ty@${TY_VERSION}" check
142+
121143
# Build and test runs once per supported target platform, on a runner whose host matches
122144
# the target. (Running tests natively per platform is the only way (without an emulation
123145
# layer we do not have) to actually exercise platform-specific code paths and catch regressions
@@ -137,6 +159,7 @@ jobs:
137159
no-cgo-check,
138160
golangci-lint,
139161
ruff,
162+
ty,
140163
]
141164
strategy:
142165
fail-fast: false

.vscode/extensions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"recommendations": [
3+
"astral-sh.ty",
34
"bazelbuild.vscode-bazel",
45
"charliermarsh.ruff",
56
"esbenp.prettier-vscode",

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ Two GitHub Actions workflows run on every push and pull request to `main`.
160160
| No-cgo policy check | Always - rejects `import "C"` and transitive deps that compile C/C++/cgo/SWIG |
161161
| golangci-lint | After module check passes - runs per Go module |
162162
| ruff | Always - `ruff format --check` and `ruff check` over all Python |
163+
| ty | Always - `uvx ty check` (Astral's static type checker) over all Python |
163164
| Build and test | After all checks above pass |
164165
| Coverage | After build and test - `bazel coverage //...`, uploads merged lcov to Codecov |
165166
@@ -205,6 +206,9 @@ VS Code-derived editors (e.g. Google Antigravity). Recommended extensions
205206
- [`charliermarsh.ruff`](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) -
206207
surfaces `ruff check` diagnostics inline and applies `ruff format` on save, matching what the CI
207208
`ruff` job and the pre-commit hooks enforce.
209+
- [`astral-sh.ty`](https://marketplace.visualstudio.com/items?itemName=astral-sh.ty) - surfaces
210+
`ty check` diagnostics inline, matching what the CI `ty` job enforces. Config lives in
211+
`[tool.ty]` in `//:pyproject.toml`.
208212
- [`emeraldwalk.runonsave`](https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave) -
209213
triggers the repo-health scripts on save.
210214
- [`ryanluker.vscode-coverage-gutters`](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters) -
@@ -214,6 +218,7 @@ VS Code-derived editors (e.g. Google Antigravity). Recommended extensions
214218
| -------------------- | ------------------------------------------ |
215219
| `golangci-lint` | `*.go` files |
216220
| `ruff` (diagnostics + format) | `*.py` files |
221+
| `ty` (type diagnostics) | `*.py` files |
217222
| `check-go-modules` | `go.mod`, workflow `.yml`, `.golangci.yml` |
218223
| `check-go-work` | `go.mod`, `go.work` |
219224

pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,24 @@ select = ["E", "F", "I", "B", "UP", "SIM", "RUF", "S"]
6565
# like "/tmp/repo" used as mock arguments (no actual temp file is created).
6666
"**/test_*.py" = ["S101", "S105", "S106", "S108", "S311"]
6767
"**/*_test.py" = ["S101", "S105", "S106", "S108", "S311"]
68+
69+
# ty: static type checker. Single repo-wide config. CI runs `uvx ty check`;
70+
# the editor extension (`astral-sh.ty` in devcontainer.json) reads the same
71+
# section. Strict mode — promote inference warnings to errors so missing
72+
# annotations don't silently pass.
73+
[tool.ty]
74+
75+
[tool.ty.environment]
76+
python-version = "3.14"
77+
78+
[tool.ty.src]
79+
# Mirror ruff's `src` so ty scans the same first-party trees. Excludes cover
80+
# Bazel's symlink farm, virtualenvs, and the host-state directory populated by
81+
# .devcontainer/initialize.sh (none of which are first-party source).
82+
include = ["meta", "tools"]
83+
exclude = ["bazel-*", ".venv", "venv", ".git-plumbing"]
84+
85+
# Rule severities are left at ty's defaults for now. ty ships with a
86+
# strict-leaning default profile and refining the rule map preemptively (before
87+
# real Python code lands) would be speculative; tighten as needed when real
88+
# code surfaces a category of finding we want to escalate.

renovate.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"customType": "regex",
4848
"managerFilePatterns": [ "/^\\.github/workflows/.*\\.ya?ml$/" ],
4949
"matchStrings": [
50-
"#\\s*renovate:\\s*datasource=(?<datasource>[a-z-]+?)\\s+depName=(?<depName>\\S+?)\\s*\\n\\s*[a-z-]*version:\\s*\"(?<currentValue>[^\"]+)\""
50+
"#\\s*renovate:\\s*datasource=(?<datasource>[a-z-]+?)\\s+depName=(?<depName>\\S+?)\\s*\\n\\s*[A-Za-z_-]*[Vv]ersion:\\s*\"(?<currentValue>[^\"]+)\""
5151
]
5252
}
5353
],

0 commit comments

Comments
 (0)