Repository navigation
Conversation
Under `mypy --strict` (`disallow_untyped_calls`), every call to an unannotated function is an error in the *caller*. Since 3.3.3 ships `py.typed`, a strict project gets one error per declaration it writes: `factory.Sequence(...)`, `factory.SubFactory(...)`, `factory.Trait(...)`, and so on, plus `untyped-decorator` on `@factory.post_generation`, `@factory.lazy_attribute` and friends. Annotate what users *call*: - the constructors of every declaration re-exported by `factory` (and `factory.django.Password`, `factory.Faker`); - the helper functions and decorators of `factory.helpers`; - the public classmethods of `BaseFactory` (`stub`, `generate`, `simple_generate`, `reset_sequence`, ...); - `factory.django.mute_signals`, so that decorating a function keeps its signature. Deliberately left out: the `_`-prefixed extension points and `evaluate()`/`call()`, which users *override*; annotating them would make mypy check user overrides for compatibility, i.e. new errors downstream. Constructors are exempt from that check, which is why this change cannot produce an override error in user code. The type of a declaration expression does not change: mypy already inferred `factory.Sequence(...)` as `Sequence` before this change; the only new information is the argument types. `tests/test_typing.py` is now also checked with `--strict --follow-imports=silent`, i.e. the way a downstream project sees factory; `type: ignore` comments on wrong calls act as negative tests through `warn_unused_ignores`. `--implicit-reexport` keeps that check independent from the `__all__` discussion (FactoryBoy#1114). `tests/test_typing_unannotated.py` holds a factory written as the docs show it, without annotations, and is checked by both mypy runs: the new annotations must not turn documented code into errors (FactoryBoy#1113). It is why `@factory.sequence` accepts `Callable[[Any], Any]`: on an unannotated method mypy types `n` as the factory class, so `Callable[[int], Any]` rejected `def username(n): ...`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Refs #1113, #1131. Complements #1114 (
__all__), does not depend on it.Problem
Since 3.3.3 ships
py.typed, a project runningmypy --strict(disallow_untyped_calls)gets one error per declaration it writes, because the declarations' constructors are
unannotated:
(the first one was reported in #1113). On a real Django project with ~30 factory modules
this is 138 errors, all of them about factory_boy rather than about the project; the usual
workaround is
disallow_untyped_calls = falsefor the whole package.Change
Annotate what users call:
factory(Sequence,SubFactory,LazyAttribute,Trait,Maybe,RelatedFactory,PostGeneration, ...),factory.Fakerandfactory.django.Password;factory.helpers:build/create/... and the decorators (post_generation,lazy_attribute,sequence, ...);BaseFactory(stub,generate,simple_generate,reset_sequence, ...);factory.django.mute_signals, so that a decorated function keeps its signature.What users override (
evaluate,call,_create,_build,_adjust_kwargs, ...) is leftuntouched on purpose: annotating it would make mypy check user overrides for compatibility.
Why this should not break anyone (cf. #1113)
factory.Sequence(...)asSequence; only the arguments are now checked.__init__overrides for compatibility, so subclasses of declarationsare not affected.
tests/test_typing_unannotated.pytype-checks the documented, unannotated style(
@factory.sequence def username(n): ...on a factory). This is why thesequencedecorator accepts
Callable[[Any], Any]: on an unannotated method mypy typesnas thefactory class, and
Callable[[int], Any]would have reported a new error there.Tests
make testnow also runsmypy --strict --implicit-reexport --follow-imports=silent tests/test_typing.py tests/test_typing_unannotated.py,i.e. sees factory the way a strict downstream project does.
--implicit-reexportcan bedropped once Explicitly export values on factory namespace #1114 lands.
tests/test_typing.pybuilds a factory using every annotated declaration (also checked atruntime), and contains wrong calls marked
# type: ignore[...]: under--strict,warn_unused_ignoresfails if an annotation stops rejecting them.factory/, the strict run reports 27 errors on these tests; with this change, 0.Out of scope (happy to follow up)
UserFactory()still returnsUserFactoryfor type checkers (Support type hinting? #468, typing: fix BaseFactory callable return type for parameterized generics. (Works in VSCode only) #1137).Sequence[str], as intypes-factory-boy),factory.fuzzy,Faker.add_provider/override_default_locale, generic ORM factories (Add missing generic onFactorysubclasses #1060, feat: Add generic typing to factories #1100).mypy factoryreportsfactory/random.py:7: "Random" has no attribute "state_set"on master; CI doesn't see it because
tests/test_typing.pydoesn't importfactory.random.🤖 Generated with Claude Code