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

ref: reduce the surface area of sentry.db.models.manager.__init__ #72948

Merged
merged 1 commit into from
Jun 18, 2024
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,10 @@ module = [
"sentry.api.helpers.source_map_helper",
"sentry.buffer.*",
"sentry.build.*",
"sentry.db.models.manager",
"sentry.db.models.manager.base",
"sentry.db.models.manager.base_query_set",
"sentry.db.models.manager.types",
"sentry.db.models.query",
"sentry.eventstore.reprocessing.redis",
"sentry.eventtypes.error",
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/db/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from django.db import router, transaction

from sentry.db.models.manager import M
from sentry.db.models.manager.types import M
from sentry.models.options.organization_option import OrganizationOption

logger = logging.getLogger("sentry.deletions")
Expand Down
44 changes: 3 additions & 41 deletions src/sentry/db/models/manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,5 @@
from collections.abc import Callable, Mapping
from typing import Any, TypeVar
from __future__ import annotations

from django.db.models import Model
from django.utils.encoding import smart_str

from sentry.utils.hashlib import md5_text

__all__ = ("BaseManager", "BaseQuerySet", "OptionManager", "M", "Value", "ValidateFunction")

M = TypeVar("M", bound=Model, covariant=True)
Value = Any
ValidateFunction = Callable[[Value], bool]


def __prep_value(model: Any, key: str, value: Model | int | str) -> str:
val = value
if isinstance(value, Model):
val = value.pk
return str(val)


def __prep_key(model: Any, key: str) -> str:
if key == "pk":
return str(model._meta.pk.name)
return key


def make_key(model: Any, prefix: str, kwargs: Mapping[str, Model | int | str]) -> str:
kwargs_bits = []
for k, v in sorted(kwargs.items()):
k = __prep_key(model, k)
v = smart_str(__prep_value(model, k, v))
kwargs_bits.append(f"{k}={v}")
kwargs_bits_str = ":".join(kwargs_bits)

return f"{prefix}:{model.__name__}:{md5_text(kwargs_bits_str).hexdigest()}"


# Exporting these classes at the bottom to avoid circular dependencies.
from .base import BaseManager
from .base_query_set import BaseQuerySet
from .option import OptionManager

__all__ = ("BaseManager",)
27 changes: 26 additions & 1 deletion src/sentry/db/models/manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
from django.db.models.fields import Field
from django.db.models.manager import BaseManager as DjangoBaseManager
from django.db.models.signals import class_prepared, post_delete, post_init, post_save
from django.utils.encoding import smart_str

from sentry.db.models.manager import M, make_key
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.db.models.manager.types import M
from sentry.db.models.query import create_or_update
from sentry.db.postgres.transactions import django_test_transaction_water_mark
from sentry.silo.base import SiloLimit
Expand Down Expand Up @@ -44,6 +45,30 @@ class ModelManagerTriggerCondition(IntEnum):
ModelManagerTriggerAction = Callable[[type[Model]], None]


def __prep_value(model: Any, key: str, value: Model | int | str) -> str:
val = value
if isinstance(value, Model):
val = value.pk
return str(val)


def __prep_key(model: Any, key: str) -> str:
if key == "pk":
return str(model._meta.pk.name)
return key


def make_key(model: Any, prefix: str, kwargs: Mapping[str, Model | int | str]) -> str:
kwargs_bits = []
for k, v in sorted(kwargs.items()):
k = __prep_key(model, k)
v = smart_str(__prep_value(model, k, v))
kwargs_bits.append(f"{k}={v}")
kwargs_bits_str = ":".join(kwargs_bits)

return f"{prefix}:{model.__name__}:{md5_text(kwargs_bits_str).hexdigest()}"


class BaseManager(DjangoBaseManager.from_queryset(BaseQuerySet), Generic[M]): # type: ignore[misc]
lookup_handlers = {"iexact": lambda x: x.upper()}
use_for_related_fields = True
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/db/models/manager/base_query_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.db import connections, router, transaction
from django.db.models import QuerySet, sql

from sentry.db.models.manager import M
from sentry.db.models.manager.types import M
from sentry.signals import post_update


Expand Down
2 changes: 1 addition & 1 deletion src/sentry/db/models/manager/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from django.core.signals import request_finished
from django.db.models import Model

from sentry.db.models.manager import M
from sentry.db.models.manager.base import BaseManager, _local_cache
from sentry.db.models.manager.types import M


class OptionManager(BaseManager[M]):
Expand Down
7 changes: 7 additions & 0 deletions src/sentry/db/models/manager/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import annotations

from typing import TypeVar

from django.db.models import Model

M = TypeVar("M", bound=Model, covariant=True)
9 changes: 5 additions & 4 deletions src/sentry/db/models/paranoia.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from django.db import models
from django.utils import timezone

from sentry.db.models import BaseManager, BaseModel, BaseQuerySet, sane_repr
from sentry.db.models.manager import M
from sentry.db.models import BaseManager, BaseModel, sane_repr
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.db.models.manager.types import M


class ParanoidQuerySet(BaseQuerySet):
class ParanoidQuerySet(BaseQuerySet[M]):
"""
Prevents objects from being hard-deleted. Instead, sets the
``date_deleted``, effectively soft-deleting the object.
Expand All @@ -22,7 +23,7 @@ class ParanoidManager(BaseManager[M]):
Only exposes objects that have NOT been soft-deleted.
"""

def get_queryset(self) -> ParanoidQuerySet:
def get_queryset(self) -> ParanoidQuerySet[M]:
return ParanoidQuerySet(self.model, using=self._db).filter(date_deleted__isnull=True)


Expand Down
16 changes: 8 additions & 8 deletions src/sentry/models/options/organization_option.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from __future__ import annotations

from collections.abc import Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, ClassVar

from django.db import models

from sentry.backup.scopes import RelocationScope
from sentry.db.models import FlexibleForeignKey, Model, region_silo_model, sane_repr
from sentry.db.models.fields.picklefield import PickledObjectField
from sentry.db.models.manager import OptionManager, ValidateFunction, Value
from sentry.db.models.manager.option import OptionManager
from sentry.utils.cache import cache

if TYPE_CHECKING:
Expand All @@ -30,9 +30,9 @@ def get_value(
self,
organization: Organization,
key: str,
default: Value | None = None,
validate: ValidateFunction | None = None,
) -> Value:
default: Any | None = None,
validate: Callable[[object], bool] | None = None,
) -> Any:
result = self.get_all_values(organization)
return result.get(key, default)

Expand All @@ -44,14 +44,14 @@ def unset_value(self, organization: Organization, key: str) -> None:
inst.delete()
self.reload_cache(organization.id, "organizationoption.unset_value")

def set_value(self, organization: Organization, key: str, value: Value) -> bool:
def set_value(self, organization: Organization, key: str, value: Any) -> bool:
inst, created = self.create_or_update(
organization=organization, key=key, values={"value": value}
)
self.reload_cache(organization.id, "organizationoption.set_value")
return bool(created) or inst > 0

def get_all_values(self, organization: Organization | int) -> Mapping[str, Value]:
def get_all_values(self, organization: Organization | int) -> Mapping[str, Any]:
if isinstance(organization, models.Model):
organization_id = organization.id
else:
Expand All @@ -67,7 +67,7 @@ def get_all_values(self, organization: Organization | int) -> Mapping[str, Value

return self._option_cache.get(cache_key, {})

def reload_cache(self, organization_id: int, update_reason: str) -> Mapping[str, Value]:
def reload_cache(self, organization_id: int, update_reason: str) -> Mapping[str, Any]:
from sentry.tasks.relay import schedule_invalidate_project_config

if update_reason != "organizationoption.get_all_values":
Expand Down
14 changes: 7 additions & 7 deletions src/sentry/models/options/project_option.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from collections.abc import Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from typing import TYPE_CHECKING, Any, ClassVar

from django.db import models
Expand All @@ -11,7 +11,7 @@
from sentry.backup.scopes import ImportScope, RelocationScope
from sentry.db.models import FlexibleForeignKey, Model, region_silo_model, sane_repr
from sentry.db.models.fields import PickledObjectField
from sentry.db.models.manager import OptionManager, ValidateFunction, Value
from sentry.db.models.manager.option import OptionManager
from sentry.utils.cache import cache

if TYPE_CHECKING:
Expand Down Expand Up @@ -88,8 +88,8 @@ def get_value(
self,
project: int | Project,
key: str,
default: Value | None = None,
validate: ValidateFunction | None = None,
default: Any | None = None,
validate: Callable[[object], bool] | None = None,
) -> Any:
result = self.get_all_values(project)
if key in result:
Expand All @@ -105,7 +105,7 @@ def unset_value(self, project: Project, key: str) -> None:
self.filter(project=project, key=key).delete()
self.reload_cache(project.id, "projectoption.unset_value")

def set_value(self, project: int | Project, key: str, value: Value) -> bool:
def set_value(self, project: int | Project, key: str, value: Any) -> bool:
if isinstance(project, models.Model):
project_id = project.id
else:
Expand All @@ -118,7 +118,7 @@ def set_value(self, project: int | Project, key: str, value: Value) -> bool:

return created or inst > 0

def get_all_values(self, project: Project | int) -> Mapping[str, Value]:
def get_all_values(self, project: Project | int) -> Mapping[str, Any]:
if isinstance(project, models.Model):
project_id = project.id
else:
Expand All @@ -134,7 +134,7 @@ def get_all_values(self, project: Project | int) -> Mapping[str, Value]:

return self._option_cache.get(cache_key, {})

def reload_cache(self, project_id: int, update_reason: str) -> Mapping[str, Value]:
def reload_cache(self, project_id: int, update_reason: str) -> Mapping[str, Any]:
from sentry.tasks.relay import schedule_invalidate_project_config

if update_reason != "projectoption.get_all_values":
Expand Down
10 changes: 5 additions & 5 deletions src/sentry/models/options/user_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from sentry.db.models import FlexibleForeignKey, Model, control_silo_model, sane_repr
from sentry.db.models.fields import PickledObjectField
from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey
from sentry.db.models.manager import OptionManager, Value
from sentry.db.models.manager.option import OptionManager

if TYPE_CHECKING:
from sentry.models.organization import Organization
Expand Down Expand Up @@ -43,8 +43,8 @@ def _make_key( # type: ignore[override]
return super()._make_key(metakey)

def get_value(
self, user: User | RpcUser, key: str, default: Value | None = None, **kwargs: Any
) -> Value:
self, user: User | RpcUser, key: str, default: Any | None = None, **kwargs: Any
) -> Any:
project = kwargs.get("project")
organization = kwargs.get("organization")

Expand All @@ -71,7 +71,7 @@ def unset_value(self, user: User, project: Project, key: str) -> None:
return
self._option_cache[metakey].pop(key, None)

def set_value(self, user: User | int, key: str, value: Value, **kwargs: Any) -> None:
def set_value(self, user: User | int, key: str, value: Any, **kwargs: Any) -> None:
project = kwargs.get("project")
organization = kwargs.get("organization")
project_id = kwargs.get("project_id", None)
Expand Down Expand Up @@ -106,7 +106,7 @@ def get_all_values(
project: Project | int | None = None,
organization: Organization | int | None = None,
force_reload: bool = False,
) -> Mapping[str, Value]:
) -> Mapping[str, Any]:
if organization and project:
raise NotImplementedError(option_scope_error)

Expand Down
5 changes: 2 additions & 3 deletions src/sentry/models/organization.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from collections.abc import Collection, Mapping, Sequence
from collections.abc import Callable, Collection, Mapping, Sequence
from enum import IntEnum
from typing import TYPE_CHECKING, Any, ClassVar

Expand All @@ -22,7 +22,6 @@
)
from sentry.db.models import BaseManager, BoundedPositiveIntegerField, region_silo_model, sane_repr
from sentry.db.models.fields.slug import SentryOrgSlugField
from sentry.db.models.manager import ValidateFunction
from sentry.db.models.outboxes import ReplicatedRegionModel
from sentry.db.models.utils import slugify_instance
from sentry.db.postgres.transactions import in_test_hide_transaction_boundary
Expand Down Expand Up @@ -475,7 +474,7 @@ def get_scopes(self, role: Role) -> frozenset[str]:
return frozenset(scopes)

def get_option(
self, key: str, default: Any | None = None, validate: ValidateFunction | None = None
self, key: str, default: Any | None = None, validate: Callable[[object], bool] | None = None
) -> Any:
return self.option_manager.get_value(self, key, default, validate)

Expand Down
5 changes: 2 additions & 3 deletions src/sentry/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
from collections import defaultdict
from collections.abc import Collection, Iterable, Mapping
from collections.abc import Callable, Collection, Iterable, Mapping
from typing import TYPE_CHECKING, Any, ClassVar
from uuid import uuid1

Expand Down Expand Up @@ -31,7 +31,6 @@
sane_repr,
)
from sentry.db.models.fields.slug import SentrySlugField
from sentry.db.models.manager import ValidateFunction
from sentry.db.models.utils import slugify_instance
from sentry.locks import locks
from sentry.models.grouplink import GroupLink
Expand Down Expand Up @@ -382,7 +381,7 @@ def option_manager(self) -> ProjectOptionManager:
return ProjectOption.objects

def get_option(
self, key: str, default: Any | None = None, validate: ValidateFunction | None = None
self, key: str, default: Any | None = None, validate: Callable[[object], bool] | None = None
) -> Any:
return self.option_manager.get_value(self, key, default, validate)

Expand Down
3 changes: 2 additions & 1 deletion src/sentry/models/releases/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from sentry_relay.exceptions import RelayError
from sentry_relay.processing import parse_release

from sentry.db.models import ArrayField, BaseQuerySet
from sentry.db.models import ArrayField
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.exceptions import InvalidSearchQuery
from sentry.models.releases.release_project import ReleaseProject
from sentry.utils.numbers import validate_bigint
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/search/snuba/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from sentry import features, quotas
from sentry.api.event_search import SearchFilter
from sentry.db.models import BaseQuerySet
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.exceptions import InvalidSearchQuery
from sentry.issues.grouptype import ErrorGroupType
from sentry.models.environment import Environment
Expand Down
Loading
Loading