Skip to content

Improve type hints for BaseTemporalField and JSONField in forms#3293

Open
01xnikhil wants to merge 11 commits into
typeddjango:masterfrom
01xnikhil:fix-forms-types
Open

Improve type hints for BaseTemporalField and JSONField in forms#3293
01xnikhil wants to merge 11 commits into
typeddjango:masterfrom
01xnikhil:fix-forms-types

Conversation

@01xnikhil
Copy link
Copy Markdown
Contributor

I have made things!

I noticed that some fields in forms/fields.pyi were still using Any for many important attributes, which makes type-checking less effective. I have updated these to be more specific.

What I changed:
In BaseTemporalField, I changed input_formats to Collection[str] | None. This helps when passing custom date/time formats.
In JSONField, I narrowed encoder and decoder from Any to their respective json module types (type[json.JSONEncoder], etc.).
I also noticed re_decimal in IntegerField was Any, so I've updated that to Pattern[str] as well.
Added a new test file tests/assert_type/forms/test_fields.py to make sure these new types actually work with assert_type.

Testing:
Ran Mypy on the modified files and the new test case, and both returned success.

AI Policy

  • I have read and agree to the AI Policy, removed any "Co-Authored-By" lines attributing coding agents, and manually reviewed the final result

@01xnikhil 01xnikhil force-pushed the fix-forms-types branch 3 times, most recently from e375c92 to 8541823 Compare April 11, 2026 09:07
Comment thread tests/assert_type/forms/test_fields.py
@01xnikhil 01xnikhil force-pushed the fix-forms-types branch 2 times, most recently from 0735208 to 1a064f3 Compare April 12, 2026 09:44
Comment thread django-stubs/forms/fields.pyi Outdated
min_value: int | Callable[[], int] | None
step_size: int | Callable[[], int] | None
re_decimal: Any
re_decimal: Pattern[str] | SimpleLazyObject[Pattern[str]]
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.

Does this align with how we type other regex created by the django util function _lazy_re_compile ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does this align with how we type other regex created by the django util function _lazy_re_compile ?

Yeah, it matches the existing patterns in the codebase. I checked django-stubs/utils/functional.pyi and saw that SimpleLazyObject is a Generic[_T]. Since _lazy_re_compile wraps the compiled pattern, using Pattern[str] | SimpleLazyObject[Pattern[str]] makes sure Mypy is happy whether the object is still lazy or already evaluated. It’s consistent with how we've handled other lazy objects here.

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.

Looking at usage in django of _lazy_re_compile, i find these for ex that are annotated like so:

RFC1123_DATE: Pattern[str]
RFC850_DATE: Pattern[str]
ASCTIME_DATE: Pattern[str]

long_open_tag_without_closing_re: SimpleLazyObject

word_split_re: Pattern[str] | SimpleLazyObject

cc_delim_re: Any  # Any: SimpleLazyObject wrapping a compiled regex

So you proposal would be yet another way of expressing the same idea.
What is the correct version ? Can we pick one and use it in every places ?

Given the typing of _lazy_re_compile, I think we should use Pattern everywhere because it reflects the runtime behavior even if it's not 100% correct.

def _lazy_re_compile(regex: _P | Pattern[_P], flags: int = 0) -> Pattern[_P]: ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looking at usage in django of _lazy_re_compile, i find these for ex that are annotated like so:

RFC1123_DATE: Pattern[str]
RFC850_DATE: Pattern[str]
ASCTIME_DATE: Pattern[str]

long_open_tag_without_closing_re: SimpleLazyObject

word_split_re: Pattern[str] | SimpleLazyObject

cc_delim_re: Any  # Any: SimpleLazyObject wrapping a compiled regex

So you proposal would be yet another way of expressing the same idea. What is the correct version ? Can we pick one and use it in every places ?

Given the typing of _lazy_re_compile, I think we should use Pattern everywhere because it reflects the runtime behavior even if it's not 100% correct.

def _lazy_re_compile(regex: _P | Pattern[_P], flags: int = 0) -> Pattern[_P]: ...

Thanks for the guidance, I totally agree—sticking with Pattern[str] for consistency makes the most sense.

I have my college minor exams until April 18th, so I will be back on the 18th evening to update the PR and standardize the other occurrences.

Thanks for understanding.

Comment thread tests/assert_type/forms/test_fields.py
@01xnikhil
Copy link
Copy Markdown
Contributor Author

Hello 👋 @UnknownPlatypus ,
Updated all three files to Pattern[str] as you said.
But stubtest is failing because may be these are SimpleLazyObject at runtime. Should I wrap them in SimpleLazyObject[Pattern[str]] or just add a # type: ignore for now?

Let me know what's better !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants