Skip to content

feat: add pyright script for type checking with configuration - #1123

Closed
JithinBathula wants to merge 3 commits into
graphistry:masterfrom
JithinBathula:pyright-local-runner
Closed

JithinBathula wants to merge 3 commits into
graphistry:masterfrom
JithinBathula:pyright-local-runner

Conversation

@JithinBathula

Copy link
Copy Markdown
Contributor

Summary

Adds pyright configuration and local runner script to catch conditionally-assigned variable bugs (like edge_map at hop.py:974) that ruff and mypy miss. No code changes — tooling only.

Non-goals: CI integration, code fixes, rule enforcement — all follow-up PRs.

Validation

  • Local lint/type/tests run for touched scope
  • CI is green

bin/pyright.sh graphistry/compute/hop.py confirms hop.py:974:122 - warning: "edge_map" is possibly unbound

Cypher Frontend CI Evidence (when PR touches cypher frontend / IR scope)

  • cypher-frontend-strict-typing (py3.12) passed (strict typing gate)
  • cypher-frontend-differential-parity (py3.12) passed (trust-but-verify gate)
  • cypher-frontend-ci-gates passed (includes test-minimal-python sentinel)
  • PR body includes links/screenshots/log snippets for any non-obvious gate evidence

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5842d1e6dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bin/pyright.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16c3f4e261

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread bin/pyright.sh Outdated
Comment on lines +10 to +13
elif command -v uvx >/dev/null 2>&1; then
PYRIGHT_CMD_ARR=(uvx --from pyright pyright)
elif command -v npx >/dev/null 2>&1; then
PYRIGHT_CMD_ARR=(npx pyright)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall through to next backend when uvx invocation fails

Selecting the backend solely by command -v means the script commits to uvx whenever it is installed, and with set -e it exits on the first uvx failure without ever trying npx. This breaks type-check runs in environments where uvx is present but cannot execute pyright (for example, restricted package access) while npx pyright is still usable, so the intended fallback chain is not actually resilient.

Useful? React with 👍 / 👎.

@lmeyerov

Copy link
Copy Markdown
Contributor

The autocomment on flipping precedence order seems right, it'd match the other shell scripts here. Can you change? Thanks!

@lmeyerov lmeyerov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! See #1123 (comment) and then we can merge

@lmeyerov

Copy link
Copy Markdown
Contributor

@codex I agree with your review below, please address

P2 Badge Fall through to next backend when uvx invocation fails

Selecting the backend solely by command -v means the script commits to uvx whenever it is installed, and with set -e it exits on the first uvx failure without ever trying npx. This breaks type-check runs in environments where uvx is present but cannot execute pyright (for example, restricted package access) while npx pyright is still usable, so the intended fallback chain is not actually resilient.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lmeyerov

Copy link
Copy Markdown
Contributor

@codex address that feedback

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@JithinBathula
JithinBathula requested a review from lmeyerov April 13, 2026 04:03
@JithinBathula

Copy link
Copy Markdown
Contributor Author

Updated the script based on the comment. Thanks

lmeyerov added a commit that referenced this pull request Sep 17, 2026
* ci(pyright): gate on the rules a file decides for itself

Pyright catches what ruff and mypy miss: locals bound on only some paths,
names that resolve nowhere, statements with no effect. Running it is easy;
gating on it is not, because most of its rules read third-party stubs.

Measured over five environments (python 3.8/3.11/3.12/3.14, and a workstation
carrying polars, cudf, scipy and scikit-learn) on one unchanged tree,
reportAttributeAccessIssue ranges from 146 to 810 findings, reportReturnType
from 2 to 78. A gate on those fails for whoever has cudf installed and passes
in CI. So only five rules gate -- the ones decided by a file's own control
flow, names and syntax -- and the rest are reported but never enforced.

Agreement across environments was necessary but not sufficient evidence:
reportOptionalSubscript, reportTypedDictNotRequiredAccess and reportIndexIssue
all matched across four environments and then diverged on the fifth, being rare
enough to agree by luck. The admission bar is the principle, not the sample.

- bin/pyright.sh pins pyright 1.1.414, and uses a local install only when it
  is that version; an unpinned tool moves the baseline underneath us.
- bin/ci_pyright_guard.py ratchets per-file counts against
  bin/ci_pyright_baseline.json, the same shape as the type-hygiene and
  comment-density guards: counts may shrink, never grow, and a file absent
  from the baseline must be clean.
- python-pyright runs it on py3.12 only. All seven lint matrix cells would
  produce byte-identical output for these rules.

273 findings are grandfathered. 195 of them are one module that builds its
namespace with globals().update(vars(...)) and already carries
`# mypy: ignore-errors` and `# ruff: noqa: F821`; its count is left visible
rather than excluded so that fixing it shows up as slack under --strict.

Builds on #1123 by Jithin Bathula, whose bin/pyright.sh and pyrightconfig.json
this keeps. Closes #1075.

Co-Authored-By: Jithin Bathula <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud

* ci(pyright): fail on a collapsed gate and on files that do not parse

Review of the previous commit found the ratchet could go quiet without anyone
noticing, which defeats its purpose.

Scope lives in pyrightconfig.json, a different file from the baseline, so
widening an exclude silenced the gate with no baseline change and no failure:
appending "graphistry/compute" to exclude dropped the gated findings from 273
to 56 and still exited 0 -- while printing "10 file(s) now below baseline". The
guard observed the collapse and passed anyway. Shrinking findings and shrinking
scope are indistinguishable from counts alone, so the baseline now records the
file count it was built over and a run that sees materially less fails. A real
scope change is still fine; it just has to go through --update-baseline, where
the delta shows up in review.

Pyright reports a file it cannot parse with no rule at all, so those findings
were routed to the ungated pile and could never fail. An unparseable file also
yields fewer gated findings, so it read as an improvement. Parse failures
consult no type either, so they are gated under <unparseable> and must stay at
zero; the tree has none today.

Also aligns Finding with the sibling guard's `@dataclass(frozen=True)` and
real annotations rather than a hand-rolled __slots__ class and `# type:`
comments.

Verified: exclude-widening and a fabricated zero-file report now exit 1; an
unparseable file exits 1 naming it; the injected possibly-unbound local still
exits 1 and reverts to 0; one baseline still passes in all five environments.
19 tests, 13-mutant battery -- 12 caught, the 2 survivors inert alone and
caught in combination.

Co-Authored-By: Jithin Bathula <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud

* docs(develop): trim the pyright ratchet section to the size of its siblings

63 lines re-derived the environment measurement in prose; that argument lives
in the PR and does not need repeating for someone reaching for the commands.
42 lines now, next to Type Hygiene at 39 and Comment Density at 43.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017ropeBMLJUuy6ViYwy15ud

---------

Co-authored-by: Jithin Bathula <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
@lmeyerov

Copy link
Copy Markdown
Contributor

Thank you for this, @JithinBathula — and apologies it sat open so long.

Your work has landed. bin/pyright.sh and the pyrightconfig.json changes shipped essentially as you wrote them in #2092, with you as Co-Authored-By on the commit. The resolution order you set up (local pyright → uvx → npx) is still there, and it turned out to matter more than it looked: when I later tested a broken include in the config, the explicit CLI target your script passes was the thing that kept pyright analysing the right 351 files. I nearly "simplified" it away before measuring.

Two things had to be settled before it could gate, both of which you'd flagged as non-goals:

The tool needed pinning. The version is now fixed at [email protected] in both fetching fallbacks, because a ratchet baseline is only meaningful against one version — upstream adds and retunes rules between releases.

The rules needed sorting by whether they're reproducible. Running your config across five environments (python 3.8/3.11/3.12/3.14, and a workstation with polars + cudf + scipy + scikit-learn) showed most pyright rules move with the environment: reportAttributeAccessIssue ranged from 146 to 810 findings on the same tree. Only rules decided by a file's own control flow, names and syntax were stable — so those five gate, and the rest are reported but never enforced. reportPossiblyUnboundVariable, the one tightening in your config, is among the five, and it's the reason the gate exists.

It's now a real CI job (python-pyright) holding findings to a per-file ratchet, so existing debt is grandfathered and new code isn't.

And it found real bugs. The hop.py "edge_map is possibly unbound" you cited in your description is exactly the class of thing it catches. Follow-up PRs using it fixed, among others:

The ratchet went from 273 findings to 230 across that series. It started with your PR.

Closing this one as superseded, not rejected — the code is in master. Thanks again, and sorry for the wait on review.

@lmeyerov

Copy link
Copy Markdown
Contributor

Superseded by #2092, which carries this work with attribution. See the comment above for where each piece landed.

@lmeyerov lmeyerov closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants