Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typing(interfaces): Type contexts #78768

Closed
wants to merge 1 commit into from
Closed
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 @@ -429,6 +429,7 @@ module = [
"sentry.hybridcloud.*",
"sentry.ingest.slicing",
"sentry.integrations.models.integration_feature",
"sentry.interfaces.contexts",
"sentry.issues",
"sentry.issues.analytics",
"sentry.issues.apps",
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/interfaces/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def __setattr__(self, name, value):
self._data[name] = value

@classmethod
def to_python(cls, data, datapath: DataPath | None = None):
def to_python(cls: Any, data: dict[str, Any], **kwargs: Any) -> dict[str, Any] | None:
Copy link
Member Author

Choose a reason for hiding this comment

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

This is to match the signature in Contexts.

Copy link
Member

Choose a reason for hiding this comment

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

cls should never be annotated -- it will receive an automatic type[Self] annotation

"""Creates a python interface object from the given raw data.

This function can assume fully normalized and valid data. It can create
Expand All @@ -119,6 +119,7 @@ def to_python(cls, data, datapath: DataPath | None = None):
return None

rv = cls(**data)
datapath = kwargs.pop("datapath", None)
object.__setattr__(rv, "datapath", datapath)
return rv

Expand Down
30 changes: 16 additions & 14 deletions src/sentry/interfaces/contexts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import string
from collections.abc import Iterator
from typing import Any, ClassVar, TypeVar

from django.utils.encoding import force_str
Expand All @@ -17,13 +18,13 @@


class _IndexFormatter(string.Formatter):
def format_field(self, value, format_spec):
def format_field(self, value: object, format_spec: str) -> str:
if not format_spec and isinstance(value, bool):
return value and "yes" or "no"
return string.Formatter.format_field(self, value, format_spec)


def format_index_expr(format_string, data):
def format_index_expr(format_string: str, data: dict[str, object]) -> str:
return str(_IndexFormatter().vformat(str(format_string), (), data).strip())


Expand Down Expand Up @@ -82,7 +83,7 @@ class ContextType:
type: str
"""This should match the `type` key in context object"""

def __init__(self, alias, data):
def __init__(self, alias: str, data: dict[str, object]) -> None:
self.alias = alias
ctx_data = {}
for key, value in data.items():
Expand All @@ -98,30 +99,31 @@ def __init__(self, alias, data):
ctx_data[force_str(key)] = self.change_type(value)
self.data = ctx_data

def to_json(self):
def to_json(self) -> dict[str, object]:
rv = dict(self.data)
rv["type"] = self.type
return prune_empty_keys(rv)

@classmethod
def values_for_data(cls, data):
def values_for_data(cls, data: dict[str, Any]) -> list[dict[str, object]]:
rv = []
for context in (data.get("contexts") or {}).values():
if context and context.get("type") == cls.type:
rv.append(context)
return rv

@classmethod
def primary_value_for_data(cls, data):
def primary_value_for_data(cls: Any, data: dict[str, object]) -> dict[str, object] | None:
val = get_path(data, "contexts", cls.type)
if val and val.get("type") == cls.type:
return val

rv = cls.values_for_data(data)
if len(rv) == 1:
return rv[0]
return None

def iter_tags(self):
def iter_tags(self) -> Iterator[tuple[str, str]]:
if self.context_to_tag_mapping:
for field, f_string in self.context_to_tag_mapping.items():
try:
Expand All @@ -134,7 +136,7 @@ def iter_tags(self):
else:
yield (f"{self.alias}.{field}", value)

def change_type(self, value: int | float | list | dict) -> Any:
def change_type(self, value: int | float | list[Any] | dict[str, Any]) -> object:
if isinstance(value, (float, int)) and len(str_value := force_str(value)) > 15:
return str_value
if isinstance(value, list):
Expand Down Expand Up @@ -217,7 +219,7 @@ class Contexts(Interface):
score = 800

@classmethod
def to_python(cls, data, **kwargs):
def to_python(cls: Any, data: dict[str, Any], **kwargs: Any) -> dict[str, Any] | None:
rv = {}

# Note the alias is the key of the context entry
Expand All @@ -230,20 +232,20 @@ def to_python(cls, data, **kwargs):
return super().to_python(rv, **kwargs)

@classmethod
def normalize_context(cls, alias, data):
def normalize_context(cls: Any, alias: str, data: dict[str, Any]) -> ContextType:
ctx_type = data.get("type", alias)
ctx_cls = context_types.get(ctx_type, DefaultContextType)
return ctx_cls(alias, data)

def iter_contexts(self):
return self._data.values()
def iter_contexts(self) -> list[ContextType]:
return list(self._data.values())

def to_json(self):
def to_json(self) -> dict[str, dict[str, object]]:
rv = {}
for alias, inst in self._data.items():
rv[alias] = inst.to_json()
return rv

def iter_tags(self):
def iter_tags(self) -> Iterator[tuple[str, str]]:
for inst in self.iter_contexts():
yield from inst.iter_tags()
Loading