Skip to content

Conversation

shayna-ch
Copy link
Member

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

thetruecpaul and others added 30 commits August 18, 2025 09:44
Added return types to functions that just return literal strings, ints,
or bools.

Used the following python script:

```
from re import sub

# find **/*.py > files.txt
for file in open("files.txt"):
    source = open(file.strip()).read()
    """
    def foo(x: int, y):
        return f"1234"
    """

    # Literal strings
    source = sub(
        r"def ([^\n>]+):(\n\s+)(return [rf]?(\"[^\"]*\"|'[^']*')\n)",
        r"def \1 -> str:\2\3",
        source,
    )

    # Literal ints
    source = sub(
        r"def ([^\n>]+):(\n\s+)(return \d+\n)",
        r"def \1 -> int:\2\3",
        source,
    )

    # Literal bools
    source = sub(
        r"def ([^\n>]+):(\n\s+)(return (True|False)\n)",
        r"def \1 -> bool:\2\3",
        source,
    )

    open(file.strip(), "w").write(source)
```
Added a bunch of return types. (Took the HttpResponseBase from the root
class.)

`mypy` no errors
two super-duper low-lift files (just adding `None` return types).
Part of Hackweek 2025 typing efforts ⭐
# Summary
Grabbed this because I thought it would just be a bunch of easy
missing-return-type errors. It turns out, when you add return types you
expose other errors. Who'd've thunk? 😅

Here's the complicating factor: the plugin framework uses `Request`.
`RequestFactory` returns `WSGIRequest`. `BaseTestCase.make_request`
returns `HttpRequest`. Our typing revealed that a bunch of incorrect
`Request-ish` types were being passed around.

This PR changes the incompatible frameworks to `MagicMock`, which more
correctly limits behavior to what is explicitly defined (and not what
would not actually happen due to type differences).

# Test Plan
`mypy tests/sentry_plugins` ==> no errors
`pytest tests/sentry_plugins/**` ==> `162 passed, 1 skipped`
was picking through the files with only one error. this got a lot of the
initially-known-to-mypy errors fixed... but I'm concerned that it
exposed more errors that I don't have a good way of exposing, since I
didn't stick to a single easily-added-to-pyproject module.

I think in the future I'll probably stick to codemods or modules.
Just needed to type the setup functions!

`rg -l 'def setUp\(self\):' tests/apidocs | xargs sed -i '' -E -e 's/def
setUp\(self\):/def setUp(self) -> None:/'`

# Test Plan

Ran `mypy tests/apidocs`, no errors
# Summary

59 files, newly typed!

# Test Plan

`mypy` no errors

---------

Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
# Summary

Developed a new pattern that let me run through some files I identified
as low-lift quickly:
* Added `"sentry.*", "test.*"` to `pyproject.toml`
* Developed a list of expected-to-be-low-lift files from mypy output
* Mostly files that were in test directory, only had one error, or only
needed `None` return types
* Then repeatedly ran `mypy <big_list_of_files>` and whittled that down
* Before pushing the branch, I removed the changes to `pyproject.toml`

Result is that I can confidently say that this PR successfully types
~150 new files.

# Test Plan 
Reverted changes to `pyproject.toml` and ran `mypy` to ensure the new
types had no effect on steady-state — no errors!
These magic test functions always have the same returns! I already
messed with some in `tests/sentry`; worth expanding to all of `tests`

```
rg 'def tearDown' tests -ls | xargs sed -i '' 's/def tearDown(self):/def tearDown(self) -> None:/'
rg 'def setUp' tests -ls | xargs sed -i '' 's/def setUp(self):/def setUp(self) -> None:/'
```
Follows #97998

Turns out there's a utility function to convert `HttpRequest` to
`Request`! Better to use the normal requests than full mocks.
completely types an additional 65 files with low errors-per-file count
This was more complex than expected 😅
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Aug 21, 2025
Copy link

codecov bot commented Aug 21, 2025

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
27285 2 27283 592
View the top 2 failed test(s) by shortest run time
tests.sentry.tasks.test_backfill_seer_grouping_records.TestBackfillSeerGroupingRecords::test_get_data_from_snuba_rate_limit_exception
Stack Traces | 8.45s run time
#x1B[1m#x1B[.../sentry/tasks/test_backfill_seer_grouping_records.py#x1B[0m:563: in test_get_data_from_snuba_rate_limit_exception
    mock_logger.exception.assert_called_with(
#x1B[1m#x1B[.../hostedtoolcache/Python/3.13.1.../x64/lib/python3.13/unittest/mock.py#x1B[0m:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
#x1B[1m#x1B[31mE   AssertionError: expected call not found.#x1B[0m
#x1B[1m#x1B[31mE   Expected: exception('backfill_seer_grouping_records.snuba_query_limit_exceeded', extra={'organization_id': 4556632115445760, 'project_id': 4556632115511297, 'error': 'Snuba Rate Limit Exceeded', 'worker_number': None})#x1B[0m
#x1B[1m#x1B[31mE     Actual: exception('backfill_seer_grouping_records.snuba_query_limit_exceeded', extra={'organization_id': 4556632115445760, 'project_id': 4556632115511297, 'error': 'Snuba Rate Limit Exceeded', 'worker_number': 0})#x1B[0m
tests.sentry.tasks.test_backfill_seer_grouping_records.TestBackfillSeerGroupingRecords::test_get_data_from_snuba_too_many_simultaneous_exception
Stack Traces | 9.49s run time
#x1B[1m#x1B[.../sentry/tasks/test_backfill_seer_grouping_records.py#x1B[0m:587: in test_get_data_from_snuba_too_many_simultaneous_exception
    mock_logger.exception.assert_called_with(
#x1B[1m#x1B[.../hostedtoolcache/Python/3.13.1.../x64/lib/python3.13/unittest/mock.py#x1B[0m:977: in assert_called_with
    raise AssertionError(_error_message()) from cause
#x1B[1m#x1B[31mE   AssertionError: expected call not found.#x1B[0m
#x1B[1m#x1B[31mE   Expected: exception('backfill_seer_grouping_records.snuba_query_limit_exceeded', extra={'organization_id': 4556632116035584, 'project_id': 4556632116035586, 'error': 'Too Many Simultaneous Snuba Queries', 'worker_number': None})#x1B[0m
#x1B[1m#x1B[31mE     Actual: exception('backfill_seer_grouping_records.snuba_query_limit_exceeded', extra={'organization_id': 4556632116035584, 'project_id': 4556632116035586, 'error': 'Too Many Simultaneous Snuba Queries', 'worker_number': 0})#x1B[0m

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Base automatically changed from hackweek/typing-25 to master August 25, 2025 16:24
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.

3 participants