Skip to content

Commit 30ee0b6

Browse files
Python: Split type checkers by target (pyright source, 5 checkers on tests/samples)
Rework the typing setup along the lines of the 'too many type checkers' approach: - Pyright (strict) is now the sole source-code type checker; mypy is removed from source and its [tool.mypy] block becomes a relaxed profile used only for tests/samples. - Tests are checked by all five checkers (pyright relaxed, mypy, pyrefly, ty, zuban); samples by pyright, pyrefly, and ty. All run in a relaxed/ basic profile so authors aren't forced into over-annotation. - Add pyrightconfig.tests.json and bump sample pyright configs to basic. - Unify test/sample typing onto the same parallel fan-out used by source pyright via run_command_items in task_runner.py. - Make version-conditional imports symmetric: keep or drop the '# type: ignore' on both branches so results match across interpreter versions (local vs CI). - Update SKILL.md, DEV_SETUP.md, and CODING_STANDARD.md for the five gating checkers and pyright on source+tests+samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 40a2dd5 commit 30ee0b6

394 files changed

Lines changed: 4999 additions & 3773 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/python-code-quality.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ jobs:
113113
- name: Run markdown code lint
114114
run: uv run poe markdown-code-lint
115115

116-
mypy:
117-
name: Mypy Checks
116+
test-typing:
117+
name: Test Typing Checks
118118
if: "!cancelled()"
119119
strategy:
120120
fail-fast: false
@@ -139,7 +139,5 @@ jobs:
139139
os: ${{ runner.os }}
140140
env:
141141
UV_CACHE_DIR: /tmp/.uv-cache
142-
- name: Run Mypy
143-
env:
144-
GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}
145-
run: uv run python scripts/workspace_poe_tasks.py ci-mypy
142+
- name: Run tests/samples type checkers (mypy, pyrefly, ty)
143+
run: uv run python scripts/workspace_poe_tasks.py ci-test-typing

python/.github/skills/python-code-quality/SKILL.md

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,22 @@ uv run poe syntax -C # Check only
2121
uv run poe syntax -S # Samples only
2222

2323
# Type checking
24-
uv run poe pyright # Pyright fan-out across packages
24+
#
25+
# Division of labor (see "Type checking architecture" below):
26+
# - Pyright (strict) is the source-code type checker.
27+
# - Pyright (relaxed `basic`), mypy, pyrefly, ty, zuban all check the TESTS;
28+
# pyright/pyrefly/ty also check the SAMPLES (mypy/zuban skip script-style samples).
29+
uv run poe pyright # Pyright (strict) over SOURCE, fan-out across packages
2530
uv run poe pyright -P core
2631
uv run poe pyright -A
27-
uv run poe mypy # MyPy fan-out across packages
32+
uv run poe test-typing # mypy + pyrefly + ty + zuban + pyright over each package's TESTS
33+
uv run poe test-typing -P core
34+
uv run poe test-typing -S # samples (pyrefly + ty + pyright)
35+
uv run poe test-typing -P core --checker mypy # narrow to one checker (repeatable)
36+
uv run poe test-typing -P core --checker pyright # relaxed pyright over the tests
37+
uv run poe mypy # alias: MyPy over the tests only
2838
uv run poe mypy -P core
29-
uv run poe mypy -A
30-
uv run poe typing # Both pyright and mypy
39+
uv run poe typing # Pyright (source) + the tests checkers
3140
uv run poe typing -P core
3241
uv run poe typing -A
3342

@@ -67,6 +76,33 @@ when markdown files change, and sample syntax lint/pyright only when files
6776
under `samples/` change.
6877
They intentionally do not run workspace `pyright` or `mypy` by default.
6978

79+
## Type checking architecture
80+
81+
Following the "too many type checkers" approach, type checkers are split by target:
82+
83+
| Target | Checker(s) | Mode | Config |
84+
|--------|-----------|------|--------|
85+
| Source (`agent_framework*`) | **pyright** | strict | `[tool.pyright]` in `pyproject.toml` |
86+
| Tests | pyright, mypy, pyrefly, ty, zuban | relaxed/basic | `pyrightconfig.tests.json`, `[tool.mypy]`, `pyrefly.toml`, `ty` rules |
87+
| Samples | pyright, pyrefly, ty | basic | `pyrightconfig.samples.json`, `pyrefly.samples.toml`, `ty.samples.toml` |
88+
89+
- **Pyright is the only *strict* source-code checker**, and it ALSO runs in a relaxed
90+
`basic` profile over the tests and samples (so the surfaces customers copy from are
91+
validated by every checker, including pyright). MyPy was removed from source; its
92+
`[tool.mypy]` block is now a *relaxed* profile used only for tests/samples.
93+
- The extra checkers run over tests/samples because those exercise the public API the way
94+
users do. The profile is intentionally relaxed (private access allowed, untyped test
95+
bodies allowed) so authors aren't forced into ugly over-annotation.
96+
- **Gating checkers** are `pyright`, `mypy`, `pyrefly`, `ty`, and `zuban` — all five run by
97+
default and gate CI. `zuban` is the strictest of the mypy-compatible pair, so the same
98+
`[tool.mypy]` config yields more findings; suppress zuban-only friction with shared
99+
`# type: ignore[code]`. Suppress relaxed-pyright friction with `# pyright: ignore[rule]`.
100+
- **Samples** add `pyright` to `pyrefly` + `ty` — mypy/zuban can't resolve script-style
101+
sample layouts (numeric-prefixed dirs, duplicate `main.py`), but pyright handles them.
102+
- The strict source-pyright (`[tool.pyright]`) enforces `reportUnnecessaryTypeIgnoreComment`
103+
and excludes tests/samples; the relaxed test/sample pyright configs do not flag unnecessary
104+
ignores.
105+
70106
## Ruff Configuration
71107

72108
- Line length: 120
@@ -77,8 +113,12 @@ They intentionally do not run workspace `pyright` or `mypy` by default.
77113

78114
## Pyright Configuration
79115

80-
- Strict mode enabled
81-
- Excludes: tests, .venv, packages/devui/frontend
116+
- **Source**: strict mode (`[tool.pyright]`), `reportUnnecessaryTypeIgnoreComment = "error"`,
117+
excludes tests, samples, .venv, packages/devui/frontend.
118+
- **Tests**: relaxed `basic` profile (`pyrightconfig.tests.json`) — private import/usage and
119+
not-required TypedDict access allowed; runs as the `pyright` checker in `test-typing`.
120+
- **Samples**: relaxed `basic` profile (`pyrightconfig.samples.json`, with a py310 variant) —
121+
runs as the `pyright` checker in `test-typing -S`.
82122

83123
## Parallel Execution
84124

@@ -90,6 +130,6 @@ in-process with streaming output.
90130

91131
CI splits into 4 parallel jobs:
92132
1. **Pre-commit hooks** — lightweight hooks (SKIP=poe-check)
93-
2. **Package checks** — syntax/pyright via check-packages
133+
2. **Package checks** — syntax/pyright (source) via check-packages
94134
3. **Samples & markdown**`check -S` plus `markdown-code-lint`
95-
4. **Mypy** — change-detected mypy checks
135+
4. **Test Typing** — change-detected mypy/pyrefly/ty over tests (`ci-test-typing`)

python/CODING_STANDARD.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,18 @@ Use typing as a helper first and suppressions as a last resort:
9292
- **Prefer explicit typing before suppression**: Start with clearer type annotations, helper types, overloads,
9393
protocols, or refactoring dynamic code into typed helpers. Prioritize performance over completeness of typing, but make a good-faith effort to reduce uncertainty with typing before ignoring. Prefer to use a cast over a typeguard function since that does add overhead.
9494
- **Avoid redundant casts**: Do not add `cast(...)` if the type already matches; casts should be reserved for
95-
unavoidable narrowing where the runtime contract is known, we will use mypy's check on redundant casts to enforce this.
95+
unavoidable narrowing where the runtime contract is known.
9696
- **Avoid multiple assignments**: Avoid assigning multiple variables just to get typing to pass, that has performance impact while typing should not have that.
97-
- **Line-level pyright ignores only**: If suppression is still required, use a line-level rule-specific ignore
97+
- **Source vs tests/samples**: Source code (`agent_framework*`) is checked **by pyright in strict mode** — use
98+
`# pyright: ignore[...]` there, never `# type: ignore` (strict pyright flags unnecessary ignores as errors). Tests
99+
and samples are checked by pyright (relaxed `basic`), mypy, pyrefly, ty (and zuban on tests) in a relaxed/basic
100+
profile; prefer real fixes (`isinstance`, `cast`, annotations, asserts for Optional access) over per-line ignores,
101+
and keep test/sample bodies readable rather than over-annotated. When a relaxed-pyright suppression is genuinely
102+
needed in tests/samples, use `# pyright: ignore[rule]`; the relaxed test/sample configs do not flag unnecessary
103+
ignores, so combine with a mypy/zuban `# type: ignore[code]` on the same line only where both are required.
104+
- **Line-level pyright ignores only**: If suppression is still required in source, use a line-level rule-specific ignore
98105
(`# pyright: ignore[reportGeneralTypeIssues]`), file-level is allowed if there is a compelling reason for it, that should be documented right beneath the ignore.
99-
Never change the global suppression flags for mypy and pyright unless the dev team okays it.
106+
Never change the global suppression flags unless the dev team okays it.
100107
- **Private usage boundary**: Accessing private members across `agent_framework*` packages can be acceptable for this
101108
codebase, but private member usage for non-Agent Framework dependencies should remain flagged.
102109

python/DEV_SETUP.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,23 +289,36 @@ uv run poe <command> -A # aggregate sweep where supported
289289
```
290290

291291
#### `pyright`
292-
Run Pyright type checking:
292+
Run Pyright type checking. Pyright is the **strict source-code type checker**, and also runs
293+
in a relaxed `basic` profile over the tests + samples (as one of the `test-typing` checkers):
293294
```bash
294295
uv run poe pyright
295296
uv run poe pyright -P core
296297
uv run poe pyright -A
297298
```
298299

300+
#### `test-typing`
301+
Run the **tests + samples** type checkers. Source code is owned by strict Pyright; the tests
302+
and samples are checked by `pyright` (relaxed), `mypy`, `pyrefly`, `ty`, and `zuban` in a
303+
deliberately relaxed/basic profile so real public-API type errors surface without forcing
304+
test/sample authors to fully annotate their code. All five gate CI:
305+
```bash
306+
uv run poe test-typing # all checkers over every package's tests
307+
uv run poe test-typing -P core # one package
308+
uv run poe test-typing -S # samples (pyright + pyrefly + ty; mypy/zuban skip script-style samples)
309+
uv run poe test-typing -P core --checker mypy # narrow to one checker (repeatable)
310+
uv run poe test-typing -P core --checker pyright # relaxed pyright over the tests
311+
```
312+
299313
#### `mypy`
300-
Run MyPy type checking:
314+
Convenience alias that runs MyPy over the test suite (MyPy no longer runs on source):
301315
```bash
302316
uv run poe mypy
303317
uv run poe mypy -P core
304-
uv run poe mypy -A
305318
```
306319

307320
#### `typing`
308-
Run both Pyright and MyPy:
321+
Run Pyright over source **and** the tests/samples checkers:
309322
```bash
310323
uv run poe typing
311324
uv run poe typing -P core

python/packages/a2a/agent_framework_a2a/_agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def run(
344344
**kwargs: Any,
345345
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
346346

347-
def run( # pyright: ignore[reportIncompatibleMethodOverride]
347+
def run(
348348
self,
349349
messages: AgentRunInputs | None = None,
350350
*,
@@ -464,7 +464,7 @@ async def _map_a2a_stream(
464464
if session is None:
465465
raise RuntimeError("Provider session must be available when context providers are configured.")
466466
await provider.before_run(
467-
agent=self, # type: ignore[arg-type]
467+
agent=self,
468468
session=session,
469469
context=session_context,
470470
state=session.state.setdefault(provider.source_id, {}),

0 commit comments

Comments
 (0)