diff --git a/.github/workflows/linting-and-tests.yml b/.github/workflows/linting-and-tests.yml index ded42414..866a6de2 100644 --- a/.github/workflows/linting-and-tests.yml +++ b/.github/workflows/linting-and-tests.yml @@ -283,8 +283,6 @@ jobs: mypy: name: "mypy" - # disable until we fix all the pre-existing errors (aka https://github.com/grafana/oncall/issues/2168 is marked as completed) - if: ${{ false }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/engine/apps/alerts/models/alert_receive_channel.py b/engine/apps/alerts/models/alert_receive_channel.py index 42d6ce0b..8579ddb3 100644 --- a/engine/apps/alerts/models/alert_receive_channel.py +++ b/engine/apps/alerts/models/alert_receive_channel.py @@ -41,7 +41,7 @@ from common.public_primary_keys import generate_public_primary_key, increase_pub if typing.TYPE_CHECKING: from django.db.models.manager import RelatedManager - from apps.alerts.models import GrafanaAlertingContactPoint + from apps.alerts.models import ChannelFilter, GrafanaAlertingContactPoint logger = logging.getLogger(__name__) @@ -114,6 +114,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject): Channel generated by user to receive Alerts to. """ + channel_filters: "RelatedManager['ChannelFilter']" contact_points: "RelatedManager['GrafanaAlertingContactPoint']" objects = AlertReceiveChannelManager() diff --git a/engine/apps/alerts/models/custom_button.py b/engine/apps/alerts/models/custom_button.py index 814f5e0c..626e7f79 100644 --- a/engine/apps/alerts/models/custom_button.py +++ b/engine/apps/alerts/models/custom_button.py @@ -1,6 +1,7 @@ import json import logging import re +import typing from json import JSONDecodeError from django.conf import settings @@ -14,6 +15,12 @@ from common.jinja_templater import apply_jinja_template from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import EscalationPolicy + + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -46,6 +53,8 @@ class CustomButtonManager(models.Manager): class CustomButton(models.Model): + escalation_policies: "RelatedManager['EscalationPolicy']" + objects = CustomButtonManager() objects_with_deleted = models.Manager() diff --git a/engine/apps/alerts/models/escalation_chain.py b/engine/apps/alerts/models/escalation_chain.py index 2dcd7254..1ac04db5 100644 --- a/engine/apps/alerts/models/escalation_chain.py +++ b/engine/apps/alerts/models/escalation_chain.py @@ -1,3 +1,5 @@ +import typing + from django.conf import settings from django.core.validators import MinLengthValidator from django.db import models, transaction @@ -5,6 +7,11 @@ from django.db import models, transaction from apps.alerts.models.escalation_policy import generate_public_primary_key_for_escalation_policy from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import ChannelFilter, EscalationPolicy + def generate_public_primary_key_for_escalation_chain(): prefix = "F" @@ -21,6 +28,9 @@ def generate_public_primary_key_for_escalation_chain(): class EscalationChain(models.Model): + channel_filters: "RelatedManager['ChannelFilter']" + escalation_policies: "RelatedManager['EscalationPolicy']" + public_primary_key = models.CharField( max_length=20, validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)], diff --git a/engine/apps/alerts/models/escalation_policy.py b/engine/apps/alerts/models/escalation_policy.py index 1bd70f37..9c9b1338 100644 --- a/engine/apps/alerts/models/escalation_policy.py +++ b/engine/apps/alerts/models/escalation_policy.py @@ -229,7 +229,10 @@ class EscalationPolicy(OrderedModel): "alerts.EscalationChain", on_delete=models.CASCADE, related_name="escalation_policies" ) - notify_to_users_queue = models.ManyToManyField("user_management.User") + notify_to_users_queue = models.ManyToManyField( + "user_management.User", + related_name="escalation_policy_notify_queues", + ) last_notified_user = models.ForeignKey( "user_management.User", @@ -244,6 +247,7 @@ class EscalationPolicy(OrderedModel): notify_to_group = models.ForeignKey( "slack.SlackUserGroup", on_delete=models.SET_NULL, + related_name="escalation_policies", default=None, null=True, ) diff --git a/engine/apps/auth_token/models/api_auth_token.py b/engine/apps/auth_token/models/api_auth_token.py index 531c00be..f3873c20 100644 --- a/engine/apps/auth_token/models/api_auth_token.py +++ b/engine/apps/auth_token/models/api_auth_token.py @@ -8,6 +8,8 @@ from apps.user_management.models import Organization, User class ApiAuthToken(BaseAuthToken): + objects: models.QuerySet["ApiAuthToken"] + user = models.ForeignKey(to=User, null=False, blank=False, related_name="auth_tokens", on_delete=models.CASCADE) organization = models.ForeignKey( to=Organization, null=False, blank=False, related_name="auth_tokens", on_delete=models.CASCADE diff --git a/engine/apps/auth_token/models/plugin_auth_token.py b/engine/apps/auth_token/models/plugin_auth_token.py index 56a33068..fce2d1bc 100644 --- a/engine/apps/auth_token/models/plugin_auth_token.py +++ b/engine/apps/auth_token/models/plugin_auth_token.py @@ -16,8 +16,14 @@ from apps.user_management.models import Organization class PluginAuthToken(BaseAuthToken): + objects: models.Manager["PluginAuthToken"] + salt = models.CharField(max_length=constants.AUTH_TOKEN_CHARACTER_LENGTH, null=True) - organization = models.ForeignKey(to=Organization, on_delete=models.CASCADE) + organization = models.ForeignKey( + to=Organization, + on_delete=models.CASCADE, + related_name="plugin_auth_tokens", + ) @classmethod def create_auth_token(cls, organization: Organization) -> Tuple["PluginAuthToken", str]: diff --git a/engine/apps/auth_token/models/schedule_export_auth_token.py b/engine/apps/auth_token/models/schedule_export_auth_token.py index ffae910f..fa1462a2 100644 --- a/engine/apps/auth_token/models/schedule_export_auth_token.py +++ b/engine/apps/auth_token/models/schedule_export_auth_token.py @@ -9,6 +9,8 @@ from apps.user_management.models import Organization, User class ScheduleExportAuthToken(BaseAuthToken): + objects: models.Manager["ScheduleExportAuthToken"] + class Meta: unique_together = ("user", "organization", "schedule") diff --git a/engine/apps/auth_token/models/user_schedule_export_auth_token.py b/engine/apps/auth_token/models/user_schedule_export_auth_token.py index 94186408..f27992d6 100644 --- a/engine/apps/auth_token/models/user_schedule_export_auth_token.py +++ b/engine/apps/auth_token/models/user_schedule_export_auth_token.py @@ -8,6 +8,8 @@ from apps.user_management.models import Organization, User class UserScheduleExportAuthToken(BaseAuthToken): + objects: models.Manager["UserScheduleExportAuthToken"] + class Meta: unique_together = ("user", "organization") diff --git a/engine/apps/mobile_app/models.py b/engine/apps/mobile_app/models.py index 7d45692e..aa94b435 100644 --- a/engine/apps/mobile_app/models.py +++ b/engine/apps/mobile_app/models.py @@ -52,6 +52,8 @@ class MobileAppVerificationToken(BaseAuthToken): class MobileAppAuthToken(BaseAuthToken): + objects: models.Manager["MobileAppAuthToken"] + user = models.OneToOneField(to=User, null=False, blank=False, on_delete=models.CASCADE) organization = models.ForeignKey( to=Organization, null=False, blank=False, related_name="mobile_app_auth_tokens", on_delete=models.CASCADE diff --git a/engine/apps/schedules/models/custom_on_call_shift.py b/engine/apps/schedules/models/custom_on_call_shift.py index 24562c33..d4e2af67 100644 --- a/engine/apps/schedules/models/custom_on_call_shift.py +++ b/engine/apps/schedules/models/custom_on_call_shift.py @@ -2,6 +2,7 @@ import copy import datetime import itertools import logging +import typing from calendar import monthrange from uuid import uuid4 @@ -27,6 +28,12 @@ from apps.schedules.tasks import ( from apps.user_management.models import User from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.schedules.models import OnCallSchedule + + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -46,6 +53,8 @@ def generate_public_primary_key_for_custom_oncall_shift(): class CustomOnCallShift(models.Model): + schedules: "RelatedManager['OnCallSchedule']" + ( FREQUENCY_DAILY, FREQUENCY_WEEKLY, diff --git a/engine/apps/schedules/models/on_call_schedule.py b/engine/apps/schedules/models/on_call_schedule.py index f759811f..eefc6c47 100644 --- a/engine/apps/schedules/models/on_call_schedule.py +++ b/engine/apps/schedules/models/on_call_schedule.py @@ -46,6 +46,13 @@ from apps.user_management.models import User from common.database import NON_POLYMORPHIC_CASCADE, NON_POLYMORPHIC_SET_NULL from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import EscalationPolicy + from apps.auth_token.models import ScheduleExportAuthToken + + RE_ICAL_SEARCH_USERNAME = r"SUMMARY:(\[L[0-9]+\] )?{}" RE_ICAL_FETCH_USERNAME = re.compile(r"SUMMARY:(?:\[L[0-9]+\] )?(\w+)") @@ -836,6 +843,10 @@ class OnCallSchedule(PolymorphicModel): class OnCallScheduleICal(OnCallSchedule): + escalation_policies: "RelatedManager['EscalationPolicy']" + objects: models.Manager["OnCallScheduleICal"] + schedule_export_token: "RelatedManager['ScheduleExportAuthToken']" + # For the ical schedule both primary and overrides icals are imported via ical url ical_url_primary = models.CharField(max_length=500, null=True, default=None) ical_file_error_primary = models.CharField(max_length=200, null=True, default=None) @@ -901,6 +912,10 @@ class OnCallScheduleICal(OnCallSchedule): class OnCallScheduleCalendar(OnCallSchedule): + escalation_policies: "RelatedManager['EscalationPolicy']" + objects: models.Manager["OnCallScheduleCalendar"] + schedule_export_token: "RelatedManager['ScheduleExportAuthToken']" + # For the calendar schedule only overrides ical is imported via ical url. ical_url_overrides = models.CharField(max_length=500, null=True, default=None) ical_file_error_overrides = models.CharField(max_length=200, null=True, default=None) @@ -994,6 +1009,10 @@ class OnCallScheduleCalendar(OnCallSchedule): class OnCallScheduleWeb(OnCallSchedule): + escalation_policies: "RelatedManager['EscalationPolicy']" + objects: models.Manager["OnCallScheduleWeb"] + schedule_export_token: "RelatedManager['ScheduleExportAuthToken']" + time_zone = models.CharField(max_length=100, default="UTC") def _generate_ical_file_primary(self): diff --git a/engine/apps/slack/models/slack_usergroup.py b/engine/apps/slack/models/slack_usergroup.py index 6d7fa708..fcafedd9 100644 --- a/engine/apps/slack/models/slack_usergroup.py +++ b/engine/apps/slack/models/slack_usergroup.py @@ -1,4 +1,5 @@ import logging +import typing import requests from django.conf import settings @@ -13,6 +14,13 @@ from apps.slack.slack_client.exceptions import SlackAPIException from apps.user_management.models.user import User from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import EscalationPolicy + from apps.schedules.models import OnCallSchedule + + logger = logging.getLogger(__name__) @@ -31,6 +39,9 @@ def generate_public_primary_key_for_slack_user_group(): class SlackUserGroup(models.Model): + escalation_policies: "RelatedManager['EscalationPolicy']" + oncall_schedules: "RelatedManager['OnCallSchedule']" + public_primary_key = models.CharField( max_length=20, validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)], diff --git a/engine/apps/telegram/models/connectors/channel.py b/engine/apps/telegram/models/connectors/channel.py index fc3c7794..49168808 100644 --- a/engine/apps/telegram/models/connectors/channel.py +++ b/engine/apps/telegram/models/connectors/channel.py @@ -1,5 +1,5 @@ import logging -from typing import Optional +import typing from django.conf import settings from django.core.validators import MinLengthValidator @@ -13,6 +13,12 @@ from apps.telegram.models import TelegramMessage from common.insight_log.chatops_insight_logs import ChatOpsEvent, ChatOpsTypePlug, write_chatops_insight_log from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import ChannelFilter + + logger = logging.getLogger(__name__) @@ -31,6 +37,8 @@ def generate_public_primary_key_for_telegram_to_at_connector() -> str: class TelegramToOrganizationConnector(models.Model): + channel_filter: "RelatedManager['ChannelFilter']" + public_primary_key = models.CharField( max_length=20, validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)], @@ -60,7 +68,7 @@ class TelegramToOrganizationConnector(models.Model): return self.channel_chat_id is not None and self.discussion_group_chat_id is not None @classmethod - def get_channel_for_alert_group(cls, alert_group: AlertGroup) -> Optional["TelegramToOrganizationConnector"]: + def get_channel_for_alert_group(cls, alert_group: AlertGroup) -> typing.Optional["TelegramToOrganizationConnector"]: # TODO: add custom queryset dm_messages_exist = alert_group.telegram_messages.filter( ~Q(chat_id__startswith="-") diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index 749d5a5d..0afbbd1c 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -21,6 +21,13 @@ from common.public_primary_keys import generate_public_primary_key, increase_pub if typing.TYPE_CHECKING: from django.db.models.manager import RelatedManager + from apps.auth_token.models import ( + ApiAuthToken, + PluginAuthToken, + ScheduleExportAuthToken, + UserScheduleExportAuthToken, + ) + from apps.mobile_app.models import MobileAppAuthToken from apps.schedules.models import OnCallSchedule from apps.user_management.models import User @@ -69,8 +76,13 @@ class OrganizationManager(models.Manager): class Organization(MaintainableObject): - users: "RelatedManager['User']" + auth_tokens: "RelatedManager['ApiAuthToken']" + mobile_app_auth_tokens: "RelatedManager['MobileAppAuthToken']" oncall_schedules: "RelatedManager['OnCallSchedule']" + plugin_auth_tokens: "RelatedManager['PluginAuthToken']" + schedule_export_token: "RelatedManager['ScheduleExportAuthToken']" + user_schedule_export_token: "RelatedManager['UserScheduleExportAuthToken']" + users: "RelatedManager['User']" objects = OrganizationManager() objects_with_deleted = models.Manager() diff --git a/engine/apps/user_management/models/team.py b/engine/apps/user_management/models/team.py index 769c5baa..3393bfdc 100644 --- a/engine/apps/user_management/models/team.py +++ b/engine/apps/user_management/models/team.py @@ -1,3 +1,5 @@ +import typing + from django.conf import settings from django.core.validators import MinLengthValidator from django.db import models @@ -6,6 +8,11 @@ from apps.metrics_exporter.helpers import metrics_bulk_update_team_label_cache from apps.metrics_exporter.metrics_cache_manager import MetricsCacheManager from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import AlertGroupLogRecord + def generate_public_primary_key_for_team(): prefix = "T" @@ -76,6 +83,8 @@ class TeamManager(models.Manager): class Team(models.Model): + oncall_schedules: "RelatedManager['AlertGroupLogRecord']" + public_primary_key = models.CharField( max_length=20, validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)], diff --git a/engine/apps/user_management/models/user.py b/engine/apps/user_management/models/user.py index 778291dd..931f1bcb 100644 --- a/engine/apps/user_management/models/user.py +++ b/engine/apps/user_management/models/user.py @@ -21,6 +21,12 @@ from apps.api.permissions import ( from apps.schedules.tasks import drop_cached_ical_for_custom_events_for_organization from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import EscalationPolicy + from apps.auth_token.models import ApiAuthToken, ScheduleExportAuthToken, UserScheduleExportAuthToken + logger = logging.getLogger(__name__) @@ -137,6 +143,12 @@ class UserQuerySet(models.QuerySet): class User(models.Model): + auth_tokens: "RelatedManager['ApiAuthToken']" + escalation_policy_notify_queues: "RelatedManager['EscalationPolicy']" + last_notified_in_escalation_policies: "RelatedManager['EscalationPolicy']" + schedule_export_token: "RelatedManager['ScheduleExportAuthToken']" + user_schedule_export_token: "RelatedManager['UserScheduleExportAuthToken']" + objects = UserManager.from_queryset(UserQuerySet)() class Meta: diff --git a/engine/apps/webhooks/models/webhook.py b/engine/apps/webhooks/models/webhook.py index 97036576..d9992a5e 100644 --- a/engine/apps/webhooks/models/webhook.py +++ b/engine/apps/webhooks/models/webhook.py @@ -1,4 +1,5 @@ import json +import typing from json import JSONDecodeError import requests @@ -23,6 +24,11 @@ from common.jinja_templater import apply_jinja_template from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length +if typing.TYPE_CHECKING: + from django.db.models.manager import RelatedManager + + from apps.alerts.models import EscalationPolicy + WEBHOOK_FIELD_PLACEHOLDER = "****************" @@ -54,6 +60,8 @@ class WebhookManager(models.Manager): class Webhook(models.Model): + escalation_policies: "RelatedManager['EscalationPolicy']" + objects = WebhookManager() objects_with_deleted = models.Manager() diff --git a/engine/pyproject.toml b/engine/pyproject.toml index 12c0cabd..a3acaeca 100644 --- a/engine/pyproject.toml +++ b/engine/pyproject.toml @@ -23,6 +23,28 @@ exclude = [ "tests/test_.*\\.py$", # test files "migrations/\\d*.*\\.py", # migration files ] +disable_error_code = [ + "abstract", + "annotation-unchecked", + "arg-type", + "assignment", + "attr-defined", + "call-arg", + "call-overload", + "has-type", + "import", + "index", + "misc", + "name-defined", + "no-redef", + "operator", + "return", + "return-value", + "typeddict-item", + "union-attr", + "valid-type", + "var-annotated", +] # mypy per-module options [[tool.mypy.overrides]] diff --git a/engine/requirements-dev.txt b/engine/requirements-dev.txt index 112438d9..4c9577a7 100644 --- a/engine/requirements-dev.txt +++ b/engine/requirements-dev.txt @@ -1,8 +1,8 @@ celery-types==0.18.0 django-filter-stubs==0.1.3 -django-stubs[compatible-mypy]==4.2.1 -djangorestframework-stubs[compatible-mypy]==3.14.1 -mypy==1.3.0 +django-stubs[compatible-mypy]==4.2.2 +djangorestframework-stubs[compatible-mypy]==3.14.2 +mypy==1.4.1 pre-commit==2.15.0 pytest==7.3.1 pytest-django==4.5.2