Skip to content

Check type stubs against compiled module, fix detected drifts - #286

Merged
benoit74 merged 3 commits into
mainfrom
stubtest
Oct 26, 2026
Merged

benoit74 merged 3 commits into
mainfrom
stubtest

Conversation

@benoit74

Copy link
Copy Markdown
Contributor

The .pyi stubs are hand-written and drift from libzim.pyx unnoticed (see the stale Creator.add_metadata stub fixed in #285). This adds two checks to QA (also part of inv checkall):

  • inv check-stubtest: mypy.stubtest compares the stubs with the compiled module: missing/extra objects, parameter names, kinds and defaults. pyright has no equivalent (it never imports the runtime module); mypy is only used as stubtest engine, pyright stays the type checker.
  • inv check-stub-annotations: stubtest does not compare annotation types, but Cython keeps the .pyx annotations on compiled functions (__annotations__). scripts/check_stub_annotations.py compares them with the stubs ones, after normalizing equivalent spellings (pathlib.Path/Path, pyint/int, Self, forward references, exported aliases like WritingBlob/Blob, Generator default parameters). Annotations missing on either side are ignored. It reports the Upgrade dependencies #285 add_metadata drift when run against the old stub.

Known blind spot: constructors of Cython extension types take their arguments in __cinit__, and their runtime __init__ is a slot wrapper with an (self, /, *args, **kwargs) signature, so neither check can verify constructor signatures. Those are allowlisted in scripts/stubtest-allowlist.txt (with Cython auto-generated pickling methods); constructor stubs stay covered by tests, which are type-checked by pyright.

Drifts found and fixed:

  • libzim.suggestion did not export SuggestionSearch and SuggestionResultSet at runtime, while stubs declared them (from libzim.suggestion import SuggestionSearch passed type checking but raised ImportError)
  • reader.set_cluster_cache_max_size parameter is size_in_bytes at runtime, nb_clusters in the stub
  • IllustrationInfo(extra_attributes=None) is accepted at runtime, but stub said Mapping[str, str] = {}; the stub now mirrors the runtime structure (NamedTuple base + __new__) so the attribute stays typed as never None
  • enum members were declared as name: Self, which type checkers no longer treat as members
  • .pyx annotations: implicit Optional (width: pyint = None) and Creator.config_*() annotated as returning _Creator instead of Self
  • stubs now declare __all__ and mark Cython extension classes with @disjoint_base (PEP 800, imported from typing_extensions in stubs only)

Tests added for each runtime-visible fix (they fail at runtime, or under pyright, before this change).

Unrelated bug fixed on the way: create_module reused its name argument as loop variable, so each submodule was also registered in sys.modules under its last member name (sys.modules["Searcher"], sys.modules["SuggestionSearcher"], ...).

@benoit74 benoit74 self-assigned this Oct 25, 2026
@benoit74
benoit74 requested a review from rgaudin October 25, 2026 21:27
@codecov

codecov Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.70%. Comparing base (75aefab) to head (22e17c4).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #286      +/-   ##
==========================================
- Coverage   95.11%   93.70%   -1.42%     
==========================================
  Files           1        1              
  Lines         635      635              
==========================================
- Hits          604      595       -9     
- Misses         31       40       +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rgaudin rgaudin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM ; good catch (didn't review the script)

…ution

SuggestionSearch and SuggestionResultSet were declared in the type stubs
but not exported by libzim.suggestion at runtime, so importing them failed.

create_module reused its name argument as loop variable, so each submodule
was also registered in sys.modules under its last member name (e.g.
sys.modules['Searcher']).
- set_cluster_cache_max_size parameter is size_in_bytes, not nb_clusters
- IllustrationInfo accepts extra_attributes=None
- declare enum members with values so type checkers see them as members
- declare __all__ and mark Cython extension classes as disjoint bases
- fix .pyx annotations: implicit Optional, and config methods returning Self
- mypy.stubtest checks names, parameters and defaults
- check_stub_annotations compares stubs annotations with the ones Cython
  keeps on compiled functions, which stubtest does not check
@benoit74
benoit74 merged commit f3ed793 into main Oct 26, 2026
68 of 70 checks passed
@benoit74
benoit74 deleted the stubtest branch October 26, 2026 16:35
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