Repository navigation
ref(typing): Type request.user with service accounts outside an allowlist - #129402
Merged
Merged
Conversation
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
October 31, 2026 01:29
54aacbe to
a6954ca
Compare
gricha
changed the base branch from
ref/principal-helper-examples
to
ref/sa-narrow-widen
November 1, 2026 04:24
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 1, 2026 04:24
a6954ca to
8d3da3d
Compare
This was referenced Nov 1, 2026
Contributor
Sentry Snapshot Testing
|
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 1, 2026 04:57
8d3da3d to
9be4ce4
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 19:55
9be4ce4 to
c5539a0
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 20:14
c5539a0 to
4ed979e
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 20:29
4ed979e to
807eec6
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 20:33
807eec6 to
51fe411
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 20:35
51fe411 to
63a1150
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 21:03
63a1150 to
293e9f4
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 23:31
413cb8c to
1a545ff
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 2, 2026 23:42
1a545ff to
16df09d
Compare
gricha
added a commit
that referenced
this pull request
Nov 3, 2026
The helpers that endpoints hand `request.user` to now take a `RequestPrincipal`: feature gates (`get_excluded_detector_types`, `has_investigations_access`, `should_serve_action_log_activity`), Seer agent access (`SeerAgentClient`, `has_seer_agent_access_with_detail`, `collect_user_org_context`, `commit_author_for_user`), `is_demo_user`, `has_staff_option`, `TeamManager.get_for_user` and the group activity serializers. - Most only pass the caller on to `features.has()` or `serialize()`, or already check `has_user()`: type-only. - `SeerAgentClient` reads the user id once through `acting_user_id()` instead of six `hasattr(self.user, "id")` checks. `is_demo_user`, `collect_user_org_context` and `commit_author_for_user` use `acting_user_id()` / `has_user()`. Users, agents and token callers get the same values as before. A service account now gets no user (no `user_id` on its Seer runs, org context without personal teams) instead of a `ServiceAccountId` that 403s when used as an int. Without it, #129402's mypy check would flag 14 more files.
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
2 times, most recently
from
November 3, 2026 00:34
d978051 to
ca074d2
Compare
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
2 times, most recently
from
November 3, 2026 04:41
0bc63b8 to
8580c43
Compare
cvxluo
approved these changes
Nov 3, 2026
| @property | ||
| def id(self) -> ServiceAccountId: | ||
| return ServiceAccountId(self.service_account_id) | ||
| # Hidden from mypy so request.user.id needs narrowing; kept at runtime as a guard. |
Contributor
There was a problem hiding this comment.
should we just delete this? seems a little insane to keep it in runtime but not in typing
Member
Author
There was a problem hiding this comment.
removing at the top of the stack
gricha
force-pushed
the
ref/mypy-request-user-principal
branch
from
November 3, 2026 19:53
8580c43 to
c39801d
Compare
gricha
changed the base branch from
ref/sa-narrow-widen
to
ref/sa-typed-helpers
November 3, 2026 19:53
…list request.user on a DRF Request can be a ServiceAccountPrincipal, but mypy didn't know, so nothing forced callers to narrow. The mypy plugin now types it as User | AnonymousUser | ServiceAccountPrincipal, except in the 23 files on tools/mypy_helpers/request_user_allowlist.txt, which keep today's type. ServiceAccountPrincipal.id is hidden from mypy but still raises UserRequired at runtime. A test fails if the allowlist grows or lists a deleted file.
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.
mypy now types DRF
request.userasUser | AnonymousUser | ServiceAccountPrincipal, except in files ontools/mypy_helpers/request_user_allowlist.txt. Those keep today's type, so mypy stays green, while every other file must narrow before it reads user-only attributes.rest_framework.request.Request.userreturns the union outside the allowlist.report_config_datarechecks a cached module when it joins or leaves the list; the list is re-read when the file changes, so a runningdmypyuses the current list for any file it rechecks. dmypy doesn't recheck an unchanged file just because it joined or left the list; touch the file or restart the daemon.tools -> ../sentry/toolssymlink, and without this its typing job failed with 10 errors in 5 files.test_drf_request_user_keeps_its_type_outside_sentryruns mypy the way getsentry does.ServiceAccountPrincipal.idis hidden from mypy (if not TYPE_CHECKING) but kept at runtime, soServiceAccountIdstill guards unconverted reads.features.batch_has(3) and 11 one-offs. A test fails if the list grows or names a deleted file, and another fails if the hook stops applying.No runtime change. It lands after the fixes: the PRs below narrow the rest, and each later fix removes its files from the list.