From f0157f40f7406c18ad44439f616c6f68d7da03bc Mon Sep 17 00:00:00 2001 From: Armen Zambrano G <44410+armenzg@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:44:54 -0400 Subject: [PATCH] typing(interfaces): Type contexts --- pyproject.toml | 1 + src/sentry/interfaces/base.py | 3 ++- src/sentry/interfaces/contexts.py | 30 ++++++++++++++++-------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1436b0401fd20a..ecf287d5d87aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/src/sentry/interfaces/base.py b/src/sentry/interfaces/base.py index 1c5eeb43423371..57d5f6b1e05567 100644 --- a/src/sentry/interfaces/base.py +++ b/src/sentry/interfaces/base.py @@ -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: """Creates a python interface object from the given raw data. This function can assume fully normalized and valid data. It can create @@ -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 diff --git a/src/sentry/interfaces/contexts.py b/src/sentry/interfaces/contexts.py index 35285b823c058d..93b45195128dd5 100644 --- a/src/sentry/interfaces/contexts.py +++ b/src/sentry/interfaces/contexts.py @@ -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 @@ -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()) @@ -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(): @@ -98,13 +99,13 @@ 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: @@ -112,7 +113,7 @@ def values_for_data(cls, data): 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 @@ -120,8 +121,9 @@ def primary_value_for_data(cls, data): 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: @@ -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): @@ -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 @@ -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()