Skip to content

fix(api): Reverse previous-page ordering for descending paginators - #129615

Draft
sentry-junior[bot] wants to merge 3 commits into
masterfrom
josh/typing-stack/paginator-prev-page-order-by
Draft

sentry-junior[bot] wants to merge 3 commits into
masterfrom
josh/typing-stack/paginator-prev-page-order-by

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Stacked on #129614 (strict mypy, types only). This is #127799 rebased onto it so the fix lands after the typing PR.

BasePaginator.build_queryset reverses the queryset ordering when a previous-page cursor needs the opposite direction. In the branch where the queryset is already ordered by -key and ascending order is needed, the result was assigned to queryset.query.order_b, a typo for order_by. The queryset stayed descending while the cursor filter was built for ascending order, so the previous page returned the wrong rows. It now assigns order_by.

#129614 keeps the typo behind # type: ignore[attr-defined]. This PR removes that ignore.

Regression test: test_prev_page_with_explicit_descending_queryset.

via @joshuarli.

--

View Session [Sentry]

sentry-junior Bot and others added 2 commits November 3, 2026 00:12
Turn on `strict = true` for the whole codebase and empty the mypy
weaklist. This change is types only and keeps master's runtime
behavior. Behavior fixes land as separate PRs on top.

- `strict = true` with `warn_return_any = false`. There are no
  `return cast(...)` calls or runtime guards that exist only to satisfy
  `no-any-return`.
- Where strict typing and master behavior conflict, master wins: use a
  cast, `Any`, or a scoped `# type: ignore[...]`, for example the
  paginator `order_b` typo that #127799 fixes.
- The `get_mapping`/`post_mapping`/`get_sequence` client helpers are
  pure casts and never raise for an unexpected shape.
- `Bounded*Field` stay bare subclasses (`# type: ignore[type-arg]`), so
  call sites don't need `[int | None, int | None]` subscripts.

Co-Authored-By: Joshua Li <14209781+joshuarli@users.noreply.github.com>
Co-Authored-By: Joshua Li <14209781+joshuarli@users.noreply.github.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Nov 3, 2026
@sentry-junior
sentry-junior Bot force-pushed the josh/typing-strict-types-only branch from 9fda5d0 to 73fa505 Compare November 3, 2026 00:31
joshuarli added a commit that referenced this pull request Nov 3, 2026
…pes only) (#129614)

This is the types-only version of #129447. It turns on `strict = true`
for the whole codebase and empties the mypy weaklist. It keeps **today's
master runtime behavior**. The behavior fixes that were folded into
#129447 come back as separate PRs stacked on this branch.

**Changes from #129447**
- `strict = true` with `warn_return_any = false`. The `no-any-return`
errors were most of the work, and fixing them added `return cast(...)`
calls, `isinstance`/`raise TypeError` guards, and `# justification:`
comments. Those are gone.
- Where strict types and master behavior conflict, master behavior stays
and the types give way: a `cast`, an `Any`, or a scoped `# type:
ignore[...]`. Examples:
- The paginator `order_b` typo is still there behind `# type:
ignore[attr-defined]`. #129615 fixes it.
- Fallbacks like `or 0`, coercions with `str()`/`int()`/`bool()`,
defensive copies that changed in-place mutation, and serializer output
are back to master.
- `get_mapping`, `post_mapping` and `get_sequence` in
`shared_integrations/client/base.py` are pure casts. They no longer
raise `ApiError` when a response has an unexpected shape.
- `Bounded*Field` are bare subclasses again, with `# type:
ignore[type-arg]` on 7 class lines. The `[int | None, int | None]`
subscripts are gone from call sites. These fields type-check as `Any`,
as they do on master.
- These are removed and can come back as separate PRs if we still want
them: `check_typing_campaign.py` with its hook and tests, the weaklist
sort tooling, the `.github/workflows/pre-commit.yml` rewrite, and the
`inviteStatus` nullability change in
`static/app/types/organization.tsx`.
- I tried `allow_redefinition = true`. Under mypy 2.1 it adds about 150
errors in existing code, so it's off and the variable renames stay.

**Rebased on master** (`e39c07b6`). Conflicts were resolved in master's
favor, with types added on top. Master code added since the old base
needed a few more stub entries in `fixtures/stubs-for-mypy`
(`PGAccessExclusive`/`PGLock`, two selenium exceptions,
`WebDriverWait.until` accepting `Literal[False]`) and annotations on a
handful of new tests.

**Deliberately left in**
- About 95 `assert x is not None` lines for type narrowing. I haven't
checked each one against master behavior.
- `UNBOUNDED_DATETIME = cast("datetime", None)` in
`tests/snuba/search/test_backend.py`. Widening `snuba_search` to
`datetime | None` spreads into `run_eap_group_search` and
`_prepare_params_for_categories`.

**Stack** (all based on this branch, land after it):
- #129615: paginator `order_by` typo (was #127799)
- #129616: msteams missing conversation id (was #129427)
- #129617: blank origin values (was #129428)
- #129618: release serializer defaults (was #129429)
- #129619: jira query required (was #129430)
- #129620: artifact manifest JSON shape (was #129432)
- #129621: dashboard threshold validation (was #129434; now accepts
decimal maximums)
- #129622: non-string debug ids (was #129435)
- #129623: non-string sourcemap headers (was #129436)
- #129624: blank allowed domains (was #129438; no longer splits strings
into characters)

#129433 isn't restacked. Master's `owned_by_acting_user()` already
returns `Q(pk__in=[])` for anonymous users, so those queries are skipped
there.

**No getsentry change needed.** getsentry's `backend typing` flagged 7
errors against an earlier push of this branch. Each one was a Sentry
signature this PR had made narrower than master's contract, so they're
fixed here, not in getsentry:
-
`BaseApiClient.request`/`get`/`get_cached`/`post`/`put`/`patch`/`delete`/`head_cached`
return `Any` again, as on master. The `get_mapping`/`get_sequence`
helpers keep their typed returns.
- `OAuth2Provider.get_oauth_client_id()` returns `Any`. getsentry
overrides it with an option typed `str | int`.
- `AbstractFile.getfile()` returns `Any`. getsentry's uploader treats it
as a `BufferedIOBase`.
- `BidirectionalMapping` is generic in its values
(`BidirectionalMapping[KT, VT]`), so lookups infer `str` instead of
`object`.
- `BitField` subclasses `BigIntegerField[Any, Any]`, so
`subscription.flags.<name>` resolves the way it does on master.

The `BitField` fix also restores two master behaviors the earlier push
had changed: plain string flags are added to `labels` again, and
`_arg_flags` holds the normalized flag list again, which is what
`deconstruct()` writes into migrations.

getsentry/getsentry#22868 is no longer needed for this PR.

**Verification**
- `mypy` (full repo, strict): `Success: no issues found in 8527 source
files`, on this branch and on this branch with all 10 stacked PRs
applied.
- `ruff check` / `ruff format --check` on the changed Python files, plus
`ruff --select F821,F401` across `src` and `tests`.
- getsentry master's mypy run against this branch reports the same 9
errors it reports against Sentry master: 6 are local-checkout gaps
(`scripts.*` and `devlocal` modules missing in my sandbox) and 3 are
already in `getsentry/jobs/migrate_legacy_seer.py`. None come from this
PR.
- I did not run pytest locally because there's no Postgres or Redis in
my sandbox. CI is the test signal.

Supersedes #129447 (fork branch, so I couldn't push to it).

<!-- junior-request-attribution:start -->
via @joshuarli.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AD0BQC9WSQ2E%3A1793660555.334709 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/slack%3AD0BQC9WSQ2E%3A1793660555.334709)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0BQC9WSQ2E%3A1793660555.334709/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Joshua Li <14209781+joshuarli@users.noreply.github.com>
Base automatically changed from josh/typing-strict-types-only to master November 3, 2026 14:41

This branch was successfully deployed

1 active deployment
Preview — 5581e7eb Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant