Skip to content

feat(apidocs): Let serializers declare the type of validated_data - #123923

Merged
azulus merged 2 commits into
masterfrom
jeremy/input-rules-3-typed-validated-data
Sep 9, 2026
Merged

feat(apidocs): Let serializers declare the type of validated_data#123923
azulus merged 2 commits into
masterfrom
jeremy/input-rules-3-typed-validated-data

Conversation

@azulus

@azulus azulus commented Sep 9, 2026

Copy link
Copy Markdown
Member

Handlers read request input through serializer.validated_data, which is typed Any. Nothing connects it to the serializer's own fields, so a misspelled key survives review and fails at runtime.

This vendors rest_framework/serializers.pyi with one change: BaseSerializer takes a second type parameter for the shape of validated_data, threaded through Serializer and ListSerializer. A validator opts in by declaring Serializer[Any, MonitorData], after which validated_data["nmae"] is a type error and **validated_data is checked against the callee's keywords.

Both type parameters default to Any, so the 219 unparameterized subclasses and the 171 already passing a single argument are untouched, and the repository's error count does not move. Generated schema output is byte-identical, because drf-spectacular reads field objects rather than type arguments.

Request[T] was the obvious alternative and does not work. It types request.data, which is not the path most handlers take, so renaming a key read out of validated_data still goes uncaught.

Declare keys NotRequired. DRF omits absent keys under partial=True, so a shape claiming a key is present would be wrong at every such call site, and a shape that is confidently wrong is worse than no shape at all. Verifying requiredness needs a field's call arguments, which mypy discards once a class crosses a module boundary, so that is left to a later change.

@azulus
azulus force-pushed the jeremy/input-rules-3-typed-validated-data branch from 9233555 to 0791294 Compare September 9, 2026 17:19
@azulus azulus changed the title feat(mypy): Give serializers a typed validated_data feat(apidocs): Type validated_data against a declared shape Sep 9, 2026
@azulus
azulus marked this pull request as ready for review September 9, 2026 17:26
@azulus
azulus requested a review from a team as a code owner September 9, 2026 17:26
Comment thread tests/tools/mypy_helpers/test_typed_validated_data.py
@azulus
azulus force-pushed the jeremy/input-rules-3-typed-validated-data branch from 0791294 to a031b4a Compare September 9, 2026 17:56
@sentry

sentry Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Sentry Snapshot Testing

Name Added Removed Changed Renamed Unchanged Skipped Status
sentry-frontend
sentry-frontend
0 0 0 0 481 0 ✅ Unchanged

⚙️ sentry-frontend Snapshot Settings

azulus and others added 2 commits September 9, 2026 12:31
Handlers read input through serializer.validated_data, which is Any, so nothing
relates it to the serializer's fields and a misspelled key fails at runtime
rather than in review. Request[T] does not help: it types request.data, which
is not the path most handlers take.

Vendors rest_framework/serializers.pyi with one change. BaseSerializer takes a
second type parameter for the shape of validated_data, threaded through
Serializer and ListSerializer, so a validator can opt in with
Serializer[Any, MonitorData] and validated_data["nmae"] becomes an error.

Both parameters default to Any, so the 219 unparameterized subclasses and the
171 already passing one argument need no edit. Verified against the whole tree:
46 errors with the stub and 46 without, all of them in an untracked scratch
directory and none in sentry source. Schema output is byte-identical.

Keys are declared NotRequired because DRF omits absent keys under partial=True,
so a shape claiming a key is present would be wrong at those 46 call sites.
Checking requiredness needs the field's call arguments, which mypy discards
across module boundaries, and is left to a later change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review raised that these cases run mypy on a file outside the project tree, so
a stub that stopped being found would leave validated_data as Any and every
assertion would pass without checking anything.

The suite is not vacuous today: removing the stub fails all six cases, because
each one asserts a specific diagnostic rather than silence. But that safety was
incidental, so state it directly. The new case returns a str-typed key as int,
which errors only when the stub is in effect and passes when validated_data
falls back to Any.

Keeping the repo's own config rather than a local one, since resolving the stub
the same way CI does is the point of running it this way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@azulus
azulus force-pushed the jeremy/input-rules-3-typed-validated-data branch from a031b4a to c7baa17 Compare September 9, 2026 19:34
@azulus azulus changed the title feat(apidocs): Type validated_data against a declared shape feat(apidocs): Let serializers declare the type of validated_data Sep 9, 2026
@azulus
azulus merged commit 808dd67 into master Sep 9, 2026
66 checks passed
@azulus
azulus deleted the jeremy/input-rules-3-typed-validated-data branch September 9, 2026 21:07
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