Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -912,4 +912,5 @@ module = [
]
disallow_any_generics = true
disallow_untyped_defs = true
strict_equality = true
# end: stronger typing
2 changes: 1 addition & 1 deletion src/sentry/flags/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ class FlagAuditLogItem(TypedDict):
flag: str
created_at: datetime.datetime
created_by: str
tags: dict[str, str]
tags: dict[str, Any]


def handle_flag_pole_event_internal(items: list[FlagAuditLogItem], organization_id: int) -> None:
Expand Down
3 changes: 0 additions & 3 deletions tests/sentry/data_export/processors/test_discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def test_handle_issue_id_fields(self) -> None:
assert processor.header_fields == ["count_id", "fake_field", "issue"]
result_list = [{"issue": self.group.id, "issue.id": self.group.id}]
new_result_list = processor.handle_fields(result_list)
assert new_result_list[0] != result_list
assert new_result_list[0]["issue"] == self.group.qualified_short_id

def test_handle_transaction_status_fields(self) -> None:
Expand All @@ -63,7 +62,6 @@ def test_handle__fields(self) -> None:
assert processor.header_fields == ["count_id", "fake_field", "issue"]
result_list = [{"issue": self.group.id, "issue.id": self.group.id}]
new_result_list = processor.handle_fields(result_list)
assert new_result_list[0] != result_list
assert new_result_list[0]["issue"] == self.group.qualified_short_id

def test_handle_equations(self) -> None:
Expand All @@ -78,7 +76,6 @@ def test_handle_equations(self) -> None:
]
result_list = [{"equation[0]": 5, "equation[1]": 8}]
new_result_list = processor.handle_fields(result_list)
assert new_result_list[0] != result_list
assert new_result_list[0]["count(id) / fake(field)"] == 5
assert new_result_list[0]["count(id) / 2"] == 8

Expand Down
8 changes: 4 additions & 4 deletions tests/sentry/data_secrecy/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ def test_post_init_validation_valid_window_without_access_start(self) -> None:
class GrantCacheStatusTest(TestCase):
def test_grant_cache_status_values(self) -> None:
"""Test that GrantCacheStatus enum has expected values"""
assert GrantCacheStatus.CACHE_MISS == "cache_miss"
assert GrantCacheStatus.NEGATIVE_CACHE == "negative_cache"
assert GrantCacheStatus.VALID_WINDOW == "valid_window"
assert GrantCacheStatus.EXPIRED_WINDOW == "expired_window"
assert GrantCacheStatus.CACHE_MISS.value == "cache_miss"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR, but curious about python enums and .value notation.

My understanding was that this is specifically meant to be for the default enums, and IntEnum / StrEnum could be accessed safely w/o .value?

(maybe this is just one of the python-isms i can't get used to, why do you need the .value when it already has the key?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thing to flag. This is, depending who you ask, a mypy bug or a Python typing spec bug.
I've seen https://discuss.python.org/t/amend-pep-586-to-make-enum-values-subtypes-of-literal/59456/9, python/mypy#16327, and a few others.

But, I believe this issue is limited to subclass enum to literal comparisons, which should be rare in practice (as the enum is intended to take the role of a literal).
So, as I understand it, a niche case with a fairly straightforward workaround (.value) that will probably go away or can be made to go away.

(I did look into updating our mypy plugin to fix these, but it wasn't trivial and didn't seem worth it)

assert GrantCacheStatus.NEGATIVE_CACHE.value == "negative_cache"
assert GrantCacheStatus.VALID_WINDOW.value == "valid_window"
assert GrantCacheStatus.EXPIRED_WINDOW.value == "expired_window"

def test_grant_cache_status_is_string_enum(self) -> None:
"""Test that GrantCacheStatus values are strings"""
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/event_manager/test_event_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1641,7 +1641,7 @@ def test_invalid_tags(self) -> None:
assert None in manager.get_data().get("tags", [])
assert 42 not in manager.get_data().get("tags", [])
event = manager.save(self.project.id)
assert 42 not in event.tags
assert 42 not in event.tags # type: ignore[comparison-overlap]
assert None not in event.tags

@mock.patch("sentry.event_manager.eventstream.backend.insert")
Expand Down
4 changes: 2 additions & 2 deletions tests/sentry/eventstream/test_eventstream.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import time
import uuid
from collections.abc import Generator
from collections.abc import Generator, MutableSequence
from datetime import datetime, timedelta
from typing import Any
from unittest.mock import MagicMock, Mock, patch
Expand Down Expand Up @@ -89,7 +89,7 @@ def __produce_event(self, *insert_args: Any, **insert_kwargs: Any) -> None:

def __produce_payload(
self, *insert_args: Any, **insert_kwargs: Any
) -> tuple[list[tuple[str, str | None]] | dict[str, str | None], Any]:
) -> tuple[MutableSequence[tuple[str, bytes]], Any]:
# pass arguments on to Kafka EventManager
self.kafka_eventstream.insert(*insert_args, **insert_kwargs)

Expand Down
15 changes: 10 additions & 5 deletions tests/sentry/integrations/middleware/hybrid_cloud/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import MagicMock, patch

import pytest
from django.http import HttpResponse
from django.test import RequestFactory, override_settings
from pytest import raises
from rest_framework import status
Expand All @@ -20,10 +21,10 @@
from sentry.types.region import Region, RegionCategory


def error_regions(region: Region, invalid_region_names: Iterable[str]) -> str:
def error_regions(region: Region, invalid_region_names: Iterable[str]) -> HttpResponse:
if region.name in invalid_region_names:
raise SiloLimit.AvailabilityError("Region is offline!")
return region.name
return HttpResponse(region.name, status=200)


class ExampleRequestParser(BaseRequestParser):
Expand Down Expand Up @@ -67,13 +68,15 @@ def test_get_response_from_control_silo(self) -> None:
@override_settings(SILO_MODE=SiloMode.CONTROL)
@patch.object(BaseRequestParser, "get_response_from_region_silo")
def test_get_responses_from_region_silos(self, mock__get_response: MagicMock) -> None:
mock__get_response.side_effect = lambda region: region.name
mock__get_response.side_effect = lambda region: HttpResponse(region.name, status=200)

response_map = self.parser.get_responses_from_region_silos(regions=self.region_config)
assert mock__get_response.call_count == len(self.region_config)

for region in self.region_config:
assert response_map[region.name].response == region.name
response = response_map[region.name].response
assert isinstance(response, HttpResponse)
assert response.content == region.name.encode()

@override_settings(SILO_MODE=SiloMode.CONTROL)
@patch.object(BaseRequestParser, "get_response_from_region_silo")
Expand All @@ -84,7 +87,9 @@ def test_get_responses_from_region_silos_with_partial_failure(

response_map = self.parser.get_responses_from_region_silos(regions=self.region_config)
assert mock__get_response.call_count == len(self.region_config)
assert response_map["us"].response == "us"
us_response = response_map["us"].response
assert isinstance(us_response, HttpResponse)
assert us_response.content == b"us"
assert type(response_map["eu"].error) is SiloLimit.AvailabilityError

@override_settings(SILO_MODE=SiloMode.CONTROL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _stacktrace(frames: list[dict[str, Any]]) -> dict[str, Any]:
],
)
def test_get_frames_to_process(
frames: list[dict[str, Any]], platform: str, expected: set[str]
frames: list[dict[str, Any]], platform: str, expected: list[dict[str, Any]]
) -> None:
frames = get_frames_to_process(_exception_with_stacktrace(frames), platform)
assert frames == expected
Expand All @@ -111,7 +111,7 @@ def test_get_frames_to_process(
([BASIC_FRAME, None], [BASIC_FRAME]), # Handle intermixing of None and dicts
],
)
def test_with_invalid_frames(frames: list[dict[str, Any]], expected: list[str]) -> None:
def test_with_invalid_frames(frames: list[dict[str, Any]], expected: list[dict[str, Any]]) -> None:
frames = get_frames_to_process(_exception_with_stacktrace(frames), "python")
assert frames == expected

Expand Down
32 changes: 18 additions & 14 deletions tests/sentry/release_health/test_metrics_sessions_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
Condition(Column("session.status"), Op.IN, ["abnormal", "errored"]),
],
[Condition(Column("release"), Op.EQ, "foo")],
{SessionStatus.ABNORMAL, SessionStatus.ERRORED},
frozenset({SessionStatus.ABNORMAL, SessionStatus.ERRORED}),
),
(
[
Expand All @@ -42,25 +42,29 @@
Condition(Column("session.status"), Op.NEQ, "abnormal"),
],
[Condition(Column("release"), Op.EQ, "foo")],
{
SessionStatus.HEALTHY,
SessionStatus.ERRORED,
SessionStatus.CRASHED,
SessionStatus.UNHANDLED,
},
frozenset(
{
SessionStatus.HEALTHY,
SessionStatus.ERRORED,
SessionStatus.CRASHED,
SessionStatus.UNHANDLED,
}
),
),
(
[
Condition(Column("release"), Op.EQ, "foo"),
Condition(Column("session.status"), Op.NOT_IN, ["abnormal", "bogus"]),
],
[Condition(Column("release"), Op.EQ, "foo")],
{
SessionStatus.HEALTHY,
SessionStatus.ERRORED,
SessionStatus.CRASHED,
SessionStatus.UNHANDLED,
},
frozenset(
{
SessionStatus.HEALTHY,
SessionStatus.ERRORED,
SessionStatus.CRASHED,
SessionStatus.UNHANDLED,
}
),
),
(
[
Expand All @@ -75,7 +79,7 @@
def test_transform_conditions(
input: ConditionGroup,
expected_output: ConditionGroup,
expected_status_filter: set[SessionStatus],
expected_status_filter: frozenset[SessionStatus],
) -> None:
output, status_filter = _extract_status_filter_from_conditions(input)
assert output == expected_output
Expand Down
10 changes: 7 additions & 3 deletions tests/sentry/replays/consumers/test_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def get_recording_data(self, segment_id: int) -> memoryview:
assert result is not None, "Expecting non-None result here"
return unpack(zlib.decompress(result))[1]

def get_video_data(self, segment_id: int) -> None | tuple[None | memoryview, memoryview]:
def get_video_data(self, segment_id: int) -> memoryview | None:
result = storage_kv.get(
_make_recording_filename(
project_id=self.project.id,
Expand Down Expand Up @@ -150,7 +150,9 @@ def test_end_to_end_consumer_processing_old(

dat = self.get_recording_data(segment_id)
assert json.loads(bytes(dat).decode("utf-8")) == data
assert self.get_video_data(segment_id) == b"hello, world!"
video_data = self.get_video_data(segment_id)
assert video_data is not None
assert bytes(video_data) == b"hello, world!"

self.project.refresh_from_db()
assert self.project.flags.has_replays
Expand Down Expand Up @@ -241,7 +243,9 @@ def test_end_to_end_consumer_processing_new(

dat = self.get_recording_data(segment_id)
assert json.loads(bytes(dat).decode("utf-8")) == data
assert self.get_video_data(segment_id) == b"hello, world!"
video_data = self.get_video_data(segment_id)
assert video_data is not None
assert bytes(video_data) == b"hello, world!"

self.project.refresh_from_db()
assert bool(self.project.flags.has_replays) is True
Expand Down
Loading