Add pyright and pyrefly type checkers - #180
Conversation
|
🔍 OpenCodeReview found 8 issue(s) in this PR.
📄
|
| block: dict[str, Any] = { | ||
| "type": options.type, | ||
| "depth": depth, | ||
| "data": {}, | ||
| } |
There was a problem hiding this comment.
Using Any for the block variable type annotation bypasses the project's own Block TypedDict type. While dict[str, Any] may be technically correct for this synthesized object, using the project's Block type would provide consistency and better type safety.
Suggestion:
| block: dict[str, Any] = { | |
| "type": options.type, | |
| "depth": depth, | |
| "data": {}, | |
| } | |
| block: Block = { | |
| "type": options.type, | |
| "depth": depth, | |
| "data": {}, | |
| } |
| entity_map_no_mutability = cast( # ty: ignore[disjoint-cast] | ||
| EntityMap, {"2": {"type": "LINK", "data": {"url": "http://test.com"}}} | ||
| ) |
| """ | ||
|
|
||
| pr: cProfile.Profile = None # type: ignore | ||
| pr: cProfile.Profile = None # type: ignore[assignment] # ty: ignore[invalid-assignment] |
There was a problem hiding this comment.
The # ty: prefix is a typo — should be # type:. This appears to be a leftover from combining two type checker ignores. Keep only the correct # type: ignore[assignment, invalid-assignment] form.
Suggestion:
| pr: cProfile.Profile = None # type: ignore[assignment] # ty: ignore[invalid-assignment] | |
| pr: cProfile.Profile = None # type: ignore[assignment, invalid-assignment] |
| """ | ||
|
|
||
| pr: cProfile.Profile = None # type: ignore | ||
| pr: cProfile.Profile = None # type: ignore[assignment] # ty: ignore[invalid-assignment] |
There was a problem hiding this comment.
The # ty: prefix is a typo — should be # type:. This appears to be a leftover from combining two type checker ignores. Consolidate to a single correct directive: # type: ignore[assignment, invalid-assignment]
Suggestion:
| pr: cProfile.Profile = None # type: ignore[assignment] # ty: ignore[invalid-assignment] | |
| pr: cProfile.Profile = None # type: ignore[assignment, invalid-assignment] |
Description
All Python code is now type-checked by four checkers — mypy, ty, pyright, and pyrefly — and all four must pass in
just lintand CI. They implement the Python typing spec independently, so they disagree in useful ways: each caught real issues the others missed (see below), and staying compatible with all of them keeps the codebase's typing idiomatic rather than checker-specific.Checker setups
Each checker got the smallest setup that adds distinct value, with a consistent policy: annotations required on production code, tests exempt from annotation requirements but still checked.
[tool.mypy]):draftjs_exporter teststo.(exclude_gitignore = truekeeps generatedsite/out), matching the other checkers.extra_checks,strict_equality_for_none,warn_unused_configs, and the error codesignore-without-code,deprecated,possibly-undefined,truthy-bool,redundant-expr,redundant-self,mutable-override,unimported-reveal.explicit-overrideand ty'smissing-override-decorator— both requiretyping_extensionsas a new runtime dependency for Python 3.10 support (TODO in config);warn_redundant_castsstays off because ty requires casts mypy considers redundant;--native-parseris ~17% faster but too new for a CI gate.[tool.ty.rules]:unsound-assignment,unsound-return-statement,unsound-yield,disjoint-cast.[tool.pyright]):standardmode (itsstrictmode is unusable here — 14k+ errors from the intentionalElement = AnyDOM abstraction), withreportTypedDictNotRequiredAccessrelaxed fortests/only via an execution environment.[tool.pyrefly]): configured to mirror mypy's annotation requirements (implicit-any,unannotated-return), plusnot-required-key-access(matching pyright),treat-all-caps-as-final,unused-ignore, anddirect-abstract-base-instantiation; atests/**sub-config mirrors mypy's tests exemption.Add-on QA tooling
just verify-typesrunspyright --verifytypesto report the type completeness of the public API — currently 80.9%. The ceiling is intentional:Elementis aTypeAliasofAnyso all DOM engines can be used interchangeably, so this is informational (CI runs it withcontinue-on-error).typeguardruntime type checking in tests (slow, false positives withAny-typed engines), pyreflyopen-unpacking/redundant-cast(both conflict with casts that ty requires).Issues found and fixed
Surfaced by pyright:
EntityState.render_entitiesaccessedentity_details["type"]/["data"]on atotal=FalseTypedDict. TheEntityTypedDict now requirestype,data, andmutability, matching the Draft.js format. Runtime leniency is preserved: entities withoutmutabilitystill render with a mutability ofNone(draftjs_exporter/entity_state.py).BeautifulSoupwas "possibly unbound" in the html5lib engine — a vestigial 2017-eratry/except ImportErrorfrom when engines were imported eagerly. Engines load lazily by dotted path these days, so the module now importsbs4plainly like the lxml engine, and selecting the html5lib engine without BeautifulSoup installed fails with a clear import error rather than aNameErrorat render time (draftjs_exporter/engines/html5lib.py).MARKDOWN_CONFIGinexample.pyswitched to.get().list[EntityResolver]was expected (tests/markdown_parser/test_resolvers.py).Surfaced by ty's new soundness rules:
apply_decoratorswas declaredGenerator[str, None, None]but also yields DOM elements — nowGenerator[str | Element, None, None](draftjs_exporter/composite_decorators.py).list[Any]→list[str]assignment in the promptfoo grader, now an explicit cast (single capture group, sofindallreturns strings —docs/prompts/graders/run_snippet.py).benchmark.py(markov_draftjs has its own ContentState type declarations), suppressed with a targeted# ty: ignore[disjoint-cast].Callable[[None], None]annotation on generated tests, nowCallable[..., None](tests/test_exports.py).Surfaced by pyrefly's annotation checks:
-> Nonereturn annotations on__init__in the string and Markdown engines.Anyattributes (last_child,type,props) inWrapper, and an untyped empty container inWrapperState(draftjs_exporter/wrapper_state.py), plus an untyped empty container inresolvers.py.Documentation
docs/CONTRIBUTING.md: new "Static typing" section with an explainer of why the project runs four checkers and a table of each checker's role and config location; commands and CI lists updated.AGENTS.md: tools list updated, stalestubs/directory reference removed.EntityTypedDict typing change.Test evidence
just lint— all four checkers, ruff, and import-linter pass.just test— 734 passed.just test-compatibility— 734 passed (Python 3.10, lowest-supported dependency versions).just docs-build— strict build passes.just verify-types— type completeness score 80.9%.