CI: Add basedpyright type checker - #167
Merged
ModeSevenIndustrialSolutions merged 1 commit intoAug 16, 2026
Merged
Conversation
Adds the canonical basedpyright hook from actions-template, a tuned [tool.basedpyright] block in pyproject.toml, and remediates every diagnostic it reports so the hook lands green. Configuration uses typeCheckingMode = "standard" scoped to src and tests. reportMissingImports and reportMissingTypeStubs are disabled because the hook runs basedpyright in an isolated virtualenv without this project's runtime dependencies. reportUnusedImport and reportUnusedVariable are disabled as ruff already covers them (F401, F841). basedpyright is pinned in the dev extra to the version the hook is frozen at, so local runs and CI cannot disagree. The diagnostics were latent defects rather than noise: - GerritExtractor.extract() was annotated as returning GerritMetadata | None, but every code path returns a GerritMetadata; its own comment said as much. The stale annotation made main.py look like it could pass None into the non-optional CompleteMetadata.gerrit_environment field, which would raise a Pydantic ValidationError at runtime. Corrected the annotation and folded the explanatory comment into the docstring. - Config re-annotated GITHUB_STEP_SUMMARY, GITHUB_EVENT_PATH and GITHUB_ACTOR_ID inside __init__, shadowing the class-level declarations. The GITHUB_ACTOR_ID redeclaration narrowed the type to int | None, contradicting the class-level int | str | None and making the documented "raw value on validation failure" fallback a type error. Dropped the redundant inline annotations. - Three fixtures in test_main.py passed roughly 80 keyword arguments that do not exist on the models: action, sender_login, protected, author_email, title, labels, topic, change_subject and others. Pydantic silently discards unknown fields, so these were dead weight that made the fixtures misrepresent the models. Replaced them with the real fields, mapping intent across where an equivalent exists: author_name to author, head_ref to source_branch, base_ref to target_branch. Separately, the ruff and ruff-format hooks carried a files pattern of ^(scripts|tests|custom_components)/.+\.py$ inherited from an unrelated template. Neither scripts/ nor custom_components/ exists here, so the pattern silently excluded all of src/ from pre-commit linting. Removed it to match the canonical template; ruff already passes repository-wide. Closes: lfreleng-actions#139 Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Matthew Watkins <mwatkins@linuxfoundation.org>
ModeSevenIndustrialSolutions
requested review from
a team
and
a balanced review from Copilot
August 15, 2026 13:48
Copilot started reviewing on behalf of
ModeSevenIndustrialSolutions
August 15, 2026 13:48
View session
There was a problem hiding this comment.
Pull request overview
Adds basedpyright type checking and resolves existing diagnostics while preserving runtime behavior.
Changes:
- Adds pinned basedpyright configuration, dependency, lock data, and pre-commit hook.
- Corrects stale type annotations and invalid test fixture fields.
- Expands Ruff pre-commit coverage to
src/.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
.pre-commit-config.yaml |
Adds basedpyright and broadens Ruff coverage. |
pyproject.toml |
Configures and pins basedpyright. |
uv.lock |
Locks basedpyright and Node.js dependency. |
src/config.py |
Removes contradictory instance annotations. |
src/extractors/gerrit.py |
Corrects the extractor return type and documentation. |
tests/test_main.py |
Aligns fixtures with actual model fields. |
tests/test_commit_extractor.py |
Narrows the optional message before measuring length. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
askb
approved these changes
Aug 15, 2026
ModeSevenIndustrialSolutions
merged commit Aug 16, 2026
a31da44
into
lfreleng-actions:main
19 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #139.
Adds the canonical
basedpyrighthook fromactions-template, a tuned[tool.basedpyright]block inpyproject.toml, and remediates every diagnostic it reports so the hook lands green.Baseline in the issue was 111 errors / 3262 warnings (default "recommended" mode). Final state is 0 errors / 0 warnings, both in the sandboxed hook environment and locally via
uv run basedpyrightwhere third-party imports actually resolve.Configuration
standardmode scoped tosrcandtests.reportMissingImports/reportMissingTypeStubsare off because the hook installs basedpyright into an isolated virtualenv without this project's runtime dependencies (pydantic, PyGithub, GitPython, PyYAML, ijson). Same rationale asdependamerge.reportUnusedImport/reportUnusedVariableare off because ruff already covers them (F401,F841).basedpyright==1.39.9added to thedevextra, pinned to the version the hook is frozen at, so local runs and CI cannot disagree.Remediation
Every diagnostic turned out to be a latent defect rather than noise.
src/extractors/gerrit.py— stale return type masking a runtime failureGerritExtractor.extract()was annotated-> GerritMetadata | None, but every code path returns aGerritMetadata— its own inline comment said so ("Always return GerritMetadata object (with empty fields) instead of None"). The stale annotation mademain.py:211look like it could passNoneinto the non-optionalCompleteMetadata.gerrit_environmentfield, which would raise a PydanticValidationErrorat runtime. Corrected the annotation and folded the explanatory comment into the docstring.src/config.py— attribute redeclaration contradicting the class-level types__init__re-annotatedGITHUB_STEP_SUMMARY,GITHUB_EVENT_PATHandGITHUB_ACTOR_ID, shadowing the class-level declarations. TheGITHUB_ACTOR_IDcase was the harmful one: the inline annotation narrowed it toint | None, contradicting the class-levelint | str | Noneand making the documented "fall back to the raw value on validation failure" path a type error. Dropped the redundant inline annotations so the class-level declarations apply.tests/test_main.py— ~80 keyword arguments that do not exist on the modelsThree fixtures passed arguments such as
action,sender_login,protected,author_email,committer_name,timestamp,title,body,state,merged,draft,head_sha,labels,requested_reviewers,assignees,topic,change_owner_name,change_subjectandcommit_message— none of which are fields onEventMetadata,RefMetadata,CommitMetadata,PullRequestMetadata,ActorMetadataorGerritMetadata.Pydantic silently discards unknown fields by default, so all of this was dead weight that made the fixtures actively misrepresent the models to anyone reading them. No assertion anywhere referenced the discarded values. Replaced with the real fields, mapping intent across where an equivalent exists (
author_name→author,head_ref→source_branch,base_ref→target_branch) and populating the genuine flags (is_branch_push,is_pull_request,is_default_branch).Drive-by: ruff was never linting
src/Not strictly in scope, but it sits three lines from the hook being added and is a real coverage hole.
The
ruffandruff-formathooks carriedfiles: ^(scripts|tests|custom_components)/.+\.py$, inherited from an unrelated (Home Assistant style) template. Neitherscripts/norcustom_components/exists in this repository, so the pattern matchedtests/only and silently excluded all ofsrc/from pre-commit linting. Removed to match the canonical template. Ruff already passes repository-wide (All checks passed!,50 files already formatted), so this is a no-op in terms of code changes — it just closes the gap.Happy to split this out into its own PR if preferred.
Validation
prek run --all-filesprek run basedpyright --all-filesuv run basedpyright(deps resolved)uv run pytestuv run ruff check .uv run ruff format --check .The hook was also verified to actually fail rather than silently pass in the sandboxed environment, by temporarily introducing a
reportReturnTypeviolation and confirming it was caught.No workflow files were modified, so no Zizmor audit was required.
Acceptance criteria
[tool.basedpyright]block present inpyproject.tomlbasedpyrighthook added, preserving existing config (skip:list, hook args and per-hook customizations left intact)prek run basedpyright --all-filesreports 0 errorsNote: this is independent of #166 (the GitPython/Dependabot security work) and branches from
main, so the two should not conflict.