Skip to content

ref(issues): Convert GroupType class config to ClassVar#113423

Merged
JoshFerge merged 3 commits into
masterfrom
joshferge/ref/classvar-grouptype
Apr 24, 2026
Merged

ref(issues): Convert GroupType class config to ClassVar#113423
JoshFerge merged 3 commits into
masterfrom
joshferge/ref/classvar-grouptype

Conversation

@JoshFerge

@JoshFerge JoshFerge commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

GroupType is never instantiated in production — subclasses declare their configuration as class-level attributes (type_id = 1001, slug = "foo", etc.) and the registry stores the classes themselves. Despite that, the base declared fields as @dataclass(frozen=True) instance attributes, so every access like group_type.type_id (where group_type: type[GroupType]) was really class-level access against an instance-level annotation.

Convert the fields to ClassVar[T], preserving the same defaults. Also convert ReplayGroupTypeDefaults.notification_config for the same reason.

Small behavior change

GroupType.__post_init__ was previously dead code — nothing instantiates GroupType, so the category-value validation never actually ran. Move the same validation to __init_subclass__ so it fires at subclass-definition time (earlier, and actually reachable). Updates:

  • test_grouptype.py::test_category_validation — now expects the ValueError at class-definition time.
  • test_validators.py — two tests were instantiating GroupType(...) to build a mock; since the base no longer takes fields as constructor args, convert them to subclasses of GroupType instead (which matches real production usage).

Why this PR exists

Prep for the mypy upgrade to 1.20.1 in #113419. 1.20.1 adds a new [misc] check, "Cannot access instance-only attribute on class object," and GroupType is the single biggest cluster of such errors in the repo. Splitting it out so the targeted reviewers only have to look at their own areas.

Safe under the current mypy (1.19.1).

Agent transcript: https://claudescope.sentry.dev/share/JFAQdL9BKvw8zOc8RROKUSNl-Xpuu8y4p4URoIp7DDk

`GroupType` is never instantiated in production: subclasses declare their
configuration as class-level attributes (`type_id = 1001`, `slug = "foo"`,
etc.) and the registry stores the classes themselves. Despite that, the base
declared fields as `@dataclass(frozen=True)` instance attributes, so every
access like `group_type.type_id` (where `group_type: type[GroupType]`) is
really class-level access.

Convert the fields to `ClassVar[T]`, preserving the same defaults. Also
convert the `ReplayGroupTypeDefaults` mixin's `notification_config` for the
same reason.

Behavior change: `GroupType.__post_init__` was previously dead code (nothing
instantiates `GroupType`), so category validation never actually ran. Move
the same validation to `__init_subclass__` so it fires at subclass-definition
time. Update the two tests that were constructing `GroupType(...)` (not a real
production usage pattern) to define subclasses and the category-validation
test to expect the failure at class-definition time.

Prep work for the mypy 1.20 upgrade (#113419) — safe under 1.19.1.

Agent transcript: https://claudescope.sentry.dev/share/8kuOPo_F7g0JelnKiHguToNZr0_y4AKNCOGNvydSZac
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 20, 2026
Isolate the TestBaseGroupTypeDetectorValidator registry. Defining
`TestGroupType(GroupType)` subclasses inside the tests now triggers
`__init_subclass__`, which registers on the global `grouptype.registry`.
Two tests both use `type_id=1`, so the second collides with the first.

Swap the module's registry for a fresh `GroupTypeRegistry()` in setUp,
matching the pattern in `BaseGroupTypeTest` (tests/sentry/issues/
test_grouptype.py).

Agent transcript: https://claudescope.sentry.dev/share/siSaRYJSAdoKlkFW34-zk5UlezkQnorq4y6bii-WA4o
@JoshFerge

Copy link
Copy Markdown
Contributor Author

@sentry review

@JoshFerge

Copy link
Copy Markdown
Contributor Author

bugbot review

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 10acb03. Configure here.

@JoshFerge
JoshFerge marked this pull request as ready for review April 24, 2026 14:51
@JoshFerge
JoshFerge requested review from a team as code owners April 24, 2026 14:51
@JoshFerge
JoshFerge requested a review from a team April 24, 2026 15:12
@JoshFerge
JoshFerge merged commit f7a4137 into master Apr 24, 2026
76 checks passed
@JoshFerge
JoshFerge deleted the joshferge/ref/classvar-grouptype branch April 24, 2026 15:15
JoshFerge added a commit that referenced this pull request Apr 24, 2026
Bumps the mypy pin to 1.20.1. Prep PRs for the bulk
class-level-attribute fixes have already merged; what remains here is
the pin bump, the `GroupType` ClassVar + `__init_subclass__` migration,
a handful of small typing cleanups, and targeted `# type: ignore`
comments for cases that will get proper fixes in the follow-up PRs
below.

### Prep PRs (already merged)

- #113422 — `ref(typing)`: `RegressionDetector` + `AttributeHandler` →
`ClassVar`
- #113423 — `ref(issues)`: `GroupType` → `ClassVar` + move validation to
`__init_subclass__`
- #113424 — `fix(typing)`: `group_status` args are `int`, not
`GroupStatus`
- #113427 — `test(typing)`: refetch via queryset instead of
`refresh_from_db` after narrowing
- #113428 — `test(typing)`: misc test-side type hint fixes

### What's in this PR

- Bump `mypy>=1.19.1` → `mypy>=1.20.1` (+ `uv.lock`)
- Drop the now-stale `kombu.*` entry from `[[tool.mypy.overrides]]`
- `src/sentry/issues/grouptype.py` — convert `GroupType` fields to
`ClassVar` and move the category-validation check from the dead
`__post_init__` into `__init_subclass__`. Plus the corresponding test
updates in `tests/sentry/issues/test_grouptype.py` and
`tests/sentry/workflow_engine/endpoints/test_validators.py`.
- Small typing fixes that don't change runtime behavior:
- `digests/backends/base.py` — widen options map to `Mapping[str, Any]`
- `discover/compare_timeseries.py` — heterogeneous mismatches dict typed
as `dict[int, dict[str, Any]]`
- `explore/endpoints/explore_saved_queries.py` — drop unused `# type:
ignore`
- `tests/sentry/incidents/test_logic.py` — stringify both sides of the
`target_identifier` comparison so mypy stops flagging it as unreachable
- Targeted `# type: ignore` on six files where the proper fix is being
landed as a follow-up PR (see below)
- `.github/workflows/scripts/bootstrap-snuba.py` — `# type:
ignore[arg-type]` on `int(workers_str)` (single-file mypy can't see
through `sys.exit`; follow-up can replace with `NoReturn`)

### Follow-up PRs (drafts, based on this branch)

Each one removes one of the `# type: ignore` comments and applies the
proper fix. They'll merge after this lands.

- #113927 — `fix(typing)`: narrow `organization_id` in `from_auth`
- #113928 — `fix(typing)`: guard None options in `ReconnectingMemcache`
- #113929 — `docs(typing)`: annotate the `save_team_assignments` ignore
with a FIXME
- #113930 — `ref(typing)`: drop redundant cast in trace metric search
type
- #113932 — `ref(typing)`: drop redundant cast on
`TraceMetric.metric_type`
- #113933 — `ref(typing)`: drop redundant casts in
`resolve_measurement_value`

### Performance

| version | cold | warm |
|---|---|---|
| 1.19.1 | 49.22s | 17.82s |
| 1.20.1 | 39.04s | 5.73s |

### CI expectation

- `mypy` job: clean (0 errors)
- `uv` jobs: resolve against `pypi.devinfra.sentry.io` (1.20.1 has been
published there)
@github-actions github-actions Bot locked and limited conversation to collaborators May 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants