Skip to content

Type check with pyrefly, and fix what it found - #23

Open
tomchop wants to merge 1 commit into
mainfrom
ci/pyrefly
Open

Type check with pyrefly, and fix what it found#23
tomchop wants to merge 1 commit into
mainfrom
ci/pyrefly

Conversation

@tomchop

@tomchop tomchop commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Adds pyrefly, and fixes everything it found. Follow-up to #22.

What it found

26 errors, all the same mistake: a dict literal fixes its value type from whichever key is set first, so a dict built up conditionally is inferred far more narrowly than it is used.

The worst was request_kwargs. It starts out holding headers, so it is inferred as dict[str, dict[str, Any]], and is then splatted into requests' typed parameters:

request_kwargs = {}                       # -> dict[str, dict[str, Any]]
if headers:
    request_kwargs["headers"] = headers
if json_data:
    request_kwargs["json"] = json_data    # already wrong
...
self.client.post(url, **request_kwargs)   # checked against every parameter

That one produced 18 of the 26 errors from three call sites — one per request method per typed parameter.

Six other dicts had the same shape (query, params built up conditionally). Annotating all seven as dict[str, Any] — which is what they are — clears every error.

Fixed rather than suppressed. No ignores, no ratchet: the annotations describe the code accurately, and the runtime is untouched. pyrefly check reports 0 errors and the 31 existing tests pass unchanged.

Setup

  • pyrefly as a dev dependency, pinned to ^1.2.0 (the current release).
  • [tool.pyrefly] in pyproject.toml, checking yeti and excluding tests, which leans on unittest.mock and would report noise about the tests rather than the library.
  • A Type check workflow on pull requests, mirroring the existing unittest one.

What this will and will not catch

Worth being explicit, because it is the reason #22 exists.

In CI, dependencies are installed, so requests resolves through its stubs and Session.verify is bool | str. A consumer that vendors this package and checks it against requests' own source sees bool instead — inferred from self.verify = True in Session.__init__, since requests ships no annotations — and reports the CA-bundle assignment as an error.

So this job would not have caught that one. It is fixed in #22 with a comment recording why, and I could not reproduce it here even with requests vendored, which suggests the downstream configuration differs further. If you can share that pyrefly config, matching it here would close the gap properly.

The package had no type checking of its own, so problems in it surfaced only
downstream. Pyrefly reported 26 errors; all are fixed rather than suppressed,
because all were the same mistake.

A dict literal fixes its value type from whichever key is set first, so a dict
built up conditionally is inferred far more narrowly than it is used. The worst
was request_kwargs: it starts by holding headers, is inferred as
dict[str, dict[str, Any]], and is then splatted into requests' typed
parameters -- which produced 18 of the 26 errors from three call sites.
Annotating the seven such dicts as dict[str, Any], which is what they are,
clears all of them.

Nothing about the runtime changes; the 31 existing tests pass unchanged.
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.

1 participant