diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 693504b35445..1f69cbfbf7d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,23 +1,14 @@ repos: - - repo: https://github.com/PyCQA/isort - rev: 6.0.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.9.7 hooks: - - id: isort - name: isort (python) - - - repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - language_version: python3 - exclude: migrations - - - repo: https://github.com/pycqa/flake8 - rev: 7.1.2 - hooks: - - id: flake8 - name: flake8 - args: [--config, api/.flake8] + # Run the linter. + - id: ruff + args: [--config, api/pyproject.toml, --config, "src = ['api']", --fix] + # Run the formatter. + - id: ruff-format + args: [--config, api/pyproject.toml, --config, "src = ['api']"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 @@ -52,5 +43,5 @@ repos: args: ["-C", "api"] ci: - skip: [python-typecheck, isort] + skip: [python-typecheck] autoupdate_commit_msg: "ci: pre-commit autoupdate" diff --git a/api/.flake8 b/api/.flake8 deleted file mode 100644 index 888ea5ea697d..000000000000 --- a/api/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -max-line-length = 120 -max-complexity = 10 -exclude = .git,.venv,.direnv,__pycache__,*/migrations/* - -per-file-ignores = - # Need the * prefix to work with pre-commit which runs from the root of the repo - *app/settings/local.py: F403, F405 - *app/settings/production.py: F403, F405 - *app/settings/saas.py: F403, F405 diff --git a/api/api/openapi.py b/api/api/openapi.py index 024f8fa4d28b..7f6baadfb36d 100644 --- a/api/api/openapi.py +++ b/api/api/openapi.py @@ -2,7 +2,11 @@ from typing import Any from drf_yasg.inspectors import SwaggerAutoSchema # type: ignore[import-untyped] -from drf_yasg.openapi import SCHEMA_DEFINITIONS, Response, Schema # type: ignore[import-untyped] +from drf_yasg.openapi import ( # type: ignore[import-untyped] + SCHEMA_DEFINITIONS, + Response, + Schema, +) from pydantic import BaseModel from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue from pydantic_core import core_schema diff --git a/api/api/urls/v1.py b/api/api/urls/v1.py index 001d49901c8a..8c697e0898e0 100644 --- a/api/api/urls/v1.py +++ b/api/api/urls/v1.py @@ -1,10 +1,10 @@ -from app_analytics.views import SDKAnalyticsFlags, SelfHostedTelemetryAPIView from django.conf import settings from django.urls import include, path, re_path from drf_yasg import openapi # type: ignore[import-untyped] from drf_yasg.views import get_schema_view # type: ignore[import-untyped] from rest_framework import authentication, permissions, routers +from app_analytics.views import SDKAnalyticsFlags, SelfHostedTelemetryAPIView from environments.identities.traits.views import SDKTraits from environments.identities.views import SDKIdentities from environments.sdk.views import SDKEnvironmentAPIView diff --git a/api/api/urls/v2.py b/api/api/urls/v2.py index 5c2754d538a6..220a022636cd 100644 --- a/api/api/urls/v2.py +++ b/api/api/urls/v2.py @@ -1,6 +1,7 @@ -from app_analytics.views import SDKAnalyticsFlagsV2 from django.urls import re_path +from app_analytics.views import SDKAnalyticsFlagsV2 + app_name = "v2" urlpatterns = [ diff --git a/api/api_keys/models.py b/api/api_keys/models.py index 053449a0ad4b..1a986e36840e 100644 --- a/api/api_keys/models.py +++ b/api/api_keys/models.py @@ -1,8 +1,15 @@ from django.conf import settings from django.db import models -from django_lifecycle import BEFORE_UPDATE, LifecycleModelMixin, hook # type: ignore[import-untyped] +from django_lifecycle import ( # type: ignore[import-untyped] + BEFORE_UPDATE, + LifecycleModelMixin, + hook, +) from rest_framework_api_key.models import AbstractAPIKey, APIKeyManager -from softdelete.models import SoftDeleteManager, SoftDeleteObject # type: ignore[import-untyped] +from softdelete.models import ( # type: ignore[import-untyped] + SoftDeleteManager, + SoftDeleteObject, +) from organisations.models import Organisation diff --git a/api/api_keys/user.py b/api/api_keys/user.py index 26eca1a3454a..38e06fe72800 100644 --- a/api/api_keys/user.py +++ b/api/api_keys/user.py @@ -73,7 +73,10 @@ def is_group_admin(self, group_id: int) -> bool: return False def has_project_permission( - self, permission: str, project: "Project", tag_ids: typing.List[int] = None # type: ignore[assignment] + self, + permission: str, + project: "Project", + tag_ids: typing.List[int] = None, # type: ignore[assignment] ) -> bool: return project in self.get_permitted_projects(permission, tag_ids) @@ -95,7 +98,9 @@ def has_organisation_permission( ) def get_permitted_projects( - self, permission_key: str, tag_ids: typing.List[int] = None # type: ignore[assignment] + self, + permission_key: str, + tag_ids: typing.List[int] = None, # type: ignore[assignment] ) -> QuerySet["Project"]: return get_permitted_projects_for_master_api_key( self.key, permission_key, tag_ids diff --git a/api/app_analytics/analytics_db_service.py b/api/app_analytics/analytics_db_service.py index 841110111027..ce755f71efad 100644 --- a/api/app_analytics/analytics_db_service.py +++ b/api/app_analytics/analytics_db_service.py @@ -1,6 +1,11 @@ from datetime import date, datetime, timedelta from typing import List +from dateutil.relativedelta import relativedelta +from django.conf import settings +from django.db.models import Sum +from django.utils import timezone + from app_analytics.dataclasses import FeatureEvaluationData, UsageData from app_analytics.influxdb_wrapper import ( get_events_for_organisation, @@ -16,11 +21,6 @@ FeatureEvaluationBucket, Resource, ) -from dateutil.relativedelta import relativedelta -from django.conf import settings -from django.db.models import Sum -from django.utils import timezone - from environments.models import Environment from features.models import Feature from organisations.models import Organisation diff --git a/api/app_analytics/cache.py b/api/app_analytics/cache.py index c63a4a39fe9f..00cea0151f1d 100644 --- a/api/app_analytics/cache.py +++ b/api/app_analytics/cache.py @@ -1,11 +1,12 @@ from collections import defaultdict from threading import Lock -from app_analytics.tasks import track_feature_evaluation, track_request -from app_analytics.track import track_feature_evaluation_influxdb from django.conf import settings from django.utils import timezone +from app_analytics.tasks import track_feature_evaluation, track_request +from app_analytics.track import track_feature_evaluation_influxdb + class APIUsageCache: def __init__(self): # type: ignore[no-untyped-def] diff --git a/api/app_analytics/management/commands/migrate_analytics.py b/api/app_analytics/management/commands/migrate_analytics.py index cb01f1f41c9a..6d5f2197be5f 100644 --- a/api/app_analytics/management/commands/migrate_analytics.py +++ b/api/app_analytics/management/commands/migrate_analytics.py @@ -1,9 +1,10 @@ import argparse from typing import Any -from app_analytics.migrate_to_pg import migrate_feature_evaluations from django.core.management import BaseCommand +from app_analytics.migrate_to_pg import migrate_feature_evaluations + class Command(BaseCommand): def add_arguments(self, parser: argparse.ArgumentParser) -> None: diff --git a/api/app_analytics/management/commands/populate_buckets.py b/api/app_analytics/management/commands/populate_buckets.py index 14508a7db78a..3e52b6b29446 100644 --- a/api/app_analytics/management/commands/populate_buckets.py +++ b/api/app_analytics/management/commands/populate_buckets.py @@ -1,13 +1,14 @@ import argparse from typing import Any +from django.conf import settings +from django.core.management import BaseCommand + from app_analytics.constants import ANALYTICS_READ_BUCKET_SIZE from app_analytics.tasks import ( populate_api_usage_bucket, populate_feature_evaluation_bucket, ) -from django.conf import settings -from django.core.management import BaseCommand MINUTES_IN_DAY: int = 1440 diff --git a/api/app_analytics/middleware.py b/api/app_analytics/middleware.py index 299967a133a3..a6725407615c 100644 --- a/api/app_analytics/middleware.py +++ b/api/app_analytics/middleware.py @@ -1,6 +1,7 @@ +from django.conf import settings + from app_analytics.cache import APIUsageCache from app_analytics.tasks import track_request -from django.conf import settings from .models import Resource from .track import ( diff --git a/api/app_analytics/migrate_to_pg.py b/api/app_analytics/migrate_to_pg.py index f9ad270eace0..0e61a7b0696c 100644 --- a/api/app_analytics/migrate_to_pg.py +++ b/api/app_analytics/migrate_to_pg.py @@ -7,7 +7,7 @@ def migrate_feature_evaluations(migrate_till: int = 30) -> None: query_api = influxdb_client.query_api() for i in range(migrate_till): - range_start = f"-{i+1}d" + range_start = f"-{i + 1}d" range_stop = f"-{i}d" query = ( f'from (bucket: "{read_bucket}") ' diff --git a/api/app_analytics/models.py b/api/app_analytics/models.py index 51e766b903d0..e4cec5fc7a55 100644 --- a/api/app_analytics/models.py +++ b/api/app_analytics/models.py @@ -2,7 +2,11 @@ from django.core.exceptions import ValidationError from django.db import models -from django_lifecycle import BEFORE_CREATE, LifecycleModelMixin, hook # type: ignore[import-untyped] +from django_lifecycle import ( # type: ignore[import-untyped] + BEFORE_CREATE, + LifecycleModelMixin, + hook, +) class Resource(models.IntegerChoices): diff --git a/api/app_analytics/tasks.py b/api/app_analytics/tasks.py index 4e3ba89a1a2f..6980fbde06b5 100644 --- a/api/app_analytics/tasks.py +++ b/api/app_analytics/tasks.py @@ -1,7 +1,6 @@ from datetime import datetime, timedelta from typing import List, Tuple -from app_analytics.constants import ANALYTICS_READ_BUCKET_SIZE from django.conf import settings from django.db.models import Q, Sum from django.utils import timezone @@ -10,6 +9,7 @@ register_task_handler, ) +from app_analytics.constants import ANALYTICS_READ_BUCKET_SIZE from environments.models import Environment from .models import ( @@ -29,7 +29,9 @@ }, ) def populate_bucket( # type: ignore[no-untyped-def] - bucket_size: int, run_every: int, source_bucket_size: int = None # type: ignore[assignment] + bucket_size: int, + run_every: int, + source_bucket_size: int = None, # type: ignore[assignment] ): populate_api_usage_bucket(bucket_size, run_every, source_bucket_size) populate_feature_evaluation_bucket(bucket_size, run_every, source_bucket_size) @@ -139,7 +141,9 @@ def get_time_buckets( def populate_api_usage_bucket( # type: ignore[no-untyped-def] - bucket_size: int, run_every: int, source_bucket_size: int = None # type: ignore[assignment] + bucket_size: int, + run_every: int, + source_bucket_size: int = None, # type: ignore[assignment] ): for bucket_start_time, bucket_end_time in get_time_buckets(bucket_size, run_every): data = _get_api_usage_source_data( @@ -156,7 +160,9 @@ def populate_api_usage_bucket( # type: ignore[no-untyped-def] def populate_feature_evaluation_bucket( # type: ignore[no-untyped-def] - bucket_size: int, run_every: int, source_bucket_size: int = None # type: ignore[assignment] + bucket_size: int, + run_every: int, + source_bucket_size: int = None, # type: ignore[assignment] ): for bucket_start_time, bucket_end_time in get_time_buckets(bucket_size, run_every): data = _get_feature_evaluation_source_data( @@ -173,7 +179,9 @@ def populate_feature_evaluation_bucket( # type: ignore[no-untyped-def] def _get_api_usage_source_data( - process_from: datetime, process_till: datetime, source_bucket_size: int = None # type: ignore[assignment] + process_from: datetime, + process_till: datetime, + source_bucket_size: int = None, # type: ignore[assignment] ) -> dict: # type: ignore[type-arg] filters = Q( created_at__lte=process_till, @@ -193,7 +201,9 @@ def _get_api_usage_source_data( def _get_feature_evaluation_source_data( - process_from: datetime, process_till: datetime, source_bucket_size: int = None # type: ignore[assignment] + process_from: datetime, + process_till: datetime, + source_bucket_size: int = None, # type: ignore[assignment] ) -> dict: # type: ignore[type-arg] filters = Q( created_at__lte=process_till, diff --git a/api/app_analytics/track.py b/api/app_analytics/track.py index 8d582b9b0ee4..87e80d2ce555 100644 --- a/api/app_analytics/track.py +++ b/api/app_analytics/track.py @@ -2,12 +2,16 @@ import uuid import requests -from app_analytics.influxdb_wrapper import InfluxDBWrapper from django.conf import settings from django.core.cache import caches -from six.moves.urllib.parse import quote # type: ignore[import-untyped] # python 2/3 compatible urllib import -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from six.moves.urllib.parse import ( # type: ignore[import-untyped] + quote, # python 2/3 compatible urllib import +) +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) +from app_analytics.influxdb_wrapper import InfluxDBWrapper from environments.models import Environment from util.util import postpone diff --git a/api/app_analytics/views.py b/api/app_analytics/views.py index 1d738d8dbe97..6f0a3fb06225 100644 --- a/api/app_analytics/views.py +++ b/api/app_analytics/views.py @@ -1,12 +1,5 @@ import logging -from app_analytics.analytics_db_service import ( - get_total_events_count, - get_usage_data, -) -from app_analytics.cache import FeatureEvaluationCache -from app_analytics.tasks import track_feature_evaluation_v2 -from app_analytics.track import track_feature_evaluation_influxdb_v2 from django.conf import settings from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped] from rest_framework import status @@ -17,12 +10,19 @@ from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import Serializer -from telemetry.serializers import TelemetrySerializer +from app_analytics.analytics_db_service import ( + get_total_events_count, + get_usage_data, +) +from app_analytics.cache import FeatureEvaluationCache +from app_analytics.tasks import track_feature_evaluation_v2 +from app_analytics.track import track_feature_evaluation_influxdb_v2 from environments.authentication import EnvironmentKeyAuthentication from environments.permissions.permissions import EnvironmentKeyPermissions from features.models import FeatureState from organisations.models import Organisation +from telemetry.serializers import TelemetrySerializer from .permissions import UsageDataPermission from .serializers import ( diff --git a/api/audit/serializers.py b/api/audit/serializers.py index 766ee39206b3..9fc73104af73 100644 --- a/api/audit/serializers.py +++ b/api/audit/serializers.py @@ -68,7 +68,8 @@ def get_change_details( ) -> typing.List[typing.Dict[str, typing.Any]]: if history_record := instance.history_record: return AuditLogChangeDetailsSerializer( # type: ignore[return-value] - instance=history_record.get_change_details(), many=True # type: ignore[attr-defined] + instance=history_record.get_change_details(), # type: ignore[attr-defined] + many=True, ).data return [] diff --git a/api/audit/services.py b/api/audit/services.py index 24a0a366b6f3..e382a761a1e8 100644 --- a/api/audit/services.py +++ b/api/audit/services.py @@ -1,7 +1,6 @@ -from core.models import AbstractBaseAuditableModel - from audit.models import AuditLog from audit.related_object_type import RelatedObjectType +from core.models import AbstractBaseAuditableModel from features.models import Feature, FeatureState from features.versioning.models import EnvironmentFeatureVersion diff --git a/api/audit/signals.py b/api/audit/signals.py index 0f909a8ab96d..8a6cf1550302 100644 --- a/api/audit/signals.py +++ b/api/audit/signals.py @@ -100,7 +100,8 @@ def send_audit_log_event_to_datadog(sender, instance, **kwargs): # type: ignore return data_dog = DataDogWrapper( - base_url=data_dog_config.base_url, api_key=data_dog_config.api_key # type: ignore[arg-type] + base_url=data_dog_config.base_url, # type: ignore[arg-type] + api_key=data_dog_config.api_key, ) _track_event_async(instance, data_dog) # type: ignore[no-untyped-call] diff --git a/api/audit/tasks.py b/api/audit/tasks.py index e8648d6107bb..d01cb2dc5323 100644 --- a/api/audit/tasks.py +++ b/api/audit/tasks.py @@ -4,7 +4,9 @@ from django.contrib.auth import get_user_model from django.utils import timezone -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from task_processor.models import TaskPriority # type: ignore[import-untyped] from audit.constants import ( diff --git a/api/audit/views.py b/api/audit/views.py index ed9200f67f60..f9fca5ae91f9 100644 --- a/api/audit/views.py +++ b/api/audit/views.py @@ -27,7 +27,9 @@ decorator=swagger_auto_schema(query_serializer=AuditLogsQueryParamSerializer()), ) class _BaseAuditLogViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet # type: ignore[type-arg] + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, # type: ignore[type-arg] ): pagination_class = CustomPagination diff --git a/api/conftest.py b/api/conftest.py index 282527ca2374..3d43168a8eb8 100644 --- a/api/conftest.py +++ b/api/conftest.py @@ -149,7 +149,7 @@ def django_db_setup(request: pytest.FixtureRequest) -> None: from django.conf import settings for db_settings in settings.DATABASES.values(): - test_db_name = f'{TEST_DATABASE_PREFIX}{db_settings["NAME"]}_{test_db_suffix}' + test_db_name = f"{TEST_DATABASE_PREFIX}{db_settings['NAME']}_{test_db_suffix}" db_settings["NAME"] = test_db_name diff --git a/api/core/helpers.py b/api/core/helpers.py index 25da76775559..29160bf54c3c 100644 --- a/api/core/helpers.py +++ b/api/core/helpers.py @@ -7,7 +7,7 @@ INSECURE_DOMAINS = ("localhost", "127.0.0.1") -_insecure_domain_pattern = re.compile(rf'({"|".join(INSECURE_DOMAINS)})(:\d+)?') +_insecure_domain_pattern = re.compile(rf"({'|'.join(INSECURE_DOMAINS)})(:\d+)?") def get_current_site_url(request: HttpRequest | Request | None = None) -> str: diff --git a/api/core/management/commands/waitfordb.py b/api/core/management/commands/waitfordb.py index 811af752994e..91c37468bc26 100644 --- a/api/core/management/commands/waitfordb.py +++ b/api/core/management/commands/waitfordb.py @@ -45,7 +45,6 @@ def handle( database: str, **options: Any, ) -> None: - start = time.monotonic() wait_between_checks = 0.25 diff --git a/api/core/middleware/admin.py b/api/core/middleware/admin.py index 9535a9aa0299..8555ddab78dc 100644 --- a/api/core/middleware/admin.py +++ b/api/core/middleware/admin.py @@ -1,9 +1,10 @@ import logging -from core.helpers import get_ip_address_from_request from django.conf import settings from django.core.exceptions import PermissionDenied +from core.helpers import get_ip_address_from_request + logger = logging.getLogger(__name__) diff --git a/api/core/middleware/axes.py b/api/core/middleware/axes.py index 6879e7944675..90a331056c19 100644 --- a/api/core/middleware/axes.py +++ b/api/core/middleware/axes.py @@ -1,4 +1,6 @@ -from axes.middleware import AxesMiddleware as DefaultAxesMiddleware # type: ignore[import-untyped] +from axes.middleware import ( # type: ignore[import-untyped] + AxesMiddleware as DefaultAxesMiddleware, +) from django.conf import settings diff --git a/api/core/models.py b/api/core/models.py index 846620d5159a..89904bf80068 100644 --- a/api/core/models.py +++ b/api/core/models.py @@ -6,8 +6,14 @@ from django.db.models import Manager from django.forms import model_to_dict from django.http import HttpRequest -from simple_history.models import HistoricalRecords, ModelChange # type: ignore[import-untyped] -from softdelete.models import SoftDeleteManager, SoftDeleteObject # type: ignore[import-untyped] +from simple_history.models import ( # type: ignore[import-untyped] + HistoricalRecords, + ModelChange, +) +from softdelete.models import ( # type: ignore[import-untyped] + SoftDeleteManager, + SoftDeleteObject, +) from audit.related_object_type import RelatedObjectType @@ -44,7 +50,10 @@ def natural_key(self): # type: ignore[no-untyped-def] class SoftDeleteAwareHistoricalRecords(HistoricalRecords): # type: ignore[misc] def create_historical_record( - self, instance: models.Model, history_type: str, using: str = None # type: ignore[assignment] + self, + instance: models.Model, + history_type: str, + using: str = None, # type: ignore[assignment] ) -> None: if getattr(instance, "deleted_at", None) is not None and history_type == "~": # Don't create `update` historical record for soft-deleted objects diff --git a/api/core/redis_cluster.py b/api/core/redis_cluster.py index 20b0cb9f79ce..a0dc28a51527 100644 --- a/api/core/redis_cluster.py +++ b/api/core/redis_cluster.py @@ -26,7 +26,9 @@ from django.core.exceptions import ImproperlyConfigured from django_redis.client.default import DefaultClient # type: ignore[import-untyped] -from django_redis.exceptions import ConnectionInterrupted # type: ignore[import-untyped] +from django_redis.exceptions import ( # type: ignore[import-untyped] + ConnectionInterrupted, +) from django_redis.pool import ConnectionFactory # type: ignore[import-untyped] from redis.cluster import RedisCluster from redis.exceptions import RedisClusterException @@ -66,7 +68,9 @@ def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def] # Dynamically generate safe versions of methods for method_name in self.SAFE_METHODS: setattr( - self, method_name, self._safe_operation(getattr(super(), method_name)) # type: ignore[no-untyped-call] # noqa: E501 + self, + method_name, + self._safe_operation(getattr(super(), method_name)), # type: ignore[no-untyped-call] # noqa: E501 ) # Let's use our own connection factory here diff --git a/api/core/signals.py b/api/core/signals.py index 02b2003d8e9d..1c6514c21643 100644 --- a/api/core/signals.py +++ b/api/core/signals.py @@ -1,6 +1,5 @@ import logging -from core.models import AbstractBaseAuditableModel from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone @@ -8,6 +7,7 @@ from task_processor.task_run_method import TaskRunMethod # type: ignore[import-untyped] from audit import tasks +from core.models import AbstractBaseAuditableModel from users.models import FFAdminUser logger = logging.getLogger(__name__) diff --git a/api/custom_auth/jwt_cookie/signals.py b/api/custom_auth/jwt_cookie/signals.py index 6a5040500529..0372dca06cba 100644 --- a/api/custom_auth/jwt_cookie/signals.py +++ b/api/custom_auth/jwt_cookie/signals.py @@ -1,11 +1,12 @@ from typing import Any from urllib.parse import urlparse -from core.helpers import get_current_site_url from corsheaders.signals import check_request_enabled # type: ignore[import-untyped] from django.dispatch import receiver from django.http import HttpRequest +from core.helpers import get_current_site_url + @receiver(check_request_enabled) def cors_allow_current_site(request: HttpRequest, **kwargs: Any) -> bool: diff --git a/api/custom_auth/mfa/backends/application.py b/api/custom_auth/mfa/backends/application.py index 2d7746ce4abd..94b827c62175 100644 --- a/api/custom_auth/mfa/backends/application.py +++ b/api/custom_auth/mfa/backends/application.py @@ -15,7 +15,8 @@ def __init__(self, mfa_method: MFAMethod, config: Dict[str, Any]) -> None: def dispatch_message(self): # type: ignore[no-untyped-def] qr_link = self._totp.provisioning_uri( - self._mfa_method.user.email, settings.TRENCH_AUTH["APPLICATION_ISSUER_NAME"] # type: ignore[arg-type] + self._mfa_method.user.email, + settings.TRENCH_AUTH["APPLICATION_ISSUER_NAME"], # type: ignore[arg-type] ) data = { "qr_link": qr_link, diff --git a/api/custom_auth/mfa/trench/command/activate_mfa_method.py b/api/custom_auth/mfa/trench/command/activate_mfa_method.py index a38c379e6db3..70527d83f429 100644 --- a/api/custom_auth/mfa/trench/command/activate_mfa_method.py +++ b/api/custom_auth/mfa/trench/command/activate_mfa_method.py @@ -12,7 +12,9 @@ class ActivateMFAMethodCommand: def __init__( - self, mfa_model: Type[MFAMethod], backup_codes_generator: Callable # type: ignore[type-arg] + self, + mfa_model: Type[MFAMethod], + backup_codes_generator: Callable, # type: ignore[type-arg] ) -> None: self._mfa_model = mfa_model self._backup_codes_generator = backup_codes_generator diff --git a/api/custom_auth/mfa/trench/models.py b/api/custom_auth/mfa/trench/models.py index 0f313c99abf2..12e32d8f5123 100644 --- a/api/custom_auth/mfa/trench/models.py +++ b/api/custom_auth/mfa/trench/models.py @@ -12,7 +12,12 @@ TextField, ) from django.utils import timezone -from django_lifecycle import BEFORE_CREATE, BEFORE_SAVE, LifecycleModel, hook # type: ignore[import-untyped] +from django_lifecycle import ( # type: ignore[import-untyped] + BEFORE_CREATE, + BEFORE_SAVE, + LifecycleModel, + hook, +) from custom_auth.mfa.trench.exceptions import MFAMethodDoesNotExistError diff --git a/api/custom_auth/mfa/trench/responses.py b/api/custom_auth/mfa/trench/responses.py index 82cbde041e61..47b9b75467dd 100644 --- a/api/custom_auth/mfa/trench/responses.py +++ b/api/custom_auth/mfa/trench/responses.py @@ -19,5 +19,8 @@ def __init__( # type: ignore[no-untyped-def] **kwargs, ) -> None: super().__init__( # type: ignore[misc] - data={self._FIELD_ERROR: str(error)}, status=status, *args, **kwargs # type: ignore[arg-type] + data={self._FIELD_ERROR: str(error)}, + status=status, # type: ignore[arg-type] + *args, + **kwargs, ) diff --git a/api/custom_auth/mfa/trench/serializers.py b/api/custom_auth/mfa/trench/serializers.py index 91f85d689e60..65f85bbb8193 100644 --- a/api/custom_auth/mfa/trench/serializers.py +++ b/api/custom_auth/mfa/trench/serializers.py @@ -24,7 +24,8 @@ def __init__(self, mfa_method_name: str, user: User, *args, **kwargs) -> None: def validate_code(self, value: str) -> str: mfa_model = get_mfa_model() mfa = mfa_model.objects.get_by_name( - user_id=self._user.id, name=self._mfa_method_name # type: ignore[attr-defined] + user_id=self._user.id, # type: ignore[attr-defined] + name=self._mfa_method_name, ) self._validate_mfa_method(mfa) diff --git a/api/custom_auth/tasks.py b/api/custom_auth/tasks.py index 1e8b0f645249..3ce6c1cc0ce8 100644 --- a/api/custom_auth/tasks.py +++ b/api/custom_auth/tasks.py @@ -2,7 +2,9 @@ from django.conf import settings from django.utils import timezone -from task_processor.decorators import register_recurring_task # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_recurring_task, +) from custom_auth.models import UserPasswordResetRequest diff --git a/api/edge_api/identities/edge_request_forwarder.py b/api/edge_api/identities/edge_request_forwarder.py index d49f4db7895c..35f347ec0cce 100644 --- a/api/edge_api/identities/edge_request_forwarder.py +++ b/api/edge_api/identities/edge_request_forwarder.py @@ -1,12 +1,14 @@ import json import requests -from core.constants import FLAGSMITH_SIGNATURE_HEADER -from core.signing import sign_payload from django.conf import settings -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from task_processor.models import TaskPriority # type: ignore[import-untyped] +from core.constants import FLAGSMITH_SIGNATURE_HEADER +from core.signing import sign_payload from environments.dynamodb.migrator import IdentityMigrator @@ -47,7 +49,10 @@ def forward_trait_request( # type: ignore[no-untyped-def] def forward_trait_request_sync( # type: ignore[no-untyped-def] - request_method: str, headers: dict, project_id: int, payload: dict # type: ignore[type-arg] + request_method: str, + headers: dict, # type: ignore[type-arg] + project_id: int, + payload: dict, # type: ignore[type-arg] ): if not _should_forward(project_id): return diff --git a/api/edge_api/identities/export.py b/api/edge_api/identities/export.py index 0f5eeffd455a..ba8f3d9bfd71 100644 --- a/api/edge_api/identities/export.py +++ b/api/edge_api/identities/export.py @@ -38,7 +38,9 @@ def export_edge_identity_and_overrides( # noqa: C901 # export identity identity_export.append( export_edge_identity( - identifier, environment_api_key, item["created_date"] # type: ignore[arg-type] + identifier, # type: ignore[arg-type] + environment_api_key, + item["created_date"], # type: ignore[arg-type] ) ) # export traits @@ -199,7 +201,6 @@ def export_featurestate_value( def export_mv_featurestate_value( featurestate_uuid: str, mv_feature_option_uuid: int, percentage_allocation: float ) -> dict: # type: ignore[type-arg] - return { "model": "multivariate.multivariatefeaturestatevalue", "fields": { diff --git a/api/edge_api/identities/tasks.py b/api/edge_api/identities/tasks.py index 219ee72188ed..9415a7dd3fad 100644 --- a/api/edge_api/identities/tasks.py +++ b/api/edge_api/identities/tasks.py @@ -2,7 +2,9 @@ import typing from django.utils import timezone -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from task_processor.models import TaskPriority # type: ignore[import-untyped] from audit.models import AuditLog diff --git a/api/edge_api/identities/views.py b/api/edge_api/identities/views.py index 104c88bfb203..da93252720ca 100644 --- a/api/edge_api/identities/views.py +++ b/api/edge_api/identities/views.py @@ -125,7 +125,9 @@ def get_queryset(self): # type: ignore[no-untyped-def] search_query: typing.Optional[EdgeIdentitySearchData] if not (search_query := query_serializer.validated_data.get("q")): return EdgeIdentity.dynamo_wrapper.get_all_items( - self.kwargs["environment_api_key"], page_size, start_key # type: ignore[arg-type] + self.kwargs["environment_api_key"], + page_size, # type: ignore[arg-type] + start_key, ) return EdgeIdentity.dynamo_wrapper.search_items( diff --git a/api/environments/authentication.py b/api/environments/authentication.py index 2773891d1f50..45fba5c18fc0 100644 --- a/api/environments/authentication.py +++ b/api/environments/authentication.py @@ -1,9 +1,9 @@ -from core.request_origin import RequestOrigin from django.conf import settings from django.core.cache import caches from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed +from core.request_origin import RequestOrigin from environments.api_keys import SERVER_API_KEY_PREFIX from environments.models import Environment diff --git a/api/environments/dynamodb/wrappers/environment_wrapper.py b/api/environments/dynamodb/wrappers/environment_wrapper.py index 9d0588643126..c6a26ced3f15 100644 --- a/api/environments/dynamodb/wrappers/environment_wrapper.py +++ b/api/environments/dynamodb/wrappers/environment_wrapper.py @@ -76,7 +76,6 @@ def get_identity_overrides_by_environment_id( feature_id: int | None = None, feature_ids: None | list[int] = None, ) -> list[dict[str, Any]] | list[IdentityOverridesQueryResponse]: - try: if feature_ids is None: return list( diff --git a/api/environments/dynamodb/wrappers/identity_wrapper.py b/api/environments/dynamodb/wrappers/identity_wrapper.py index 4dd057dbcb15..67cf5c8d956e 100644 --- a/api/environments/dynamodb/wrappers/identity_wrapper.py +++ b/api/environments/dynamodb/wrappers/identity_wrapper.py @@ -174,7 +174,9 @@ def search_items( return self.query_items(**query_kwargs) def get_segment_ids( - self, identity_pk: str = None, identity_model: IdentityModel = None # type: ignore[assignment] + self, + identity_pk: str = None, # type: ignore[assignment] + identity_model: IdentityModel = None, # type: ignore[assignment] ) -> list: # type: ignore[type-arg] if not (identity_pk or identity_model): raise ValueError("Must provide one of identity_pk or identity_model.") diff --git a/api/environments/identities/models.py b/api/environments/identities/models.py index 0e3d274de320..9003fd65f3b0 100644 --- a/api/environments/identities/models.py +++ b/api/environments/identities/models.py @@ -157,7 +157,9 @@ def get_overridden_feature_states(self) -> dict[int, FeatureState]: return {fs.feature_id: fs for fs in self.identity_features.all()} def get_segments( - self, traits: typing.List[Trait] = None, overrides_only: bool = False # type: ignore[assignment] + self, + traits: typing.List[Trait] = None, # type: ignore[assignment] + overrides_only: bool = False, ) -> typing.List[Segment]: """ Get the list of segments this identity is a part of. diff --git a/api/environments/identities/traits/models.py b/api/environments/identities/traits/models.py index 86f17d97e734..367dd8e84274 100644 --- a/api/environments/identities/traits/models.py +++ b/api/environments/identities/traits/models.py @@ -1,9 +1,9 @@ import typing -from core.constants import BOOLEAN, FLOAT, INTEGER, STRING from django.core.exceptions import ObjectDoesNotExist from django.db import models +from core.constants import BOOLEAN, FLOAT, INTEGER, STRING from environments.identities.traits.exceptions import TraitPersistenceError diff --git a/api/environments/identities/traits/serializers.py b/api/environments/identities/traits/serializers.py index d11098709a17..c6c3eec8a6dc 100644 --- a/api/environments/identities/traits/serializers.py +++ b/api/environments/identities/traits/serializers.py @@ -1,6 +1,6 @@ -from core.constants import INTEGER from rest_framework import exceptions, serializers +from core.constants import INTEGER from environments.identities.models import Identity from environments.identities.serializers import IdentitySerializer from environments.identities.traits.fields import TraitValueField diff --git a/api/environments/identities/traits/views.py b/api/environments/identities/traits/views.py index dd0797dafc97..5b5c9e31034f 100644 --- a/api/environments/identities/traits/views.py +++ b/api/environments/identities/traits/views.py @@ -1,4 +1,7 @@ -from common.environments.permissions import MANAGE_IDENTITIES, VIEW_IDENTITIES # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_IDENTITIES, + VIEW_IDENTITIES, +) from django.conf import settings from django.core.exceptions import BadRequest from django.db.models import Q diff --git a/api/environments/identities/views.py b/api/environments/identities/views.py index a8bc37d4a649..e99992377519 100644 --- a/api/environments/identities/views.py +++ b/api/environments/identities/views.py @@ -1,9 +1,10 @@ import typing from collections import namedtuple -from common.environments.permissions import MANAGE_IDENTITIES, VIEW_IDENTITIES # type: ignore[import-untyped] -from core.constants import FLAGSMITH_UPDATED_AT_HEADER -from core.request_origin import RequestOrigin +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_IDENTITIES, + VIEW_IDENTITIES, +) from django.conf import settings from django.db.models import Q from django.utils import timezone @@ -15,6 +16,8 @@ from rest_framework.response import Response from app.pagination import CustomPagination +from core.constants import FLAGSMITH_UPDATED_AT_HEADER +from core.request_origin import RequestOrigin from edge_api.identities.edge_request_forwarder import forward_identity_request from environments.identities.models import Identity from environments.identities.serializers import ( diff --git a/api/environments/models.py b/api/environments/models.py index 90582242d3f8..b7f661fe8dd8 100644 --- a/api/environments/models.py +++ b/api/environments/models.py @@ -3,8 +3,6 @@ import uuid from copy import deepcopy -from core.models import abstract_base_auditable_model_factory -from core.request_origin import RequestOrigin from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.cache import caches @@ -28,6 +26,8 @@ ENVIRONMENT_UPDATED_MESSAGE, ) from audit.related_object_type import RelatedObjectType +from core.models import abstract_base_auditable_model_factory +from core.request_origin import RequestOrigin from environments.api_keys import ( generate_client_api_key, generate_server_api_key, @@ -172,7 +172,10 @@ def natural_key(self): # type: ignore[no-untyped-def] return (self.api_key,) def clone( - self, name: str, api_key: str = None, clone_feature_states_async: bool = False # type: ignore[assignment] + self, + name: str, + api_key: str = None, # type: ignore[assignment] + clone_feature_states_async: bool = False, ) -> "Environment": """ Creates a clone of the environment, related objects and returns the @@ -243,7 +246,9 @@ def get_from_cache(cls, api_key): # type: ignore[no-untyped-def] @classmethod def write_environments_to_dynamodb( - cls, environment_id: int = None, project_id: int = None # type: ignore[assignment] + cls, + environment_id: int = None, # type: ignore[assignment] + project_id: int = None, # type: ignore[assignment] ) -> None: # use a list to make sure the entire qs is evaluated up front environments_filter = ( @@ -289,7 +294,9 @@ def write_environments_to_dynamodb( environment_v2_wrapper.write_environments(environments) def get_feature_state( - self, feature_id: int, filter_kwargs: dict = None # type: ignore[type-arg,assignment] + self, + feature_id: int, + filter_kwargs: dict = None, # type: ignore[type-arg,assignment] ) -> typing.Optional[FeatureState]: """ Get the corresponding feature state in an environment for a given feature id. diff --git a/api/environments/permissions/permissions.py b/api/environments/permissions/permissions.py index 5c2ec1f3c359..8869c22025fe 100644 --- a/api/environments/permissions/permissions.py +++ b/api/environments/permissions/permissions.py @@ -1,7 +1,11 @@ import typing -from common.environments.permissions import VIEW_ENVIRONMENT # type: ignore[import-untyped] -from common.projects.permissions import CREATE_ENVIRONMENT # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + VIEW_ENVIRONMENT, +) +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, +) from django.db.models import Model, Q from rest_framework import exceptions from rest_framework.permissions import BasePermission, IsAuthenticated diff --git a/api/environments/sdk/serializers.py b/api/environments/sdk/serializers.py index 1be70b2218f8..a2fa9fdfcec7 100644 --- a/api/environments/sdk/serializers.py +++ b/api/environments/sdk/serializers.py @@ -1,9 +1,9 @@ import typing from collections import defaultdict -from core.constants import BOOLEAN, FLOAT, INTEGER, STRING from rest_framework import serializers +from core.constants import BOOLEAN, FLOAT, INTEGER, STRING from environments.identities.models import Identity from environments.identities.serializers import ( IdentifierOnlyIdentitySerializer, @@ -128,7 +128,8 @@ def create(self, validated_data): # type: ignore[no-untyped-def] class IdentifyWithTraitsSerializer( - HideSensitiveFieldsSerializerMixin, serializers.Serializer # type: ignore[type-arg] + HideSensitiveFieldsSerializerMixin, + serializers.Serializer, # type: ignore[type-arg] ): identifier = serializers.CharField( required=False, diff --git a/api/environments/sdk/services.py b/api/environments/sdk/services.py index 023c3ef97ebc..c7341e93e187 100644 --- a/api/environments/sdk/services.py +++ b/api/environments/sdk/services.py @@ -95,7 +95,7 @@ def get_transient_identifier(sdk_trait_data: list[SDKTraitData]) -> str: if sdk_trait_data: return hashlib.sha256( "".join( - f'{trait["trait_key"]}{trait_value["value"]}' + f"{trait['trait_key']}{trait_value['value']}" for trait in sorted(sdk_trait_data, key=itemgetter("trait_key")) if (trait_value := trait["trait_value"]) is not None ).encode(), diff --git a/api/environments/sdk/views.py b/api/environments/sdk/views.py index f9af8ff84616..acba8cf94bef 100644 --- a/api/environments/sdk/views.py +++ b/api/environments/sdk/views.py @@ -1,9 +1,9 @@ -from core.constants import FLAGSMITH_UPDATED_AT_HEADER from django.http import HttpRequest from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped] from rest_framework.response import Response from rest_framework.views import APIView +from core.constants import FLAGSMITH_UPDATED_AT_HEADER from environments.authentication import EnvironmentKeyAuthentication from environments.models import Environment from environments.permissions.permissions import EnvironmentKeyPermissions diff --git a/api/environments/tasks.py b/api/environments/tasks.py index f6c1bd968ce3..9e01a3021475 100644 --- a/api/environments/tasks.py +++ b/api/environments/tasks.py @@ -1,4 +1,6 @@ -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from task_processor.models import TaskPriority # type: ignore[import-untyped] from audit.models import AuditLog diff --git a/api/features/feature_external_resources/models.py b/api/features/feature_external_resources/models.py index 9e7f75874167..54699a0d76d2 100644 --- a/api/features/feature_external_resources/models.py +++ b/api/features/feature_external_resources/models.py @@ -138,7 +138,6 @@ def execute_before_save_actions(self) -> None: .get(id=self.feature.project.organisation_id) .github_config.first() ): - call_github_task( organisation_id=self.feature.project.organisation_id, # type: ignore[arg-type] type=GitHubEventType.FEATURE_EXTERNAL_RESOURCE_REMOVED.value, diff --git a/api/features/feature_external_resources/serializers.py b/api/features/feature_external_resources/serializers.py index 64241090257f..610c5c3a5835 100644 --- a/api/features/feature_external_resources/serializers.py +++ b/api/features/feature_external_resources/serializers.py @@ -6,7 +6,6 @@ class FeatureExternalResourceSerializer(serializers.ModelSerializer): # type: ignore[type-arg] - metadata = serializers.JSONField(required=False, allow_null=True, default=None) class Meta: diff --git a/api/features/feature_health/models.py b/api/features/feature_health/models.py index 709d3abcbde3..225d03419aa1 100644 --- a/api/features/feature_health/models.py +++ b/api/features/feature_health/models.py @@ -1,13 +1,13 @@ import typing -from core.models import ( - AbstractBaseExportableModel, - abstract_base_auditable_model_factory, -) from django.db import models from django.utils import timezone from audit.related_object_type import RelatedObjectType +from core.models import ( + AbstractBaseExportableModel, + abstract_base_auditable_model_factory, +) from features.feature_health.constants import ( FEATURE_HEALTH_EVENT_CREATED_FOR_ENVIRONMENT_MESSAGE, FEATURE_HEALTH_EVENT_CREATED_MESSAGE, diff --git a/api/features/feature_health/tasks.py b/api/features/feature_health/tasks.py index f8df628f31bc..81ed4bfd313b 100644 --- a/api/features/feature_health/tasks.py +++ b/api/features/feature_health/tasks.py @@ -1,4 +1,6 @@ -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from features.feature_health import services from features.models import Feature diff --git a/api/features/feature_segments/permissions.py b/api/features/feature_segments/permissions.py index c4d1380f2492..4914ac628ea9 100644 --- a/api/features/feature_segments/permissions.py +++ b/api/features/feature_segments/permissions.py @@ -1,6 +1,8 @@ from contextlib import suppress -from common.environments.permissions import MANAGE_SEGMENT_OVERRIDES # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_SEGMENT_OVERRIDES, +) from rest_framework.permissions import IsAuthenticated from environments.models import Environment diff --git a/api/features/feature_segments/serializers.py b/api/features/feature_segments/serializers.py index 0f968fede734..e53a58b18cc8 100644 --- a/api/features/feature_segments/serializers.py +++ b/api/features/feature_segments/serializers.py @@ -1,4 +1,6 @@ -from common.environments.permissions import MANAGE_SEGMENT_OVERRIDES # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_SEGMENT_OVERRIDES, +) from common.features.serializers import ( # type: ignore[import-untyped] CreateSegmentOverrideFeatureSegmentSerializer, ) @@ -57,9 +59,9 @@ def save(self, **kwargs) -> FeatureSegment: # type: ignore[no-untyped-def] priority: int | None = self.validated_data.get("priority", None) if kwargs["environment"].use_v2_feature_versioning: # pragma: no cover - assert ( - kwargs["environment_feature_version"] is not None - ), "Must provide environment_feature_version for environment using v2 versioning" + assert kwargs["environment_feature_version"] is not None, ( + "Must provide environment_feature_version for environment using v2 versioning" + ) if priority is not None: collision_qs = FeatureSegment.objects.filter( diff --git a/api/features/feature_states/models.py b/api/features/feature_states/models.py index 2ccbce3a6df6..9cbf1bb65e5e 100644 --- a/api/features/feature_states/models.py +++ b/api/features/feature_states/models.py @@ -35,5 +35,6 @@ def value(self) -> typing.Union[str, int, bool]: STRING: self.string_value, BOOLEAN: self.boolean_value, }.get( - self.type, self.string_value # type: ignore[arg-type] + self.type, # type: ignore[arg-type] + self.string_value, ) diff --git a/api/features/features_service.py b/api/features/features_service.py index 6a9da6a7a64a..6ec7f36a5fe7 100644 --- a/api/features/features_service.py +++ b/api/features/features_service.py @@ -104,7 +104,6 @@ def get_edge_overrides_data( if feature_state.feature_segment_id: env_feature_overrides_data.num_segment_overrides += 1 for identity_overrides_v2_list in get_overrides_data_future.result(): - for identity_override in identity_overrides_v2_list.identity_overrides: # Only override features that exists in core if identity_override.feature_state.feature.id in all_overrides_data: diff --git a/api/features/import_export/views.py b/api/features/import_export/views.py index a211ba9bad77..e9d7680a1dae 100644 --- a/api/features/import_export/views.py +++ b/api/features/import_export/views.py @@ -82,7 +82,8 @@ def download_feature_export(request: Request, feature_export_id: int) -> Respons ) response = Response( - json.loads(feature_export.data), content_type="application/json" # type: ignore[arg-type] + json.loads(feature_export.data), # type: ignore[arg-type] + content_type="application/json", ) response.headers["Content-Disposition"] = ( f"attachment; filename=feature_export.{feature_export_id}.json" @@ -111,7 +112,8 @@ def download_flagsmith_on_flagsmith(request: Request) -> Response: raise Http404("There is no present downloadable export.") response = Response( - json.loads(fof.feature_export.data), content_type="application/json" # type: ignore[arg-type] + json.loads(fof.feature_export.data), # type: ignore[arg-type] + content_type="application/json", ) response.headers["Content-Disposition"] = ( f"attachment; filename=flagsmith_on_flagsmith.{fof.id}.json" diff --git a/api/features/managers.py b/api/features/managers.py index 714767d03d2a..bac126a3ca5c 100644 --- a/api/features/managers.py +++ b/api/features/managers.py @@ -2,12 +2,12 @@ import typing -from core.models import UUIDNaturalKeyManagerMixin from django.db.models import Q, QuerySet from django.utils import timezone from ordered_model.models import OrderedModelManager # type: ignore[import-untyped] from softdelete.models import SoftDeleteManager # type: ignore[import-untyped] +from core.models import UUIDNaturalKeyManagerMixin from features.versioning.models import EnvironmentFeatureVersion if typing.TYPE_CHECKING: @@ -25,7 +25,10 @@ class FeatureManager(UUIDNaturalKeyManagerMixin, SoftDeleteManager): # type: ig class FeatureStateManager(UUIDNaturalKeyManagerMixin, SoftDeleteManager): # type: ignore[misc] def get_live_feature_states( # type: ignore[no-untyped-def] - self, environment: "Environment", additional_filters: Q = None, **kwargs # type: ignore[assignment] + self, + environment: "Environment", + additional_filters: Q = None, # type: ignore[assignment] + **kwargs, ) -> QuerySet["FeatureState"]: # TODO: replace additional_filters with just using kwargs in calling locations diff --git a/api/features/models.py b/api/features/models.py index b213e751f249..6ad7e1a60c5f 100644 --- a/api/features/models.py +++ b/api/features/models.py @@ -6,11 +6,6 @@ import uuid from copy import deepcopy -from core.models import ( - AbstractBaseExportableModel, - SoftDeleteExportableModel, - abstract_base_auditable_model_factory, -) from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ( @@ -48,6 +43,11 @@ ) from audit.related_object_type import RelatedObjectType from audit.tasks import create_segment_priorities_changed_audit_log +from core.models import ( + AbstractBaseExportableModel, + SoftDeleteExportableModel, + abstract_base_auditable_model_factory, +) from environments.identities.helpers import ( get_hashed_percentage_for_object_ids, ) @@ -148,7 +148,6 @@ def create_github_comment(self) -> None: and self.project.organisation.github_config.exists() and self.deleted_at ): - call_github_task( organisation_id=self.project.organisation_id, # type: ignore[arg-type] type=GitHubEventType.FLAG_DELETED.value, @@ -428,7 +427,6 @@ def create_github_comment(self) -> None: and self.feature.project.github_project.exists() and self.feature.project.organisation.github_config.exists() ): - call_github_task( self.feature.project.organisation_id, # type: ignore[arg-type] GitHubEventType.SEGMENT_OVERRIDE_DELETED.value, @@ -698,7 +696,8 @@ def generate_feature_state_value_data(self, value): # type: ignore[no-untyped-d } def get_feature_state_value_by_hash_key( - self, identity_hash_key: typing.Union[str, int] = None # type: ignore[assignment] + self, + identity_hash_key: typing.Union[str, int] = None, # type: ignore[assignment] ) -> typing.Any: feature_state_value = ( self.get_multivariate_feature_state_value(identity_hash_key) # type: ignore[arg-type] diff --git a/api/features/multivariate/models.py b/api/features/multivariate/models.py index 6044539ff117..2995aa1ef622 100644 --- a/api/features/multivariate/models.py +++ b/api/features/multivariate/models.py @@ -1,10 +1,6 @@ import typing import uuid -from core.models import ( - AbstractBaseExportableModel, - abstract_base_auditable_model_factory, -) from django.core.exceptions import ObjectDoesNotExist from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -17,6 +13,10 @@ ) from audit.related_object_type import RelatedObjectType +from core.models import ( + AbstractBaseExportableModel, + abstract_base_auditable_model_factory, +) from features.feature_states.models import AbstractBaseFeatureValueModel from features.feature_types import MULTIVARIATE, STANDARD diff --git a/api/features/multivariate/views.py b/api/features/multivariate/views.py index 71afb9940055..edcc90b3c51d 100644 --- a/api/features/multivariate/views.py +++ b/api/features/multivariate/views.py @@ -1,4 +1,7 @@ -from common.projects.permissions import CREATE_FEATURE, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_FEATURE, + VIEW_PROJECT, +) from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped] from rest_framework import viewsets from rest_framework.decorators import api_view diff --git a/api/features/permissions.py b/api/features/permissions.py index ab9111982789..55138be141a6 100644 --- a/api/features/permissions.py +++ b/api/features/permissions.py @@ -1,18 +1,21 @@ from contextlib import suppress -from common.environments.permissions import MANAGE_SEGMENT_OVERRIDES # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_SEGMENT_OVERRIDES, + UPDATE_FEATURE_STATE, + VIEW_ENVIRONMENT, +) from common.environments.permissions import ( TAG_SUPPORTED_PERMISSIONS as TAG_SUPPORTED_ENVIRONMENT_PERMISSIONS, ) -from common.environments.permissions import ( - UPDATE_FEATURE_STATE, - VIEW_ENVIRONMENT, +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_FEATURE, + DELETE_FEATURE, + VIEW_PROJECT, ) -from common.projects.permissions import CREATE_FEATURE, DELETE_FEATURE # type: ignore[import-untyped] from common.projects.permissions import ( TAG_SUPPORTED_PERMISSIONS as TAG_SUPPORTED_PROJECT_PERMISSIONS, ) -from common.projects.permissions import VIEW_PROJECT from django.shortcuts import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request @@ -118,7 +121,9 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ tag_ids = list(feature.tags.values_list("id", flat=True)) return request.user.has_environment_permission( # type: ignore[union-attr] - required_permission, environment, tag_ids=tag_ids # type: ignore[arg-type] + required_permission, + environment, + tag_ids=tag_ids, # type: ignore[arg-type] ) return False @@ -126,7 +131,10 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ return False def has_object_permission( - self, request: Request, view: GenericViewSet, obj: FeatureState # type: ignore[override,type-arg] + self, + request: Request, + view: GenericViewSet, # type: ignore[override,type-arg] + obj: FeatureState, ) -> bool: permission = ( MANAGE_SEGMENT_OVERRIDES if obj.feature_segment_id else UPDATE_FEATURE_STATE @@ -137,7 +145,9 @@ def has_object_permission( tag_ids = list(obj.feature.tags.values_list("id", flat=True)) return request.user.has_environment_permission( # type: ignore[union-attr] - permission, environment=obj.environment, tag_ids=tag_ids # type: ignore[arg-type] + permission, + environment=obj.environment, # type: ignore[arg-type] + tag_ids=tag_ids, # type: ignore[arg-type] ) diff --git a/api/features/serializers.py b/api/features/serializers.py index cf86efe6f745..f38654dd5a52 100644 --- a/api/features/serializers.py +++ b/api/features/serializers.py @@ -13,7 +13,9 @@ MetadataSerializer, SerializerWithMetadata, ) -from drf_writable_nested import WritableNestedModelSerializer # type: ignore[attr-defined] +from drf_writable_nested import ( # type: ignore[attr-defined] + WritableNestedModelSerializer, +) from drf_yasg.utils import swagger_serializer_method # type: ignore[import-untyped] from rest_framework import serializers from rest_framework.exceptions import PermissionDenied @@ -344,13 +346,10 @@ class UpdateFeatureSerializerWithMetadata(FeatureSerializerWithMetadata): """prevent users from changing certain values after creation""" class Meta(FeatureSerializerWithMetadata.Meta): - read_only_fields = ( - FeatureSerializerWithMetadata.Meta.read_only_fields - + ( # type: ignore[assignment] - "default_enabled", - "initial_value", - "name", - ) + read_only_fields = FeatureSerializerWithMetadata.Meta.read_only_fields + ( # type: ignore[assignment] + "default_enabled", + "initial_value", + "name", ) @@ -499,7 +498,6 @@ def save(self, **kwargs): # type: ignore[no-untyped-def] and feature_state.environment.project.github_project.exists() # type: ignore[union-attr] and feature_state.environment.project.organisation.github_config.exists() # type: ignore[union-attr] ): - call_github_task( organisation_id=feature_state.feature.project.organisation_id, # type: ignore[union-attr] type=GitHubEventType.FLAG_UPDATED.value, diff --git a/api/features/tasks.py b/api/features/tasks.py index 5982c66243d2..adfae9bfb677 100644 --- a/api/features/tasks.py +++ b/api/features/tasks.py @@ -1,6 +1,8 @@ import logging -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from environments.models import Webhook from features.models import Feature, FeatureState diff --git a/api/features/versioning/managers.py b/api/features/versioning/managers.py index 9f90f8e0000d..f194411da00c 100644 --- a/api/features/versioning/managers.py +++ b/api/features/versioning/managers.py @@ -46,7 +46,9 @@ def get_latest_versions_as_queryset( ) def _get_latest_versions( - self, environment_id: int = None, environment_api_key: str = None # type: ignore[assignment] + self, + environment_id: int = None, # type: ignore[assignment] + environment_api_key: str = None, # type: ignore[assignment] ) -> RawQuerySet: # type: ignore[type-arg] assert (environment_id or environment_api_key) and not ( environment_id and environment_api_key diff --git a/api/features/versioning/models.py b/api/features/versioning/models.py index 4428755c408c..a6d1ae80fcb2 100644 --- a/api/features/versioning/models.py +++ b/api/features/versioning/models.py @@ -4,18 +4,22 @@ import uuid from copy import deepcopy -from core.models import ( - SoftDeleteExportableModel, - abstract_base_auditable_model_factory, -) from django.conf import settings from django.db import models from django.db.models import Index from django.utils import timezone -from django_lifecycle import BEFORE_CREATE, LifecycleModelMixin, hook # type: ignore[import-untyped] +from django_lifecycle import ( # type: ignore[import-untyped] + BEFORE_CREATE, + LifecycleModelMixin, + hook, +) from softdelete.models import SoftDeleteObject # type: ignore[import-untyped] from api_keys.models import MasterAPIKey +from core.models import ( + SoftDeleteExportableModel, + abstract_base_auditable_model_factory, +) from features.versioning.dataclasses import Conflict from features.versioning.exceptions import FeatureVersioningError from features.versioning.managers import EnvironmentFeatureVersionManager @@ -142,9 +146,9 @@ def publish( live_from: datetime.datetime | None = None, persist: bool = True, ) -> None: - assert not ( - published_by and published_by_api_key - ), "Version must be published by either a user or a MasterAPIKey" + assert not (published_by and published_by_api_key), ( + "Version must be published by either a user or a MasterAPIKey" + ) now = timezone.now() diff --git a/api/features/versioning/permissions.py b/api/features/versioning/permissions.py index 4bb1925a54ee..d28828628ba7 100644 --- a/api/features/versioning/permissions.py +++ b/api/features/versioning/permissions.py @@ -32,11 +32,16 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ tag_ids = list(feature.tags.values_list("id", flat=True)) return request.user.has_environment_permission( # type: ignore[union-attr] - permission=required_permission, environment=environment, tag_ids=tag_ids # type: ignore[arg-type] + permission=required_permission, + environment=environment, + tag_ids=tag_ids, # type: ignore[arg-type] ) def has_object_permission( - self, request: Request, view: GenericViewSet, obj: EnvironmentFeatureVersion # type: ignore[override,type-arg] # noqa: E501 + self, + request: Request, + view: GenericViewSet, # type: ignore[override,type-arg] + obj: EnvironmentFeatureVersion, # noqa: E501 ) -> bool: if view.action == "retrieve": # permissions for retrieving handled in view.get_queryset @@ -49,7 +54,9 @@ def has_object_permission( tag_ids = list(obj.feature.tags.values_list("id", flat=True)) return request.user.has_environment_permission( # type: ignore[union-attr] - permission=required_permission, environment=obj.environment, tag_ids=tag_ids # type: ignore[arg-type] + permission=required_permission, + environment=obj.environment, + tag_ids=tag_ids, # type: ignore[arg-type] ) @@ -75,13 +82,18 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ ) def has_object_permission( - self, request: Request, view: GenericViewSet, obj: FeatureState # type: ignore[override,type-arg] + self, + request: Request, + view: GenericViewSet, # type: ignore[override,type-arg] + obj: FeatureState, ) -> bool: if view.action == "retrieve": return request.user.has_environment_permission( # type: ignore[union-attr] - permission=VIEW_ENVIRONMENT, environment=obj.environment # type: ignore[arg-type] + permission=VIEW_ENVIRONMENT, + environment=obj.environment, # type: ignore[arg-type] ) return request.user.has_environment_permission( # type: ignore[union-attr] - permission=UPDATE_FEATURE_STATE, environment=obj.environment # type: ignore[arg-type] + permission=UPDATE_FEATURE_STATE, + environment=obj.environment, # type: ignore[arg-type] ) diff --git a/api/features/versioning/serializers.py b/api/features/versioning/serializers.py index 89d8cc8b3795..a9af31d1240c 100644 --- a/api/features/versioning/serializers.py +++ b/api/features/versioning/serializers.py @@ -41,7 +41,6 @@ def save(self, **kwargs): # type: ignore[no-untyped-def] and feature_state.environment.project.github_project.exists() and feature_state.environment.project.organisation.github_config.exists() ): - call_github_task( organisation_id=feature_state.environment.project.organisation_id, type=GitHubEventType.FLAG_UPDATED.value, @@ -135,14 +134,11 @@ class EnvironmentFeatureVersionCreateSerializer(EnvironmentFeatureVersionSeriali ) class Meta(EnvironmentFeatureVersionSerializer.Meta): - fields = ( - EnvironmentFeatureVersionSerializer.Meta.fields - + ( # type: ignore[assignment] - "feature_states_to_create", - "feature_states_to_update", - "segment_ids_to_delete_overrides", - "publish_immediately", - ) + fields = EnvironmentFeatureVersionSerializer.Meta.fields + ( # type: ignore[assignment] + "feature_states_to_create", + "feature_states_to_update", + "segment_ids_to_delete_overrides", + "publish_immediately", ) non_model_fields = ( "feature_states_to_create", @@ -207,7 +203,9 @@ def create( return version # type: ignore[no-any-return] def _create_feature_state( - self, feature_state: dict, version: EnvironmentFeatureVersion # type: ignore[type-arg] + self, + feature_state: dict, # type: ignore[type-arg] + version: EnvironmentFeatureVersion, ) -> None: if not self._is_segment_override(feature_state): raise serializers.ValidationError( diff --git a/api/features/versioning/tasks.py b/api/features/versioning/tasks.py index b8bff9b5c320..dec0649f5800 100644 --- a/api/features/versioning/tasks.py +++ b/api/features/versioning/tasks.py @@ -5,7 +5,9 @@ from django.core.mail import send_mail from django.template.loader import render_to_string from django.utils import timezone -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from audit.constants import ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE from audit.models import AuditLog diff --git a/api/features/versioning/urls.py b/api/features/versioning/urls.py index c44d016db7fb..b34d54f9e15a 100644 --- a/api/features/versioning/urls.py +++ b/api/features/versioning/urls.py @@ -1,6 +1,8 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from rest_framework_nested.routers import NestedSimpleRouter # type: ignore[import-untyped] +from rest_framework_nested.routers import ( # type: ignore[import-untyped] + NestedSimpleRouter, +) from features.versioning.views import ( EnvironmentFeatureVersionFeatureStatesViewSet, diff --git a/api/features/versioning/versioning_service.py b/api/features/versioning/versioning_service.py index 4f38f71b406a..41eb8f9c6ce9 100644 --- a/api/features/versioning/versioning_service.py +++ b/api/features/versioning/versioning_service.py @@ -9,7 +9,8 @@ def get_environment_flags_queryset( - environment: Environment, feature_name: str = None # type: ignore[assignment] + environment: Environment, + feature_name: str = None, # type: ignore[assignment] ) -> QuerySet[FeatureState]: """ Get a queryset of the latest live versions of an environments' feature states diff --git a/api/features/versioning/views.py b/api/features/versioning/views.py index 78bdd96e9065..f21997ee9405 100644 --- a/api/features/versioning/views.py +++ b/api/features/versioning/views.py @@ -1,6 +1,8 @@ from datetime import timedelta -from common.environments.permissions import VIEW_ENVIRONMENT # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + VIEW_ENVIRONMENT, +) from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] from django.db.models import BooleanField, ExpressionWrapper, Q, QuerySet from django.shortcuts import get_object_or_404 @@ -156,7 +158,8 @@ def _apply_visibility_limits(self, queryset: QuerySet) -> QuerySet: # type: ign if ( subscription_metadata and ( - version_limit_days := subscription_metadata.feature_history_visibility_days + version_limit_days + := subscription_metadata.feature_history_visibility_days ) is not None ): @@ -247,7 +250,8 @@ def get_serializer_context(self): # type: ignore[no-untyped-def] return context def perform_create( - self, serializer: CustomCreateSegmentOverrideFeatureStateSerializer # type: ignore[override] + self, + serializer: CustomCreateSegmentOverrideFeatureStateSerializer, # type: ignore[override] ) -> None: serializer.save( feature=self.feature, @@ -256,7 +260,8 @@ def perform_create( ) def perform_update( - self, serializer: CustomCreateSegmentOverrideFeatureStateSerializer # type: ignore[override] + self, + serializer: CustomCreateSegmentOverrideFeatureStateSerializer, # type: ignore[override] ) -> None: serializer.save( feature=self.feature, diff --git a/api/features/views.py b/api/features/views.py index 647db460e010..4c38122bacfe 100644 --- a/api/features/views.py +++ b/api/features/views.py @@ -3,11 +3,7 @@ from datetime import timedelta from functools import reduce -from app_analytics.analytics_db_service import get_feature_evaluation_data -from app_analytics.influxdb_wrapper import get_multiple_event_list_for_feature from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] -from core.constants import FLAGSMITH_UPDATED_AT_HEADER -from core.request_origin import RequestOrigin from django.conf import settings from django.core.cache import caches from django.db.models import Max, Q, QuerySet @@ -26,6 +22,10 @@ from rest_framework.response import Response from app.pagination import CustomPagination +from app_analytics.analytics_db_service import get_feature_evaluation_data +from app_analytics.influxdb_wrapper import get_multiple_event_list_for_feature +from core.constants import FLAGSMITH_UPDATED_AT_HEADER +from core.request_origin import RequestOrigin from environments.authentication import EnvironmentKeyAuthentication from environments.identities.models import Identity from environments.identities.serializers import ( @@ -190,7 +190,8 @@ def paginate_queryset(self, queryset: QuerySet[Feature]) -> list[Feature]: # ty def perform_create(self, serializer): # type: ignore[no-untyped-def] serializer.save( - project_id=int(self.kwargs.get("project_pk")), user=self.request.user # type: ignore[arg-type] + project_id=int(self.kwargs.get("project_pk")), # type: ignore[arg-type] + user=self.request.user, ) def perform_update(self, serializer): # type: ignore[no-untyped-def] diff --git a/api/features/workflows/core/models.py b/api/features/workflows/core/models.py index ee4a5eda8c8a..e52517e9f768 100644 --- a/api/features/workflows/core/models.py +++ b/api/features/workflows/core/models.py @@ -3,12 +3,6 @@ import typing from datetime import datetime -from core.helpers import get_current_site_url -from core.models import ( - AbstractBaseExportableModel, - SoftDeleteExportableModel, - abstract_base_auditable_model_factory, -) from django.conf import settings from django.core.mail import send_mail from django.db import models @@ -36,6 +30,12 @@ create_feature_state_updated_by_change_request_audit_log, create_feature_state_went_live_audit_log, ) +from core.helpers import get_current_site_url +from core.models import ( + AbstractBaseExportableModel, + SoftDeleteExportableModel, + abstract_base_auditable_model_factory, +) from environments.tasks import rebuild_environment_document from features.models import FeatureState from features.versioning.models import EnvironmentFeatureVersion @@ -339,9 +339,10 @@ def live_from(self) -> datetime | None: return first_change_set.live_from # type: ignore[no-any-return] # Finally, we do the same for environment feature versions. - elif first_environment_feature_version := self.environment_feature_versions.order_by( - "live_from" - ).first(): + elif ( + first_environment_feature_version + := self.environment_feature_versions.order_by("live_from").first() + ): return first_environment_feature_version.live_from return None diff --git a/api/integrations/common/models.py b/api/integrations/common/models.py index 4c7a66b828a9..6a8ee0d813ba 100644 --- a/api/integrations/common/models.py +++ b/api/integrations/common/models.py @@ -1,6 +1,5 @@ import logging -from core.models import SoftDeleteExportableModel from django.db import models from django_lifecycle import ( # type: ignore[import-untyped] AFTER_SAVE, @@ -9,6 +8,7 @@ hook, ) +from core.models import SoftDeleteExportableModel from environments.models import Environment logger = logging.getLogger(__name__) diff --git a/api/integrations/common/serializers.py b/api/integrations/common/serializers.py index 87907549d597..ee6a1b0fb135 100644 --- a/api/integrations/common/serializers.py +++ b/api/integrations/common/serializers.py @@ -14,7 +14,8 @@ def create(self, validated_data): # type: ignore[no-untyped-def] return super().create(validated_data) def _get_existing_integration_model_obj( - self, validated_data: dict # type: ignore[type-arg] + self, + validated_data: dict, # type: ignore[type-arg] ) -> typing.Optional[Model]: """ Get the existing integration model (e.g. MixpanelConfig) for the given one-to-one related diff --git a/api/integrations/common/views.py b/api/integrations/common/views.py index 39efdab1bdca..4171414ade1d 100644 --- a/api/integrations/common/views.py +++ b/api/integrations/common/views.py @@ -1,4 +1,6 @@ -from common.environments.permissions import VIEW_ENVIRONMENT # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + VIEW_ENVIRONMENT, +) from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] from django.db.models import QuerySet from django.shortcuts import get_object_or_404 diff --git a/api/integrations/github/client.py b/api/integrations/github/client.py index de30f63f95a5..f86ec5a5d865 100644 --- a/api/integrations/github/client.py +++ b/api/integrations/github/client.py @@ -101,7 +101,6 @@ def build_paginated_response( def post_comment_to_github( installation_id: str, owner: str, repo: str, issue: str, body: str ) -> dict[str, Any]: - url = f"{GITHUB_API_URL}repos/{owner}/{repo}/issues/{issue}/comments" headers = build_request_headers(installation_id) payload = {"body": body} diff --git a/api/integrations/github/github.py b/api/integrations/github/github.py index dea5e876a483..330f8a674ad3 100644 --- a/api/integrations/github/github.py +++ b/api/integrations/github/github.py @@ -3,10 +3,10 @@ from dataclasses import asdict from typing import Any -from core.helpers import get_current_site_url from django.db.models import Q from django.utils.formats import get_format +from core.helpers import get_current_site_url from features.models import Feature, FeatureState, FeatureStateValue from integrations.github.constants import ( DELETED_FEATURE_TEXT, @@ -51,7 +51,6 @@ def tag_feature_per_github_event( event_type: str, action: str, metadata: dict[str, Any], repo_full_name: str ) -> None: - # Get Feature with external resource of type GITHUB and url matching the resource URL feature = Feature.objects.filter( Q(external_resources__type="GITHUB_PR") @@ -131,7 +130,6 @@ def generate_body_comment( feature_states: list[dict[str, typing.Any]], segment_name: str | None = None, ) -> str: - is_removed = event_type == GitHubEventType.FEATURE_EXTERNAL_RESOURCE_REMOVED.value is_segment_override_deleted = ( event_type == GitHubEventType.SEGMENT_OVERRIDE_DELETED.value @@ -250,7 +248,6 @@ def call_github_task( url: str | None, feature_states: typing.Union[list[typing.Any], list[typing.Any]] | None, ) -> None: - github_configuration = GithubConfiguration.objects.get( organisation_id=organisation_id ) diff --git a/api/integrations/github/models.py b/api/integrations/github/models.py index 7298c9f7fbf7..1aaa7680ba4f 100644 --- a/api/integrations/github/models.py +++ b/api/integrations/github/models.py @@ -1,7 +1,6 @@ import logging import re -from core.models import SoftDeleteExportableModel from django.db import models from django_lifecycle import ( # type: ignore[import-untyped] AFTER_CREATE, @@ -11,6 +10,7 @@ hook, ) +from core.models import SoftDeleteExportableModel from integrations.github.constants import GITHUB_TAG_COLOR from organisations.models import Organisation diff --git a/api/integrations/github/permissions.py b/api/integrations/github/permissions.py index 576e053e2238..ec14952ed275 100644 --- a/api/integrations/github/permissions.py +++ b/api/integrations/github/permissions.py @@ -8,7 +8,6 @@ class HasPermissionToGithubConfiguration(BasePermission): """ def has_permission(self, request, view): # type: ignore[no-untyped-def] - organisation_id = view.kwargs.get("organisation_pk") return request.user.belongs_to(organisation_id=int(organisation_id)) diff --git a/api/integrations/github/tasks.py b/api/integrations/github/tasks.py index 1ad5b8f702dd..963febf0157a 100644 --- a/api/integrations/github/tasks.py +++ b/api/integrations/github/tasks.py @@ -2,7 +2,9 @@ from typing import Any, List from urllib.parse import urlparse -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from features.models import Feature from integrations.github.client import post_comment_to_github @@ -25,7 +27,12 @@ def send_post_request(data: CallGithubData) -> None: installation_id = data.github_data.installation_id segment_name: str | None = data.github_data.segment_name body = generate_body_comment( - feature_name, event_type, project_id, feature_id, feature_states, segment_name # type: ignore[arg-type] + feature_name, + event_type, + project_id, # type: ignore[arg-type] + feature_id, + feature_states, # type: ignore[arg-type] + segment_name, ) if ( @@ -38,7 +45,11 @@ def send_post_request(data: CallGithubData) -> None: pathname = urlparse(url).path split_url = pathname.split("/") # type: ignore[arg-type] post_comment_to_github( - installation_id, split_url[1], split_url[2], split_url[4], body # type: ignore[arg-type] + installation_id, + split_url[1], # type: ignore[arg-type] + split_url[2], # type: ignore[arg-type] + split_url[4], # type: ignore[arg-type] + body, ) elif event_type == GitHubEventType.FEATURE_EXTERNAL_RESOURCE_REMOVED.value: @@ -46,7 +57,11 @@ def send_post_request(data: CallGithubData) -> None: pathname = urlparse(url).path # type: ignore[assignment] split_url = pathname.split("/") # type: ignore[arg-type] post_comment_to_github( - installation_id, split_url[1], split_url[2], split_url[4], body # type: ignore[arg-type] + installation_id, + split_url[1], # type: ignore[arg-type] + split_url[2], # type: ignore[arg-type] + split_url[4], # type: ignore[arg-type] + body, ) else: url = data.feature_external_resources[ @@ -55,13 +70,16 @@ def send_post_request(data: CallGithubData) -> None: pathname = urlparse(url).path split_url = pathname.split("/") # type: ignore[arg-type] post_comment_to_github( - installation_id, split_url[1], split_url[2], split_url[4], body # type: ignore[arg-type] + installation_id, + split_url[1], # type: ignore[arg-type] + split_url[2], # type: ignore[arg-type] + split_url[4], # type: ignore[arg-type] + body, ) @register_task_handler() # type: ignore[misc] def call_github_app_webhook_for_feature_state(event_data: dict[str, Any]) -> None: - from features.feature_external_resources.models import ( FeatureExternalResource, ) diff --git a/api/integrations/github/views.py b/api/integrations/github/views.py index 69c3c57708e7..623240ee5200 100644 --- a/api/integrations/github/views.py +++ b/api/integrations/github/views.py @@ -44,7 +44,6 @@ def github_auth_required(func): # type: ignore[no-untyped-def] @wraps(func) def wrapper(request, organisation_pk): # type: ignore[no-untyped-def] - if not GithubConfiguration.has_github_configuration( organisation_id=organisation_pk ): @@ -71,14 +70,14 @@ def wrapper(*args, **kwargs) -> Response: # type: ignore[no-untyped-def] return func(*args, **kwargs) # type: ignore[no-any-return] except ValueError as e: return Response( - data={"detail": (f"{error or default_error}" f" Error: {str(e)}")}, + data={"detail": (f"{error or default_error} Error: {str(e)}")}, content_type="application/json", status=status.HTTP_400_BAD_REQUEST, ) except requests.RequestException as e: logger.error(f"{error or default_error} Error: {str(e)}", exc_info=e) return Response( - data={"detail": (f"{error or default_error}" f" Error: {str(e)}")}, + data={"detail": (f"{error or default_error} Error: {str(e)}")}, content_type="application/json", status=status.HTTP_502_BAD_GATEWAY, ) @@ -141,7 +140,6 @@ def get_queryset(self): # type: ignore[no-untyped-def] raise ValidationError({"github_pk": ["Must be an integer"]}) def create(self, request, *args, **kwargs) -> Response | None: # type: ignore[no-untyped-def,return,override] - try: response: Response = super().create(request, *args, **kwargs) github_configuration: GithubConfiguration = GithubConfiguration.objects.get( diff --git a/api/integrations/grafana/mappers.py b/api/integrations/grafana/mappers.py index f5121b15786a..74c3abaee5f2 100644 --- a/api/integrations/grafana/mappers.py +++ b/api/integrations/grafana/mappers.py @@ -30,7 +30,7 @@ def _get_instance_tags_from_audit_log_record( if isinstance(instance, FeatureState): return [ f"feature:{(feature := instance.feature).name}", - f'flag:{"enabled" if instance.enabled else "disabled"}', + f"flag:{'enabled' if instance.enabled else 'disabled'}", *_get_feature_tags(feature), # type: ignore[has-type] ] diff --git a/api/integrations/launch_darkly/models.py b/api/integrations/launch_darkly/models.py index 764327854dc8..e637a52c9d4f 100644 --- a/api/integrations/launch_darkly/models.py +++ b/api/integrations/launch_darkly/models.py @@ -1,10 +1,10 @@ from typing import TYPE_CHECKING, Literal, Optional, TypedDict -from core.models import abstract_base_auditable_model_factory from django.db import models from typing_extensions import NotRequired from audit.related_object_type import RelatedObjectType +from core.models import abstract_base_auditable_model_factory from projects.models import Project if TYPE_CHECKING: diff --git a/api/integrations/launch_darkly/tasks.py b/api/integrations/launch_darkly/tasks.py index 868a22c58b22..a5f53c430434 100644 --- a/api/integrations/launch_darkly/tasks.py +++ b/api/integrations/launch_darkly/tasks.py @@ -1,4 +1,6 @@ -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from integrations.launch_darkly.models import LaunchDarklyImportRequest from integrations.launch_darkly.services import process_import_request diff --git a/api/integrations/launch_darkly/views.py b/api/integrations/launch_darkly/views.py index 96ef82579072..f43f162f9993 100644 --- a/api/integrations/launch_darkly/views.py +++ b/api/integrations/launch_darkly/views.py @@ -1,4 +1,7 @@ -from common.projects.permissions import CREATE_ENVIRONMENT, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, + VIEW_PROJECT, +) from django.db.models import QuerySet from django.db.utils import IntegrityError from django.shortcuts import get_object_or_404 diff --git a/api/integrations/lead_tracking/hubspot/client.py b/api/integrations/lead_tracking/hubspot/client.py index eafaa34b06c4..f0f75a90a3d0 100644 --- a/api/integrations/lead_tracking/hubspot/client.py +++ b/api/integrations/lead_tracking/hubspot/client.py @@ -10,7 +10,9 @@ SimplePublicObjectInput, SimplePublicObjectInputForCreate, ) -from hubspot.crm.contacts import BatchReadInputSimplePublicObjectId # type: ignore[import-untyped] +from hubspot.crm.contacts import ( # type: ignore[import-untyped] + BatchReadInputSimplePublicObjectId, +) from integrations.lead_tracking.hubspot.constants import ( HUBSPOT_FORM_ID, diff --git a/api/integrations/lead_tracking/hubspot/lead_tracker.py b/api/integrations/lead_tracking/hubspot/lead_tracker.py index 7ab4b8ef59d2..aa1cc3be5d93 100644 --- a/api/integrations/lead_tracking/hubspot/lead_tracker.py +++ b/api/integrations/lead_tracking/hubspot/lead_tracker.py @@ -66,7 +66,9 @@ def create_lead(self, user: FFAdminUser, organisation: Organisation = None) -> N ) def get_or_create_organisation_hubspot_id( - self, user: FFAdminUser, organisation: Organisation = None # type: ignore[assignment] + self, + user: FFAdminUser, + organisation: Organisation = None, # type: ignore[assignment] ) -> str: """ Return the Hubspot API's id for an organisation. diff --git a/api/integrations/lead_tracking/hubspot/tasks.py b/api/integrations/lead_tracking/hubspot/tasks.py index 2bf74f3f62a8..c16c804a1a67 100644 --- a/api/integrations/lead_tracking/hubspot/tasks.py +++ b/api/integrations/lead_tracking/hubspot/tasks.py @@ -1,5 +1,7 @@ from django.conf import settings -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) @register_task_handler() # type: ignore[misc] diff --git a/api/integrations/lead_tracking/pipedrive/client.py b/api/integrations/lead_tracking/pipedrive/client.py index b04e6f95abed..7805408ff4a0 100644 --- a/api/integrations/lead_tracking/pipedrive/client.py +++ b/api/integrations/lead_tracking/pipedrive/client.py @@ -26,7 +26,9 @@ def __init__( self.session = session or requests.Session() def create_organization( # type: ignore[no-untyped-def] - self, name: str, organization_fields: typing.Dict[str, typing.Any] = None # type: ignore[assignment] + self, + name: str, + organization_fields: typing.Dict[str, typing.Any] = None, # type: ignore[assignment] ): api_response_data = self._make_request( resource="organizations", @@ -126,7 +128,8 @@ def list_lead_labels(self) -> typing.List[PipedriveLeadLabel]: expected_status_code=200, ) return [ - PipedriveLeadLabel.from_response_data(label) for label in api_response_data # type: ignore[misc] + PipedriveLeadLabel.from_response_data(label) # type: ignore[misc] + for label in api_response_data ] def _make_request( diff --git a/api/integrations/lead_tracking/pipedrive/models.py b/api/integrations/lead_tracking/pipedrive/models.py index ed46b242c56f..3daa124f7fc1 100644 --- a/api/integrations/lead_tracking/pipedrive/models.py +++ b/api/integrations/lead_tracking/pipedrive/models.py @@ -19,7 +19,9 @@ class BasePipedriveModel: @classmethod def from_response_data( - cls, data: dict, schema: Schema = None # type: ignore[type-arg,assignment] + cls, + data: dict, # type: ignore[type-arg] + schema: Schema = None, # type: ignore[assignment] ) -> "BasePipedriveModel": schema = schema or cls.schema return cls(**schema.load(data)) diff --git a/api/integrations/segment/segment.py b/api/integrations/segment/segment.py index 30fe10e1edd2..7e39e8aca5b5 100644 --- a/api/integrations/segment/segment.py +++ b/api/integrations/segment/segment.py @@ -2,6 +2,7 @@ import typing from analytics.client import Client as SegmentClient # type: ignore[import-untyped] + from environments.identities.models import Identity from environments.identities.traits.models import Trait from features.models import FeatureState diff --git a/api/integrations/segment/serializers.py b/api/integrations/segment/serializers.py index edc67c2c8efc..2508240a893e 100644 --- a/api/integrations/segment/serializers.py +++ b/api/integrations/segment/serializers.py @@ -8,7 +8,6 @@ class SegmentConfigurationSerializer(BaseEnvironmentIntegrationModelSerializer): - base_url = serializers.ChoiceField( choices=[ constants.DEFAULT_BASE_URL, diff --git a/api/integrations/slack/models.py b/api/integrations/slack/models.py index c32c3ae2f71b..8ce5db4632a1 100644 --- a/api/integrations/slack/models.py +++ b/api/integrations/slack/models.py @@ -1,6 +1,6 @@ -from core.models import AbstractBaseExportableModel, SoftDeleteExportableModel from django.db import models +from core.models import AbstractBaseExportableModel, SoftDeleteExportableModel from projects.models import Project diff --git a/api/integrations/slack/views.py b/api/integrations/slack/views.py index 7f5c632883dc..1ac03107cbec 100644 --- a/api/integrations/slack/views.py +++ b/api/integrations/slack/views.py @@ -89,7 +89,8 @@ def slack_oauth_callback(self, request, environment_api_key): # type: ignore[no ) validate_state(request.GET.get("state"), request) # type: ignore[no-untyped-call] bot_token = SlackWrapper().get_bot_token( - code, self._get_slack_callback_url(environment_api_key) # type: ignore[no-untyped-call] + code, + self._get_slack_callback_url(environment_api_key), # type: ignore[no-untyped-call] ) SlackConfiguration.objects.update_or_create( diff --git a/api/integrations/webhook/models.py b/api/integrations/webhook/models.py index 978043ccad43..0f72143270be 100644 --- a/api/integrations/webhook/models.py +++ b/api/integrations/webhook/models.py @@ -11,7 +11,8 @@ class WebhookConfiguration( - LifecycleModelMixin, AbstractBaseSoftDeleteExportableWebhookModel # type: ignore[misc] + LifecycleModelMixin, # type: ignore[misc] + AbstractBaseSoftDeleteExportableWebhookModel, ): environment = models.OneToOneField( Environment, related_name="webhook_config", on_delete=models.CASCADE diff --git a/api/metadata/models.py b/api/metadata/models.py index 59a9c5f3f1cc..8cacbd9cf007 100644 --- a/api/metadata/models.py +++ b/api/metadata/models.py @@ -1,10 +1,10 @@ from urllib.parse import urlparse -from core.models import AbstractBaseExportableModel from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models +from core.models import AbstractBaseExportableModel from organisations.models import Organisation from .fields import GenericObjectID diff --git a/api/organisations/chargebee/tasks.py b/api/organisations/chargebee/tasks.py index de68c6cf70e1..7299a0fa857a 100644 --- a/api/organisations/chargebee/tasks.py +++ b/api/organisations/chargebee/tasks.py @@ -1,4 +1,6 @@ -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from organisations.chargebee.cache import ChargebeeCache diff --git a/api/organisations/invites/models.py b/api/organisations/invites/models.py index 6f1954ddf164..2095dff497a9 100644 --- a/api/organisations/invites/models.py +++ b/api/organisations/invites/models.py @@ -1,5 +1,3 @@ -from core.helpers import get_current_site_url -from core.models import AbstractBaseExportableModel from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.db import models @@ -13,6 +11,8 @@ ) from app.utils import create_hash +from core.helpers import get_current_site_url +from core.models import AbstractBaseExportableModel from organisations.invites.exceptions import InviteLinksDisabledError from organisations.models import Organisation, OrganisationRole from users.models import FFAdminUser, UserPermissionGroup @@ -33,7 +33,9 @@ class Meta: class InviteLink( - LifecycleModelMixin, AbstractBaseInviteModel, AbstractBaseExportableModel # type: ignore[misc] + LifecycleModelMixin, # type: ignore[misc] + AbstractBaseInviteModel, + AbstractBaseExportableModel, ): expires_at = models.DateTimeField( blank=True, diff --git a/api/organisations/models.py b/api/organisations/models.py index b49b834134dd..9e7670020c3c 100644 --- a/api/organisations/models.py +++ b/api/organisations/models.py @@ -3,7 +3,6 @@ from typing import Any -from core.models import SoftDeleteExportableModel from django.conf import settings from django.core.cache import caches from django.core.validators import MaxValueValidator, MinValueValidator @@ -19,6 +18,7 @@ from simple_history.models import HistoricalRecords # type: ignore[import-untyped] from app.utils import is_enterprise, is_saas +from core.models import SoftDeleteExportableModel from features.versioning.constants import DEFAULT_VERSION_LIMIT_DAYS from integrations.lead_tracking.hubspot.tasks import ( track_hubspot_lead, @@ -396,9 +396,7 @@ def _get_subscription_metadata_for_chargebee(self) -> ChargebeeObjMetadata: if self.organisation.has_subscription_information_cache(): # Getting the data from the subscription information cache because # data is guaranteed to be up to date by using a Chargebee webhook. - cb_metadata = ( - self.organisation.subscription_information_cache.as_chargebee_subscription_metadata() - ) + cb_metadata = self.organisation.subscription_information_cache.as_chargebee_subscription_metadata() else: cb_metadata = get_subscription_metadata_from_id(self.subscription_id) # type: ignore[assignment,arg-type] diff --git a/api/organisations/permissions/permissions.py b/api/organisations/permissions/permissions.py index 25b43dd29216..59a383a84e21 100644 --- a/api/organisations/permissions/permissions.py +++ b/api/organisations/permissions/permissions.py @@ -204,7 +204,10 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ return request.user.is_master_api_key_user # type: ignore[no-any-return,union-attr] def has_object_permission( # type: ignore[no-untyped-def] - self, request: Request, view: GenericViewSet, obj # type: ignore[override,type-arg] + self, + request: Request, + view: GenericViewSet, # type: ignore[override,type-arg] + obj, ) -> bool: organisation_pk = view.kwargs.get("organisation_pk") if isinstance(request.user, FFAdminUser): diff --git a/api/organisations/permissions/views.py b/api/organisations/permissions/views.py index a4fe71952b06..54f1aca1fd2b 100644 --- a/api/organisations/permissions/views.py +++ b/api/organisations/permissions/views.py @@ -54,9 +54,7 @@ class UserPermissionGroupOrganisationPermissionViewSet( def get_queryset(self): # type: ignore[no-untyped-def] return UserPermissionGroupOrganisationPermission.objects.select_related( # type: ignore[misc] "group" - ).filter( - organisation_id=self.kwargs.get("organisation_pk") - ) + ).filter(organisation_id=self.kwargs.get("organisation_pk")) def get_serializer_class(self): # type: ignore[no-untyped-def] return {"list": UserPermissionGroupOrganisationPermissionListSerializer}.get( diff --git a/api/organisations/subscription_info_cache.py b/api/organisations/subscription_info_cache.py index f3006198e1ba..ec7025ea5f85 100644 --- a/api/organisations/subscription_info_cache.py +++ b/api/organisations/subscription_info_cache.py @@ -1,10 +1,11 @@ import typing from datetime import timedelta -from app_analytics.influxdb_wrapper import get_top_organisations from django.conf import settings from django.utils import timezone +from app_analytics.influxdb_wrapper import get_top_organisations + from .chargebee import get_subscription_metadata_from_id # type: ignore[attr-defined] from .models import Organisation, OrganisationSubscriptionInformationCache from .subscriptions.constants import CHARGEBEE, SubscriptionCacheEntity diff --git a/api/organisations/task_helpers.py b/api/organisations/task_helpers.py index 6f304533efa1..519b4e2ccae4 100644 --- a/api/organisations/task_helpers.py +++ b/api/organisations/task_helpers.py @@ -1,14 +1,14 @@ import logging from datetime import timedelta -from app_analytics.influxdb_wrapper import get_current_api_usage -from core.helpers import get_current_site_url from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.mail import send_mail from django.template.loader import render_to_string from django.utils import timezone +from app_analytics.influxdb_wrapper import get_current_api_usage +from core.helpers import get_current_site_url from organisations.models import ( Organisation, OrganisationAPIUsageNotification, diff --git a/api/organisations/tasks.py b/api/organisations/tasks.py index ffc12999170b..4ca2d30bb431 100644 --- a/api/organisations/tasks.py +++ b/api/organisations/tasks.py @@ -2,7 +2,6 @@ import math from datetime import timedelta -from app_analytics.influxdb_wrapper import get_current_api_usage from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.mail import send_mail @@ -13,6 +12,7 @@ register_task_handler, ) +from app_analytics.influxdb_wrapper import get_current_api_usage from integrations.flagsmith.client import get_client from organisations import subscription_info_cache from organisations.chargebee import ( # type: ignore[attr-defined] diff --git a/api/organisations/urls.py b/api/organisations/urls.py index e7ad0386cc01..80913d8f3f4e 100644 --- a/api/organisations/urls.py +++ b/api/organisations/urls.py @@ -1,12 +1,12 @@ -from app_analytics.views import ( - get_usage_data_total_count_view, - get_usage_data_view, -) from django.conf import settings from django.urls import include, path, re_path from rest_framework_nested import routers # type: ignore[import-untyped] from api_keys.views import MasterAPIKeyViewSet +from app_analytics.views import ( + get_usage_data_total_count_view, + get_usage_data_view, +) from audit.views import OrganisationAuditLogViewSet from integrations.github.views import ( GithubConfigurationViewSet, @@ -155,7 +155,9 @@ ] if settings.LICENSING_INSTALLED: # pragma: no cover - from licensing.views import create_or_update_licence # type: ignore[import-not-found] + from licensing.views import ( # type: ignore[import-not-found] + create_or_update_licence, + ) urlpatterns.extend( [ diff --git a/api/organisations/views.py b/api/organisations/views.py index 97cc4ba93ff2..15ed8b75b202 100644 --- a/api/organisations/views.py +++ b/api/organisations/views.py @@ -4,11 +4,6 @@ import logging from datetime import timedelta -from app_analytics.influxdb_wrapper import ( - get_events_for_organisation, - get_multiple_event_list_for_organisation, -) -from core.helpers import get_current_site_url from dateutil.relativedelta import relativedelta from django.utils import timezone from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped] @@ -22,6 +17,11 @@ from rest_framework.response import Response from rest_framework.throttling import ScopedRateThrottle +from app_analytics.influxdb_wrapper import ( + get_events_for_organisation, + get_multiple_event_list_for_organisation, +) +from core.helpers import get_current_site_url from integrations.lead_tracking.hubspot.services import ( register_hubspot_tracker, ) diff --git a/api/permissions/permission_service.py b/api/permissions/permission_service.py index 3fbdfd6452a0..498f9ade01ac 100644 --- a/api/permissions/permission_service.py +++ b/api/permissions/permission_service.py @@ -60,7 +60,9 @@ def is_master_api_key_environment_admin( def get_permitted_projects_for_user( - user: "FFAdminUser", permission_key: str, tag_ids: List[int] = None # type: ignore[assignment] + user: "FFAdminUser", + permission_key: str, + tag_ids: List[int] = None, # type: ignore[assignment] ) -> QuerySet[Project]: """ Get all projects that the user has the given permissions for. @@ -78,7 +80,10 @@ def get_permitted_projects_for_user( be returned """ project_ids_from_base_filter = get_object_id_from_base_permission_filter( - user, Project, permission_key, tag_ids=tag_ids # type: ignore[arg-type] + user, + Project, # type: ignore[arg-type] + permission_key, + tag_ids=tag_ids, ) organisation_filter = Q( @@ -94,7 +99,9 @@ def get_permitted_projects_for_user( def get_permitted_projects_for_master_api_key( - master_api_key: "MasterAPIKey", permission_key: str, tag_ids: List[int] = None # type: ignore[assignment] + master_api_key: "MasterAPIKey", + permission_key: str, + tag_ids: List[int] = None, # type: ignore[assignment] ) -> QuerySet[Project]: if master_api_key.is_admin: return Project.objects.filter(organisation_id=master_api_key.organisation_id) @@ -135,7 +142,10 @@ def get_permitted_environments_for_user( return queryset environment_ids_from_base_filter = get_object_id_from_base_permission_filter( - user, Environment, permission_key, tag_ids=tag_ids # type: ignore[arg-type] + user, + Environment, # type: ignore[arg-type] + permission_key, + tag_ids=tag_ids, ) queryset = Environment.objects.filter( id__in=environment_ids_from_base_filter, project=project @@ -182,7 +192,10 @@ def user_has_organisation_permission( # compared to project and environment `get_base_permission_filter` # with allow_admin=True will not work for organisation base_filter = get_base_permission_filter( - user, Organisation, permission_key, allow_admin=False # type: ignore[arg-type] + user, + Organisation, # type: ignore[arg-type] + permission_key, + allow_admin=False, ) filter_ = base_filter & Q(id=organisation.id) @@ -255,7 +268,9 @@ def get_object_id_from_base_permission_filter( # type: ignore[no-untyped-def] def get_user_permission_filter( - user: "FFAdminUser", permission_key: str = None, allow_admin: bool = True # type: ignore[assignment] + user: "FFAdminUser", + permission_key: str = None, # type: ignore[assignment] + allow_admin: bool = True, ) -> Q: base_filter = Q(userpermission__user=user) permission_filter = Q(userpermission__admin=True) if allow_admin else Q() @@ -269,7 +284,9 @@ def get_user_permission_filter( def get_group_permission_filter( - user: "FFAdminUser", permission_key: str = None, allow_admin: bool = True # type: ignore[assignment] + user: "FFAdminUser", + permission_key: str = None, # type: ignore[assignment] + allow_admin: bool = True, ) -> Q: base_filter = Q(grouppermission__group__users=user) permission_filter = Q(grouppermission__admin=True) if allow_admin else Q() diff --git a/api/permissions/rbac_wrapper.py b/api/permissions/rbac_wrapper.py index e60d281e6466..2049fdf9f8af 100644 --- a/api/permissions/rbac_wrapper.py +++ b/api/permissions/rbac_wrapper.py @@ -8,8 +8,10 @@ from organisations.models import Organisation from projects.models import Project -if settings.IS_RBAC_INSTALLED: - from rbac.permission_service import get_role_permission_filter # type: ignore[import-not-found] +if settings.IS_RBAC_INSTALLED: # pragma: no cover + from rbac.permission_service import ( # type: ignore[import-not-found] + get_role_permission_filter, + ) from rbac.permissions_calculator import ( # type: ignore[import-not-found] RolePermissionData, get_roles_permission_data_for_environment, diff --git a/api/poetry.lock b/api/poetry.lock index 2209a347dbc2..1236a0934cba 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -169,34 +169,34 @@ files = [ [[package]] name = "black" -version = "24.3.0" +version = "25.1.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, + {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, + {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, + {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, + {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, + {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, + {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, + {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, + {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, + {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, + {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, + {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, + {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, + {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, + {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, + {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, + {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, + {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, + {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, + {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, + {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, + {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, + {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, ] [package.dependencies] @@ -208,7 +208,7 @@ platformdirs = ">=2" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -4313,6 +4313,34 @@ requests = ">=2.7,<3.0" [package.extras] test = ["flake8 (==3.7.9)", "mock (==2.0.0)", "pylint (==2.8.0)"] +[[package]] +name = "ruff" +version = "0.9.7" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4"}, + {file = "ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66"}, + {file = "ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef"}, + {file = "ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0"}, + {file = "ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606"}, + {file = "ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d"}, + {file = "ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c"}, + {file = "ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037"}, + {file = "ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6"}, +] + [[package]] name = "rx" version = "3.2.0" @@ -5170,4 +5198,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">3.11,<3.13" -content-hash = "60a5db12be1dfd07ad05960881b0491b1e5623331d50012a0b215b67c4026aa9" +content-hash = "7eb9cdf46b0be2785338bab0a697d9ae8210237e91fc2a7c69b36843beb9f8d0" diff --git a/api/projects/admin.py b/api/projects/admin.py index c17bb6b8a6e5..4919d025d3a9 100644 --- a/api/projects/admin.py +++ b/api/projects/admin.py @@ -81,6 +81,8 @@ def delete_all_segments(self, request: HttpRequest, queryset: QuerySet): # type Segment.objects.filter(project__in=queryset).delete() def has_delete_all_segments_permission( - self, request: HttpRequest, obj: Project = None # type: ignore[assignment] + self, + request: HttpRequest, + obj: Project = None, # type: ignore[assignment] ) -> bool: return request.user.is_superuser diff --git a/api/projects/managers.py b/api/projects/managers.py index c74bc170481f..8034c7dd1d77 100644 --- a/api/projects/managers.py +++ b/api/projects/managers.py @@ -1,6 +1,7 @@ -from core.models import AbstractBaseExportableModelManager from softdelete.models import SoftDeleteManager # type: ignore[import-untyped] +from core.models import AbstractBaseExportableModelManager + class ProjectManager(SoftDeleteManager, AbstractBaseExportableModelManager): # type: ignore[misc] def get_queryset(self): # type: ignore[no-untyped-def] diff --git a/api/projects/models.py b/api/projects/models.py index 5096c520261c..bf21604a4f95 100644 --- a/api/projects/models.py +++ b/api/projects/models.py @@ -3,7 +3,6 @@ import re -from core.models import SoftDeleteExportableModel from django.conf import settings from django.core.cache import caches from django.db import models @@ -17,6 +16,7 @@ hook, ) +from core.models import SoftDeleteExportableModel from environments.dynamodb import DynamoProjectMetadata from organisations.models import Organisation from permissions.models import ( @@ -50,7 +50,7 @@ class Project(LifecycleModelMixin, SoftDeleteExportableModel): # type: ignore[d ) hide_disabled_flags = models.BooleanField( default=False, - help_text="If true will exclude flags from SDK which are " "disabled", + help_text="If true will exclude flags from SDK which are disabled", ) enable_dynamo_db = models.BooleanField( default=False, diff --git a/api/projects/permissions.py b/api/projects/permissions.py index c28fbf135bb5..b42b0990bab0 100644 --- a/api/projects/permissions.py +++ b/api/projects/permissions.py @@ -34,7 +34,7 @@ def has_permission(self, request, view): # type: ignore[no-untyped-def] if ( subscription_metadata.projects and total_projects_created >= subscription_metadata.projects - and not getattr(request, "is_e2e", False) is True + and getattr(request, "is_e2e", False) is not True ): return False if organisation.restrict_project_create_to_admin: diff --git a/api/projects/tags/models.py b/api/projects/tags/models.py index be892af26316..2c3de7f32293 100644 --- a/api/projects/tags/models.py +++ b/api/projects/tags/models.py @@ -1,6 +1,6 @@ -from core.models import AbstractBaseExportableModel from django.db import models +from core.models import AbstractBaseExportableModel from projects.models import Project diff --git a/api/projects/tags/permissions.py b/api/projects/tags/permissions.py index 81a2306b156c..1171c31e04e4 100644 --- a/api/projects/tags/permissions.py +++ b/api/projects/tags/permissions.py @@ -1,4 +1,7 @@ -from common.projects.permissions import MANAGE_TAGS, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + MANAGE_TAGS, + VIEW_PROJECT, +) from rest_framework.permissions import BasePermission from projects.models import Project diff --git a/api/projects/tasks.py b/api/projects/tasks.py index d19a3b87e499..497e5fa9abf3 100644 --- a/api/projects/tasks.py +++ b/api/projects/tasks.py @@ -2,7 +2,9 @@ from django.conf import settings from django.db import transaction -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) @register_task_handler() # type: ignore[misc] diff --git a/api/projects/views.py b/api/projects/views.py index 41754b32a770..fa72e167c583 100644 --- a/api/projects/views.py +++ b/api/projects/views.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from common.projects.permissions import TAG_SUPPORTED_PERMISSIONS, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + TAG_SUPPORTED_PERMISSIONS, + VIEW_PROJECT, +) from django.conf import settings from django.utils.decorators import method_decorator from drf_yasg import openapi # type: ignore[import-untyped] diff --git a/api/pyproject.toml b/api/pyproject.toml index 3f302e8100d8..16a794e8730a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -12,26 +12,47 @@ readme = "readme.md" requires-python = ">=3.11, <3.13" dynamic = ["dependencies"] -[tool.black] +[tool.ruff] line-length = 88 -target-version = ['py310', 'py311'] -include = '\.pyi?$' -exclude = ''' -/( - \.eggs - | \.git - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | \.direnv - | _build - | buck-out - | build - | dist - | migrations -)/ -''' +indent-width = 4 +target-version = "py311" +extend-exclude = ["migrations"] + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +docstring-code-format = true + +# Set the line length limit used when formatting code snippets in +# docstrings. +docstring-code-line-length = "dynamic" + +[tool.ruff.lint] +# Establish parity with flake8 + isort +select = ["C901", "E4", "E7", "E9", "F", "I", "W"] +ignore = [] + +[tool.ruff.lint.per-file-ignores] +# Need the * prefix to work with pre-commit which runs from the root of the repo +"*app/settings/local.py"= ["F403", "F405"] +"*app/settings/production.py"= ["F403", "F405"] +"*app/settings/saas.py"= ["F403", "F405"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] [tool.coverage.report] # Regexes for lines to exclude from consideration @@ -44,66 +65,6 @@ exclude_also = [ [tool.coverage.run] omit = ["scripts/*"] -[tool.isort] -profile = "black" -known_first_party = [ - 'analytics', - 'app', - 'custom_auth', - 'environments', - 'integrations', - 'organisations', - 'projects', - 'segments', - 'tests', - 'users', - 'webhooks', - 'api', - 'audit', - 'e2etests', - 'features', - 'permissions', - 'util', -] -known_third_party = [ - '_pytest', - 'apiclient', - 'app_analytics', - 'axes', - 'chargebee', - 'core', - 'coreapi', - 'corsheaders', - 'dj_database_url', - 'django', - 'django_lifecycle', - 'djoser', - 'drf_writable_nested', - 'drf_yasg', - 'environs', - 'google', - 'influxdb_client', - 'ordered_model', - 'pyotp', - 'pytest', - 'pytz', - 'requests', - 'responses', - 'rest_framework', - 'rest_framework_nested', - 'rest_framework_recursive', - 'sentry_sdk', - 'shortuuid', - 'simple_history', - 'six', - 'task_processor', - 'telemetry', - 'tests', - 'trench', - 'whitenoise', -] -skip = ['migrations', '.venv', '.direnv'] - [tool.pytest.ini_options] addopts = ['--ds=app.settings.test', '-vvvv', '-p', 'no:warnings'] console_output_style = 'count' @@ -235,7 +196,6 @@ pep8 = "~1.7.1" autopep8 = "~2.0.1" pytest = "~7.2.1" pytest-django = "^4.8.0" -black = "~24.3.0" pytest-cov = "~4.1.0" datamodel-code-generator = "~0.25" requests-mock = "^1.11.0" @@ -255,6 +215,7 @@ types-influxdb-client = "^1.45.0.20241221" types-psycopg2 = "^2.9.21.20250121" types-python-dateutil = "^2.9.0.20241206" types-pytz = "^2025.1.0.20250204" +ruff = "^0.9.7" [build-system] requires = ["poetry>=2.0.0"] diff --git a/api/sales_dashboard/views.py b/api/sales_dashboard/views.py index 66a14cfa25d4..b47530f988da 100644 --- a/api/sales_dashboard/views.py +++ b/api/sales_dashboard/views.py @@ -2,10 +2,6 @@ from datetime import timedelta import re2 as re # type: ignore[import-untyped] -from app_analytics.influxdb_wrapper import ( - get_event_list_for_organisation, - get_events_for_organisation, -) from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required from django.core.serializers.json import DjangoJSONEncoder @@ -26,6 +22,10 @@ from django.views.generic import ListView from django.views.generic.edit import FormView +from app_analytics.influxdb_wrapper import ( + get_event_list_for_organisation, + get_events_for_organisation, +) from environments.dynamodb.migrator import IdentityMigrator from environments.identities.models import Identity from import_export.export import full_export diff --git a/api/segments/managers.py b/api/segments/managers.py index d09a9aa68f24..d1adddb94771 100644 --- a/api/segments/managers.py +++ b/api/segments/managers.py @@ -1,6 +1,7 @@ -from core.models import SoftDeleteExportableManager from django.db.models import F +from core.models import SoftDeleteExportableManager + class SegmentManager(SoftDeleteExportableManager): pass diff --git a/api/segments/models.py b/api/segments/models.py index d7f055fdc0da..f05cc5b9c8af 100644 --- a/api/segments/models.py +++ b/api/segments/models.py @@ -3,10 +3,6 @@ import uuid from copy import deepcopy -from core.models import ( - SoftDeleteExportableModel, - abstract_base_auditable_model_factory, -) from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError @@ -25,6 +21,10 @@ SEGMENT_UPDATED_MESSAGE, ) from audit.related_object_type import RelatedObjectType +from core.models import ( + SoftDeleteExportableModel, + abstract_base_auditable_model_factory, +) from features.models import Feature from metadata.models import Metadata from projects.models import Project @@ -235,7 +235,8 @@ def clean(self): # type: ignore[no-untyped-def] num_parents = sum(parent is not None for parent in parents) if num_parents != 1: raise ValidationError( - "Segment rule must have exactly one parent, %d found", num_parents # type: ignore[arg-type] + "Segment rule must have exactly one parent, %d found", + num_parents, # type: ignore[arg-type] ) def __str__(self): # type: ignore[no-untyped-def] @@ -319,7 +320,8 @@ def deep_clone(self, cloned_segment: Segment) -> "SegmentRule": class Condition( - SoftDeleteExportableModel, abstract_base_auditable_model_factory(["uuid"]) # type: ignore[misc] + SoftDeleteExportableModel, + abstract_base_auditable_model_factory(["uuid"]), # type: ignore[misc] ): history_record_class_path = "segments.models.HistoricalCondition" related_object_type = RelatedObjectType.SEGMENT @@ -370,7 +372,6 @@ def __str__(self): # type: ignore[no-untyped-def] def get_skip_create_audit_log(self) -> bool: try: - if self.rule.deleted_at: return True diff --git a/api/segments/permissions.py b/api/segments/permissions.py index 63779f91a60d..72294c6c2bf9 100644 --- a/api/segments/permissions.py +++ b/api/segments/permissions.py @@ -1,4 +1,7 @@ -from common.projects.permissions import MANAGE_SEGMENTS, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + MANAGE_SEGMENTS, + VIEW_PROJECT, +) from rest_framework.permissions import IsAuthenticated from projects.models import Project diff --git a/api/segments/tasks.py b/api/segments/tasks.py index 27efdd9264a2..5f8d9d1cee56 100644 --- a/api/segments/tasks.py +++ b/api/segments/tasks.py @@ -1,4 +1,6 @@ -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from segments.models import Segment diff --git a/api/segments/views.py b/api/segments/views.py index 1c1d0f1ae7da..fa246e309b76 100644 --- a/api/segments/views.py +++ b/api/segments/views.py @@ -1,7 +1,9 @@ import logging from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] -from common.segments.serializers import SegmentSerializer # type: ignore[import-untyped] +from common.segments.serializers import ( # type: ignore[import-untyped] + SegmentSerializer, +) from django.utils.decorators import method_decorator from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped] from rest_framework import viewsets diff --git a/api/sse/tasks.py b/api/sse/tasks.py index fedd57f5a2fe..6988fea52b56 100644 --- a/api/sse/tasks.py +++ b/api/sse/tasks.py @@ -2,7 +2,6 @@ from datetime import timedelta import requests -from app_analytics.influxdb_wrapper import influxdb_client from django.conf import settings from influxdb_client import Point, WriteOptions from task_processor.decorators import ( # type: ignore[import-untyped] @@ -10,6 +9,7 @@ register_task_handler, ) +from app_analytics.influxdb_wrapper import influxdb_client from environments.models import Environment from projects.models import Project from sse import sse_service diff --git a/api/telemetry/serializers.py b/api/telemetry/serializers.py index 1f401d9ae771..a6952b4addf8 100644 --- a/api/telemetry/serializers.py +++ b/api/telemetry/serializers.py @@ -1,8 +1,9 @@ -from app_analytics.influxdb_wrapper import InfluxDBWrapper -from core.helpers import get_ip_address_from_request from django.conf import settings from rest_framework import serializers +from app_analytics.influxdb_wrapper import InfluxDBWrapper +from core.helpers import get_ip_address_from_request + class TelemetrySerializer(serializers.Serializer): # type: ignore[type-arg] organisations = serializers.IntegerField() diff --git a/api/telemetry/telemetry.py b/api/telemetry/telemetry.py index 7be1b971e0da..e1666dc80ed0 100644 --- a/api/telemetry/telemetry.py +++ b/api/telemetry/telemetry.py @@ -1,6 +1,7 @@ import logging import requests + from telemetry.models import TelemetryData from telemetry.serializers import TelemetrySerializer diff --git a/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py b/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py index 563c0e7b0312..0ae7eab32a70 100644 --- a/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py +++ b/api/tests/integration/custom_auth/end_to_end/test_custom_auth_integration.py @@ -8,7 +8,10 @@ from django.urls import reverse from pytest_mock import MockerFixture from rest_framework import status -from rest_framework.test import APIClient, override_settings # type: ignore[attr-defined] +from rest_framework.test import ( # type: ignore[attr-defined] + APIClient, + override_settings, +) from rest_framework_simplejwt.tokens import SlidingToken from organisations.invites.models import Invite diff --git a/api/tests/integration/edge_api/identities/test_edge_identity_featurestates_viewset.py b/api/tests/integration/edge_api/identities/test_edge_identity_featurestates_viewset.py index 3c56f8f06a81..e77cac9e753a 100644 --- a/api/tests/integration/edge_api/identities/test_edge_identity_featurestates_viewset.py +++ b/api/tests/integration/edge_api/identities/test_edge_identity_featurestates_viewset.py @@ -5,7 +5,6 @@ from unittest import mock import pytest -from core.constants import BOOLEAN, INTEGER, STRING from django.urls import reverse from flag_engine.features.models import ( FeatureModel, @@ -21,6 +20,7 @@ from rest_framework.exceptions import NotFound from rest_framework.test import APIClient +from core.constants import BOOLEAN, INTEGER, STRING from edge_api.identities.models import ( # type: ignore[attr-defined] EdgeIdentity, IdentityFeaturesList, @@ -545,9 +545,9 @@ def test_edge_identities_update_featurestate( # type: ignore[no-untyped-def] expected_identity_document = copy.deepcopy(identity_document) # Next, let's modify the fs value that we updated - expected_identity_document["identity_features"][0][ - "feature_state_value" - ] = expected_feature_state_value + expected_identity_document["identity_features"][0]["feature_state_value"] = ( + expected_feature_state_value + ) # Next, let's update the enabled expected_identity_document["identity_features"][0]["enabled"] = expected_fs_enabled @@ -644,9 +644,9 @@ def test_edge_identities_update_mv_featurestate( # type: ignore[no-untyped-def] expected_identity_document = copy.deepcopy(identity_document) # Next, let's modify the fs value that we updated - expected_identity_document["identity_features"][2][ - "feature_state_value" - ] = expected_feature_state_value + expected_identity_document["identity_features"][2]["feature_state_value"] = ( + expected_feature_state_value + ) # Next, let's update the enabled expected_identity_document["identity_features"][2]["enabled"] = expected_fs_enabled @@ -880,9 +880,9 @@ def test_edge_identities_with_identifier_update_featurestate( # type: ignore[no # have correct updates # First, let's modify the fs value that we updated - identity_document["identity_features"][0][ - "feature_state_value" - ] = expected_feature_state_value + identity_document["identity_features"][0]["feature_state_value"] = ( + expected_feature_state_value + ) # Next, let's update the enabled identity_document["identity_features"][0]["enabled"] = expected_fs_enabled diff --git a/api/tests/integration/environments/identities/test_integration_identities_feature_states.py b/api/tests/integration/environments/identities/test_integration_identities_feature_states.py index 19aefe9dbb68..829fdb745e77 100644 --- a/api/tests/integration/environments/identities/test_integration_identities_feature_states.py +++ b/api/tests/integration/environments/identities/test_integration_identities_feature_states.py @@ -2,11 +2,12 @@ import typing import pytest -from core.constants import BOOLEAN, INTEGER, STRING from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient +from core.constants import BOOLEAN, INTEGER, STRING + @pytest.mark.parametrize( "segment_override_type, segment_override_value", diff --git a/api/tests/unit/app_analytics/test_analytics_db_service.py b/api/tests/unit/app_analytics/test_analytics_db_service.py index 50549a8e1f24..8447452c19e2 100644 --- a/api/tests/unit/app_analytics/test_analytics_db_service.py +++ b/api/tests/unit/app_analytics/test_analytics_db_service.py @@ -1,6 +1,11 @@ from datetime import date, datetime, timedelta import pytest +from django.conf import settings +from django.utils import timezone +from pytest_django.fixtures import SettingsWrapper +from pytest_mock import MockerFixture + from app_analytics.analytics_db_service import ( get_feature_evaluation_data, get_feature_evaluation_data_from_local_db, @@ -17,11 +22,6 @@ FeatureEvaluationBucket, Resource, ) -from django.conf import settings -from django.utils import timezone -from pytest_django.fixtures import SettingsWrapper -from pytest_mock import MockerFixture - from environments.models import Environment from features.models import Feature from organisations.models import ( diff --git a/api/tests/unit/app_analytics/test_middleware.py b/api/tests/unit/app_analytics/test_middleware.py index afdfd047c43b..5726c3a04159 100644 --- a/api/tests/unit/app_analytics/test_middleware.py +++ b/api/tests/unit/app_analytics/test_middleware.py @@ -1,10 +1,11 @@ import pytest -from app_analytics.middleware import APIUsageMiddleware -from app_analytics.models import Resource from django.test import RequestFactory from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture +from app_analytics.middleware import APIUsageMiddleware +from app_analytics.models import Resource + @pytest.mark.parametrize( "path, enum_resource_value", diff --git a/api/tests/unit/app_analytics/test_migrate_to_pg.py b/api/tests/unit/app_analytics/test_migrate_to_pg.py index 639310062778..739c1233f69b 100644 --- a/api/tests/unit/app_analytics/test_migrate_to_pg.py +++ b/api/tests/unit/app_analytics/test_migrate_to_pg.py @@ -1,10 +1,11 @@ import pytest -from app_analytics.migrate_to_pg import migrate_feature_evaluations -from app_analytics.models import FeatureEvaluationBucket from django.conf import settings from django.utils import timezone from pytest_mock import MockerFixture +from app_analytics.migrate_to_pg import migrate_feature_evaluations +from app_analytics.models import FeatureEvaluationBucket + @pytest.mark.skipif( "analytics" not in settings.DATABASES, diff --git a/api/tests/unit/app_analytics/test_models.py b/api/tests/unit/app_analytics/test_models.py index 82191c508767..53e6b09f301f 100644 --- a/api/tests/unit/app_analytics/test_models.py +++ b/api/tests/unit/app_analytics/test_models.py @@ -1,12 +1,13 @@ import pytest +from django.conf import settings +from django.core.exceptions import ValidationError +from django.utils import timezone + from app_analytics.models import ( APIUsageBucket, FeatureEvaluationBucket, Resource, ) -from django.conf import settings -from django.core.exceptions import ValidationError -from django.utils import timezone if "analytics" not in settings.DATABASES: pytest.skip( diff --git a/api/tests/unit/app_analytics/test_tasks.py b/api/tests/unit/app_analytics/test_tasks.py index 5f8e30fe2649..532321383ca4 100644 --- a/api/tests/unit/app_analytics/test_tasks.py +++ b/api/tests/unit/app_analytics/test_tasks.py @@ -1,6 +1,11 @@ from datetime import datetime import pytest +from django.conf import settings +from django.utils import timezone +from freezegun.api import FrozenDateTimeFactory +from pytest_django.fixtures import SettingsWrapper + from app_analytics.models import ( APIUsageBucket, APIUsageRaw, @@ -15,10 +20,6 @@ track_feature_evaluation, track_request, ) -from django.conf import settings -from django.utils import timezone -from freezegun.api import FrozenDateTimeFactory -from pytest_django.fixtures import SettingsWrapper if "analytics" not in settings.DATABASES: pytest.skip( @@ -208,11 +209,17 @@ def test_populate_feature_evaluation_bucket_15m(freezer: FrozenDateTimeFactory): # for the last two hours, i.e: from 9:09:47 to 7:10:47 for i in range(60 * 2): _create_feature_evaluation_event( # type: ignore[no-untyped-call] - environment_id, feature_name, 1, now - timezone.timedelta(minutes=1 * i) # type: ignore[attr-defined] + environment_id, + feature_name, + 1, + now - timezone.timedelta(minutes=1 * i), # type: ignore[attr-defined] ) # create events in some other environments _create_feature_evaluation_event( # type: ignore[no-untyped-call] - 999, feature_name, 1, now - timezone.timedelta(minutes=1 * i) # type: ignore[attr-defined] + 999, + feature_name, + 1, + now - timezone.timedelta(minutes=1 * i), # type: ignore[attr-defined] ) # create events for some other features _create_feature_evaluation_event( # type: ignore[no-untyped-call] @@ -449,16 +456,25 @@ def test_clean_up_old_analytics_data_removes_old_data( ) new_feature_evaluation_raw_data.append( _create_feature_evaluation_event( # type: ignore[no-untyped-call] - environment_id, "feature1", 1, now - timezone.timedelta(days=1) # type: ignore[attr-defined] + environment_id, + "feature1", + 1, + now - timezone.timedelta(days=1), # type: ignore[attr-defined] ) ) # FeatureEvaluationRaw data that should be removed _create_feature_evaluation_event( # type: ignore[no-untyped-call] - environment_id, "feature1", 1, now - timezone.timedelta(days=3) # type: ignore[attr-defined] + environment_id, + "feature1", + 1, + now - timezone.timedelta(days=3), # type: ignore[attr-defined] ) _create_feature_evaluation_event( # type: ignore[no-untyped-call] - environment_id, "feature1", 1, now - timezone.timedelta(days=2) # type: ignore[attr-defined] + environment_id, + "feature1", + 1, + now - timezone.timedelta(days=2), # type: ignore[attr-defined] ) # FeatureEvaluationBucket data that should not be removed diff --git a/api/tests/unit/app_analytics/test_unit_app_analytics_cache.py b/api/tests/unit/app_analytics/test_unit_app_analytics_cache.py index c4232ad53a8c..96925c63a849 100644 --- a/api/tests/unit/app_analytics/test_unit_app_analytics_cache.py +++ b/api/tests/unit/app_analytics/test_unit_app_analytics_cache.py @@ -1,10 +1,11 @@ -from app_analytics.cache import APIUsageCache, FeatureEvaluationCache -from app_analytics.models import Resource from django.utils import timezone from freezegun import freeze_time from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture +from app_analytics.cache import APIUsageCache, FeatureEvaluationCache +from app_analytics.models import Resource + def test_api_usage_cache( mocker: MockerFixture, diff --git a/api/tests/unit/app_analytics/test_unit_app_analytics_influxdb_wrapper.py b/api/tests/unit/app_analytics/test_unit_app_analytics_influxdb_wrapper.py index dadaa6b0f40c..e1da70b466e8 100644 --- a/api/tests/unit/app_analytics/test_unit_app_analytics_influxdb_wrapper.py +++ b/api/tests/unit/app_analytics/test_unit_app_analytics_influxdb_wrapper.py @@ -3,9 +3,17 @@ from unittest import mock from unittest.mock import MagicMock -import app_analytics import pytest from _pytest.monkeypatch import MonkeyPatch +from django.conf import settings +from django.utils import timezone +from influxdb_client.client.exceptions import InfluxDBError +from influxdb_client.rest import ApiException +from pytest_django.fixtures import SettingsWrapper +from pytest_mock import MockerFixture +from urllib3.exceptions import HTTPError + +import app_analytics from app_analytics.influxdb_wrapper import ( InfluxDBWrapper, build_filter_string, @@ -19,14 +27,6 @@ get_top_organisations, get_usage_data, ) -from django.conf import settings -from django.utils import timezone -from influxdb_client.client.exceptions import InfluxDBError -from influxdb_client.rest import ApiException -from pytest_django.fixtures import SettingsWrapper -from pytest_mock import MockerFixture -from urllib3.exceptions import HTTPError - from organisations.models import Organisation # Given @@ -190,7 +190,6 @@ def test_influx_db_query_when_get_events_list_then_query_api_called(monkeypatch) def test_influx_db_query_when_get_multiple_events_for_organisation_then_query_api_called( # type: ignore[no-untyped-def] # noqa: E501 monkeypatch, project_id, environment_id, expected_filters ): - expected_query = ( ( f'from(bucket:"{read_bucket}") ' diff --git a/api/tests/unit/app_analytics/test_unit_app_analytics_track.py b/api/tests/unit/app_analytics/test_unit_app_analytics_track.py index 2b73ced96789..e88abdfd5da5 100644 --- a/api/tests/unit/app_analytics/test_unit_app_analytics_track.py +++ b/api/tests/unit/app_analytics/test_unit_app_analytics_track.py @@ -1,12 +1,13 @@ from unittest import mock import pytest +from pytest_mock import MockerFixture + from app_analytics.track import ( track_feature_evaluation_influxdb, track_request_googleanalytics, track_request_influxdb, ) -from pytest_mock import MockerFixture @pytest.mark.parametrize( diff --git a/api/tests/unit/app_analytics/test_unit_app_analytics_views.py b/api/tests/unit/app_analytics/test_unit_app_analytics_views.py index f1dc9b5219cf..a87e3516f44f 100644 --- a/api/tests/unit/app_analytics/test_unit_app_analytics_views.py +++ b/api/tests/unit/app_analytics/test_unit_app_analytics_views.py @@ -2,14 +2,6 @@ from datetime import date, timedelta import pytest -from app_analytics.constants import ( - CURRENT_BILLING_PERIOD, - NINETY_DAY_PERIOD, - PREVIOUS_BILLING_PERIOD, -) -from app_analytics.dataclasses import UsageData -from app_analytics.models import FeatureEvaluationRaw -from app_analytics.views import SDKAnalyticsFlags from django.conf import settings from django.urls import reverse from django.utils import timezone @@ -18,6 +10,14 @@ from rest_framework import status from rest_framework.test import APIClient +from app_analytics.constants import ( + CURRENT_BILLING_PERIOD, + NINETY_DAY_PERIOD, + PREVIOUS_BILLING_PERIOD, +) +from app_analytics.dataclasses import UsageData +from app_analytics.models import FeatureEvaluationRaw +from app_analytics.views import SDKAnalyticsFlags from environments.identities.models import Identity from environments.models import Environment from features.models import Feature diff --git a/api/tests/unit/audit/test_unit_audit_views.py b/api/tests/unit/audit/test_unit_audit_views.py index a087e1b88402..96815f8e745a 100644 --- a/api/tests/unit/audit/test_unit_audit_views.py +++ b/api/tests/unit/audit/test_unit_audit_views.py @@ -46,7 +46,8 @@ def test_audit_log_can_be_filtered_by_environments( url = reverse("api-v1:audit-list") # When response = admin_client.get( - url, {"project": project.id, "environments": [audit_env.id]} # type: ignore[arg-type] + url, + {"project": project.id, "environments": [audit_env.id]}, # type: ignore[arg-type] ) # Then assert response.status_code == status.HTTP_200_OK diff --git a/api/tests/unit/core/middleware/test_unit_core_middleware_admin.py b/api/tests/unit/core/middleware/test_unit_core_middleware_admin.py index bc775a5dce17..42dd6c398ac9 100644 --- a/api/tests/unit/core/middleware/test_unit_core_middleware_admin.py +++ b/api/tests/unit/core/middleware/test_unit_core_middleware_admin.py @@ -1,10 +1,11 @@ from unittest import mock import pytest -from core.middleware.admin import AdminWhitelistMiddleware from django.core.exceptions import PermissionDenied from django.test import override_settings +from core.middleware.admin import AdminWhitelistMiddleware + allowed_ip_address = "10.0.0.1" not_allowed_ip_address = "11.0.0.1" diff --git a/api/tests/unit/core/middleware/test_unit_core_middleware_cache_control.py b/api/tests/unit/core/middleware/test_unit_core_middleware_cache_control.py index 2a726d7fb43c..144529fc4906 100644 --- a/api/tests/unit/core/middleware/test_unit_core_middleware_cache_control.py +++ b/api/tests/unit/core/middleware/test_unit_core_middleware_cache_control.py @@ -1,6 +1,7 @@ -from core.middleware.cache_control import NeverCacheMiddleware from django.http import HttpResponse +from core.middleware.cache_control import NeverCacheMiddleware + def test_NoCacheMiddleware_adds_cache_control_headers(mocker): # type: ignore[no-untyped-def] # Given diff --git a/api/tests/unit/core/test_helpers.py b/api/tests/unit/core/test_helpers.py index e57483bd7045..6f15fc9ba362 100644 --- a/api/tests/unit/core/test_helpers.py +++ b/api/tests/unit/core/test_helpers.py @@ -1,9 +1,10 @@ import typing import pytest -from core.helpers import get_current_site_url from django.contrib.sites.models import Site +from core.helpers import get_current_site_url + if typing.TYPE_CHECKING: from pytest_django.fixtures import DjangoAssertNumQueries, SettingsWrapper from pytest_mock import MockerFixture diff --git a/api/tests/unit/core/test_redis_cluster.py b/api/tests/unit/core/test_redis_cluster.py index 519a45703ed6..d23e85fc660a 100644 --- a/api/tests/unit/core/test_redis_cluster.py +++ b/api/tests/unit/core/test_redis_cluster.py @@ -1,9 +1,12 @@ import pytest -from core.redis_cluster import ClusterConnectionFactory, SafeRedisClusterClient -from django_redis.exceptions import ConnectionInterrupted # type: ignore[import-untyped] +from django_redis.exceptions import ( # type: ignore[import-untyped] + ConnectionInterrupted, +) from pytest_mock import MockerFixture from redis.exceptions import RedisClusterException +from core.redis_cluster import ClusterConnectionFactory, SafeRedisClusterClient + def test_cluster_connection_factory__connect_cache(mocker: MockerFixture): # type: ignore[no-untyped-def] # Given diff --git a/api/tests/unit/core/test_unit_core_management.py b/api/tests/unit/core/test_unit_core_management.py index 3cfb1df1fcc7..2f6f8cf665b3 100644 --- a/api/tests/unit/core/test_unit_core_management.py +++ b/api/tests/unit/core/test_unit_core_management.py @@ -30,9 +30,7 @@ def test_rollbackmigrationsappliedafter(mocker: MockerFixture) -> None: mocked_migration_recorder = mocker.patch( "core.management.commands.rollbackmigrationsappliedafter.MigrationRecorder" ) - mocked_migration_recorder.Migration.objects.filter.return_value.order_by.return_value = ( - migrations - ) + mocked_migration_recorder.Migration.objects.filter.return_value.order_by.return_value = migrations mocked_call_command = mocker.patch( "core.management.commands.rollbackmigrationsappliedafter.call_command" @@ -66,7 +64,8 @@ def test_rollbackmigrationsappliedafter_invalid_date(mocker: MockerFixture) -> N def test_rollbackmigrationsappliedafter_no_migrations( - mocker: MockerFixture, capsys: CaptureFixture # type: ignore[type-arg] + mocker: MockerFixture, + capsys: CaptureFixture, # type: ignore[type-arg] ) -> None: # Given dt_string = "2024-10-01" diff --git a/api/tests/unit/edge_api/identities/test_edge_request_forwarder.py b/api/tests/unit/edge_api/identities/test_edge_request_forwarder.py index 1eeaf8cba7e7..fcdc2ffbb0bf 100644 --- a/api/tests/unit/edge_api/identities/test_edge_request_forwarder.py +++ b/api/tests/unit/edge_api/identities/test_edge_request_forwarder.py @@ -1,8 +1,8 @@ import json import pytest -from core.constants import FLAGSMITH_SIGNATURE_HEADER +from core.constants import FLAGSMITH_SIGNATURE_HEADER from edge_api.identities.edge_request_forwarder import ( forward_identity_request, forward_trait_request, @@ -21,9 +21,9 @@ def test_forwarder_function_makes_no_request_if_migration_is_not_yet_done( # ty project_id = 1 mocked_migration_done = mocker.PropertyMock(return_value=False) - type(forwarder_mocked_migrator.return_value).is_migration_done = ( - mocked_migration_done - ) + type( + forwarder_mocked_migrator.return_value + ).is_migration_done = mocked_migration_done # When forwarder_function("GET", {}, project_id, None) @@ -47,9 +47,9 @@ def test_forward_identity_request_makes_correct_get_request( # type: ignore[no- headers = {"X-Environment-Key": api_key} mocked_migration_done = mocker.PropertyMock(return_value=True) - type(forwarder_mocked_migrator.return_value).is_migration_done = ( - mocked_migration_done - ) + type( + forwarder_mocked_migrator.return_value + ).is_migration_done = mocked_migration_done # When forward_identity_request("GET", headers, project_id, query_params) @@ -78,9 +78,9 @@ def test_forward_identity_request_makes_correct_post_request( # type: ignore[no headers = {"X-Environment-Key": api_key} mocked_migration_done = mocker.MagicMock(return_value=True) - type(forwarder_mocked_migrator.return_value).is_migration_done = ( - mocked_migration_done - ) + type( + forwarder_mocked_migrator.return_value + ).is_migration_done = mocked_migration_done # When forward_identity_request("POST", headers, project_id, request_data=request_data) @@ -113,9 +113,9 @@ def test_forward_trait_request_sync_makes_correct_post_request( # type: ignore[ headers = {"X-Environment-Key": api_key} mocked_migration_done = mocker.MagicMock(return_value=True) - type(forwarder_mocked_migrator.return_value).is_migration_done = ( - mocked_migration_done - ) + type( + forwarder_mocked_migrator.return_value + ).is_migration_done = mocked_migration_done # When forward_trait_request_sync("POST", headers, project_id, payload=request_data) diff --git a/api/tests/unit/edge_api/identities/test_permissions.py b/api/tests/unit/edge_api/identities/test_permissions.py index 5904487215a2..6359a494e3ed 100644 --- a/api/tests/unit/edge_api/identities/test_permissions.py +++ b/api/tests/unit/edge_api/identities/test_permissions.py @@ -1,4 +1,6 @@ -from common.environments.permissions import UPDATE_FEATURE_STATE # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + UPDATE_FEATURE_STATE, +) from edge_api.identities.permissions import ( EdgeIdentityWithIdentifierViewPermissions, diff --git a/api/tests/unit/environments/dynamodb/wrappers/test_unit_dynamodb_identity_wrapper.py b/api/tests/unit/environments/dynamodb/wrappers/test_unit_dynamodb_identity_wrapper.py index 965f0b9fe4ac..211ef39cd1c5 100644 --- a/api/tests/unit/environments/dynamodb/wrappers/test_unit_dynamodb_identity_wrapper.py +++ b/api/tests/unit/environments/dynamodb/wrappers/test_unit_dynamodb_identity_wrapper.py @@ -3,7 +3,6 @@ import pytest from boto3.dynamodb.conditions import Key -from core.constants import INTEGER from django.core.exceptions import ObjectDoesNotExist from flag_engine.identities.models import IdentityModel from flag_engine.segments.constants import IN @@ -11,6 +10,7 @@ from pytest_mock import MockerFixture from rest_framework.exceptions import NotFound +from core.constants import INTEGER from edge_api.identities.search import ( IDENTIFIER_ATTRIBUTE, EdgeIdentitySearchData, diff --git a/api/tests/unit/environments/identities/test_unit_identities_feature_states_views.py b/api/tests/unit/environments/identities/test_unit_identities_feature_states_views.py index 64a706d59222..350798ec9bc0 100644 --- a/api/tests/unit/environments/identities/test_unit_identities_feature_states_views.py +++ b/api/tests/unit/environments/identities/test_unit_identities_feature_states_views.py @@ -5,11 +5,11 @@ UPDATE_FEATURE_STATE, VIEW_ENVIRONMENT, ) -from core.constants import STRING from django.test import Client from django.urls import reverse from rest_framework import status +from core.constants import STRING from environments.identities.models import Identity from environments.models import Environment from features.models import Feature, FeatureState, FeatureStateValue @@ -123,7 +123,6 @@ def test_identity_clone_flag_states_from( environment: Environment, admin_client: Client, ) -> None: - def features_for_identity_clone_flag_states_from( project: Project, ) -> tuple[Feature, ...]: diff --git a/api/tests/unit/environments/identities/test_unit_identities_models.py b/api/tests/unit/environments/identities/test_unit_identities_models.py index 89f370c9fd59..ca3a889f0326 100644 --- a/api/tests/unit/environments/identities/test_unit_identities_models.py +++ b/api/tests/unit/environments/identities/test_unit_identities_models.py @@ -1,5 +1,4 @@ import pytest -from core.constants import FLOAT from django.utils import timezone from flag_engine.segments.constants import ( EQUAL, @@ -10,6 +9,7 @@ ) from pytest_django import DjangoAssertNumQueries +from core.constants import FLOAT from environments.identities.models import Identity from environments.identities.traits.models import Trait from environments.models import Environment diff --git a/api/tests/unit/environments/identities/test_unit_identities_views.py b/api/tests/unit/environments/identities/test_unit_identities_views.py index 306063fdb16e..a34996c239fa 100644 --- a/api/tests/unit/environments/identities/test_unit_identities_views.py +++ b/api/tests/unit/environments/identities/test_unit_identities_views.py @@ -4,8 +4,10 @@ from unittest import mock import pytest -from common.environments.permissions import MANAGE_IDENTITIES, VIEW_IDENTITIES # type: ignore[import-untyped] -from core.constants import FLAGSMITH_UPDATED_AT_HEADER, STRING +from common.environments.permissions import ( # type: ignore[import-untyped] + MANAGE_IDENTITIES, + VIEW_IDENTITIES, +) from django.test import override_settings from django.urls import reverse from django.utils import timezone @@ -15,6 +17,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.test import APIClient +from core.constants import FLAGSMITH_UPDATED_AT_HEADER, STRING from environments.identities.helpers import ( get_hashed_percentage_for_object_ids, ) diff --git a/api/tests/unit/environments/identities/traits/test_traits_views.py b/api/tests/unit/environments/identities/traits/test_traits_views.py index 76177acb3dd1..aaa68de2b96c 100644 --- a/api/tests/unit/environments/identities/traits/test_traits_views.py +++ b/api/tests/unit/environments/identities/traits/test_traits_views.py @@ -7,13 +7,13 @@ VIEW_IDENTITIES, ) from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] -from core.constants import INTEGER, STRING from django.test import override_settings from django.urls import reverse from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.test import APIClient +from core.constants import INTEGER, STRING from environments.identities.models import Identity from environments.identities.traits.constants import ( TRAIT_STRING_VALUE_MAX_LENGTH, diff --git a/api/tests/unit/environments/identities/traits/test_unit_traits_serializers.py b/api/tests/unit/environments/identities/traits/test_unit_traits_serializers.py index a80c2f7f9e20..1eb118958987 100644 --- a/api/tests/unit/environments/identities/traits/test_unit_traits_serializers.py +++ b/api/tests/unit/environments/identities/traits/test_unit_traits_serializers.py @@ -1,5 +1,4 @@ from core.constants import STRING - from environments.identities.traits.models import Trait from environments.sdk.serializers import SDKBulkCreateUpdateTraitSerializer diff --git a/api/tests/unit/environments/permissions/test_unit_environments_permissions.py b/api/tests/unit/environments/permissions/test_unit_environments_permissions.py index dea164495eec..087113e854ce 100644 --- a/api/tests/unit/environments/permissions/test_unit_environments_permissions.py +++ b/api/tests/unit/environments/permissions/test_unit_environments_permissions.py @@ -1,6 +1,8 @@ from unittest import mock -from common.projects.permissions import CREATE_ENVIRONMENT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, +) from environments.identities.models import Identity from environments.models import Environment diff --git a/api/tests/unit/environments/permissions/test_unit_environments_views.py b/api/tests/unit/environments/permissions/test_unit_environments_views.py index a685e444f2a9..7c1ec9bae487 100644 --- a/api/tests/unit/environments/permissions/test_unit_environments_views.py +++ b/api/tests/unit/environments/permissions/test_unit_environments_views.py @@ -1,7 +1,9 @@ import json import pytest -from common.environments.permissions import VIEW_ENVIRONMENT # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + VIEW_ENVIRONMENT, +) from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient diff --git a/api/tests/unit/environments/sdk/test_unit_sdk_serializers.py b/api/tests/unit/environments/sdk/test_unit_sdk_serializers.py index a795f03f8f87..90466c87d97c 100644 --- a/api/tests/unit/environments/sdk/test_unit_sdk_serializers.py +++ b/api/tests/unit/environments/sdk/test_unit_sdk_serializers.py @@ -1,8 +1,8 @@ import pytest -from core.request_origin import RequestOrigin from django.db.models import Q from pytest_mock import MockerFixture +from core.request_origin import RequestOrigin from environments.identities.models import Identity from environments.identities.traits.models import Trait from environments.models import Environment diff --git a/api/tests/unit/environments/test_unit_environments_models.py b/api/tests/unit/environments/test_unit_environments_models.py index ef61a3a67297..4340d7e6b6d7 100644 --- a/api/tests/unit/environments/test_unit_environments_models.py +++ b/api/tests/unit/environments/test_unit_environments_models.py @@ -5,8 +5,6 @@ from unittest.mock import MagicMock, Mock import pytest -from core.constants import STRING -from core.request_origin import RequestOrigin from django.test import override_settings from django.utils import timezone from mypy_boto3_dynamodb.service_resource import Table @@ -16,6 +14,8 @@ from audit.models import AuditLog from audit.related_object_type import RelatedObjectType +from core.constants import STRING +from core.request_origin import RequestOrigin from environments.identities.models import Identity from environments.models import ( Environment, diff --git a/api/tests/unit/environments/test_unit_environments_views.py b/api/tests/unit/environments/test_unit_environments_views.py index 3f1fbd574f12..5bc6407dd6c9 100644 --- a/api/tests/unit/environments/test_unit_environments_views.py +++ b/api/tests/unit/environments/test_unit_environments_views.py @@ -6,8 +6,9 @@ TAG_SUPPORTED_PERMISSIONS, VIEW_ENVIRONMENT, ) -from common.projects.permissions import CREATE_ENVIRONMENT # type: ignore[import-untyped] -from core.constants import STRING +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, +) from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.urls import reverse @@ -20,6 +21,7 @@ from api_keys.models import MasterAPIKey from audit.models import AuditLog, RelatedObjectType # type: ignore[attr-defined] +from core.constants import STRING from environments.identities.models import Identity from environments.identities.traits.models import Trait from environments.models import Environment, EnvironmentAPIKey, Webhook diff --git a/api/tests/unit/environments/test_unit_environments_views_sdk_environment.py b/api/tests/unit/environments/test_unit_environments_views_sdk_environment.py index 4b98ff7def2a..1be73c94c5e1 100644 --- a/api/tests/unit/environments/test_unit_environments_views_sdk_environment.py +++ b/api/tests/unit/environments/test_unit_environments_views_sdk_environment.py @@ -1,11 +1,11 @@ from typing import TYPE_CHECKING -from core.constants import FLAGSMITH_UPDATED_AT_HEADER from django.urls import reverse from flag_engine.segments.constants import EQUAL from rest_framework import status from rest_framework.test import APIClient +from core.constants import FLAGSMITH_UPDATED_AT_HEADER from environments.identities.models import Identity from environments.models import Environment, EnvironmentAPIKey from features.feature_types import MULTIVARIATE diff --git a/api/tests/unit/features/multivariate/test_migrations.py b/api/tests/unit/features/multivariate/test_migrations.py index 7fb5cde3903d..e8c1425c72fa 100644 --- a/api/tests/unit/features/multivariate/test_migrations.py +++ b/api/tests/unit/features/multivariate/test_migrations.py @@ -1,9 +1,10 @@ import typing import pytest -from core.constants import STRING from django.conf import settings +from core.constants import STRING + @pytest.mark.skipif( settings.SKIP_MIGRATION_TESTS is True, diff --git a/api/tests/unit/features/multivariate/test_unit_multivariate_models.py b/api/tests/unit/features/multivariate/test_unit_multivariate_models.py index cbf4feaf802a..00f6cda87cba 100644 --- a/api/tests/unit/features/multivariate/test_unit_multivariate_models.py +++ b/api/tests/unit/features/multivariate/test_unit_multivariate_models.py @@ -224,7 +224,6 @@ def test_multivariate_feature_state_value__get_skip_create_audit_log_for_feature multivariate_feature: MultivariateFeatureOption, environment: Environment, ) -> None: - # Given mvfsv = MultivariateFeatureStateValue.objects.filter( feature_state__environment=environment @@ -235,7 +234,8 @@ def test_multivariate_feature_state_value__get_skip_create_audit_log_for_feature # Then mvfsv_history_instance = MultivariateFeatureStateValue.history.filter( - id=mvfsv.id, history_type="-" # type: ignore[union-attr] + id=mvfsv.id, # type: ignore[union-attr] + history_type="-", ).first() assert mvfsv_history_instance.instance.get_skip_create_audit_log() is True diff --git a/api/tests/unit/features/multivariate/test_unit_multivariate_views.py b/api/tests/unit/features/multivariate/test_unit_multivariate_views.py index 0c736e938e63..fcf23228a9db 100644 --- a/api/tests/unit/features/multivariate/test_unit_multivariate_views.py +++ b/api/tests/unit/features/multivariate/test_unit_multivariate_views.py @@ -1,7 +1,10 @@ import uuid import pytest -from common.projects.permissions import CREATE_FEATURE, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_FEATURE, + VIEW_PROJECT, +) from django.urls import reverse from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] from rest_framework import status diff --git a/api/tests/unit/features/test_unit_feature_external_resources_views.py b/api/tests/unit/features/test_unit_feature_external_resources_views.py index bdbf4fa39770..8eaef36fb162 100644 --- a/api/tests/unit/features/test_unit_feature_external_resources_views.py +++ b/api/tests/unit/features/test_unit_feature_external_resources_views.py @@ -4,7 +4,9 @@ import pytest import responses import simplejson as json # type: ignore[import-untyped] -from common.environments.permissions import UPDATE_FEATURE_STATE # type: ignore[import-untyped] +from common.environments.permissions import ( # type: ignore[import-untyped] + UPDATE_FEATURE_STATE, +) from django.core.serializers.json import DjangoJSONEncoder from django.urls import reverse from django.utils.formats import get_format @@ -78,7 +80,6 @@ def test_create_feature_external_resource( post_request_mock: MagicMock, mock_github_client_generate_token: MagicMock, ) -> None: - # Given repository_owner_name = ( f"{github_repository.repository_owner}/{github_repository.repository_name}" @@ -195,7 +196,6 @@ def test_create_feature_external_resource_missing_tags( post_request_mock: MagicMock, mock_github_client_generate_token: MagicMock, ) -> None: - # Given Tag.objects.all().delete() repository_owner_name = ( @@ -788,7 +788,6 @@ def test_create_github_comment_using_v2_fails_on_wrong_params( post_request_mock: MagicMock, mocker: MockerFixture, ) -> None: - # Given environment_feature_version = EnvironmentFeatureVersion.objects.create( environment=environment_v2_versioning, feature=feature diff --git a/api/tests/unit/features/test_unit_features_models.py b/api/tests/unit/features/test_unit_features_models.py index ff4e41e1bc94..7c2e072c5a85 100644 --- a/api/tests/unit/features/test_unit_features_models.py +++ b/api/tests/unit/features/test_unit_features_models.py @@ -226,7 +226,6 @@ def test_cannot_create_duplicate_feature_state_in_an_environment( feature: Feature, environment: Environment, ) -> None: - # Given duplicate_feature_state = FeatureState( feature=feature, environment=environment, enabled=True diff --git a/api/tests/unit/features/test_unit_features_serializers.py b/api/tests/unit/features/test_unit_features_serializers.py index e5ff737b8d7a..f6d7d9bcbaf3 100644 --- a/api/tests/unit/features/test_unit_features_serializers.py +++ b/api/tests/unit/features/test_unit_features_serializers.py @@ -1,6 +1,6 @@ import pytest -from core.constants import STRING +from core.constants import STRING from features.models import FeatureState from features.multivariate.models import ( MultivariateFeatureOption, diff --git a/api/tests/unit/features/test_unit_features_views.py b/api/tests/unit/features/test_unit_features_views.py index 51d316039c09..18e34eb91692 100644 --- a/api/tests/unit/features/test_unit_features_views.py +++ b/api/tests/unit/features/test_unit_features_views.py @@ -6,7 +6,6 @@ import pytest import pytz -from app_analytics.dataclasses import FeatureEvaluationData from common.environments.permissions import ( # type: ignore[import-untyped] MANAGE_SEGMENT_OVERRIDES, UPDATE_FEATURE_STATE, @@ -16,7 +15,6 @@ CREATE_FEATURE, VIEW_PROJECT, ) -from core.constants import FLAGSMITH_UPDATED_AT_HEADER from django.conf import settings from django.forms import model_to_dict from django.urls import reverse @@ -29,12 +27,14 @@ from rest_framework import status from rest_framework.test import APIClient +from app_analytics.dataclasses import FeatureEvaluationData from audit.constants import ( FEATURE_DELETED_MESSAGE, IDENTITY_FEATURE_STATE_DELETED_MESSAGE, IDENTITY_FEATURE_STATE_UPDATED_MESSAGE, ) from audit.models import AuditLog, RelatedObjectType # type: ignore[attr-defined] +from core.constants import FLAGSMITH_UPDATED_AT_HEADER from environments.dynamodb import ( DynamoEnvironmentV2Wrapper, DynamoIdentityWrapper, diff --git a/api/tests/unit/features/versioning/test_unit_versioning_models.py b/api/tests/unit/features/versioning/test_unit_versioning_models.py index c06ee26662f0..8b9e0d7ae5b2 100644 --- a/api/tests/unit/features/versioning/test_unit_versioning_models.py +++ b/api/tests/unit/features/versioning/test_unit_versioning_models.py @@ -2,11 +2,11 @@ from datetime import timedelta import pytest -from core.constants import STRING from django.utils import timezone from freezegun import freeze_time from pytest_mock import MockerFixture +from core.constants import STRING from environments.models import Environment from environments.tasks import rebuild_environment_document from features.models import Feature, FeatureSegment, FeatureState diff --git a/api/tests/unit/features/versioning/test_unit_versioning_tasks.py b/api/tests/unit/features/versioning/test_unit_versioning_tasks.py index 33171f0eced8..99dee61db63a 100644 --- a/api/tests/unit/features/versioning/test_unit_versioning_tasks.py +++ b/api/tests/unit/features/versioning/test_unit_versioning_tasks.py @@ -5,7 +5,6 @@ import freezegun import pytest import responses -from core.constants import STRING from django.core.mail import EmailMessage from django.template.loader import render_to_string from django.utils import timezone @@ -13,6 +12,7 @@ from freezegun.api import FrozenDateTimeFactory from rest_framework.exceptions import ValidationError +from core.constants import STRING from environments.identities.models import Identity from environments.models import Environment, Webhook from features.models import Feature, FeatureSegment, FeatureState diff --git a/api/tests/unit/features/versioning/test_unit_versioning_views.py b/api/tests/unit/features/versioning/test_unit_versioning_views.py index 2a86bbc9abc8..67c3189e3550 100644 --- a/api/tests/unit/features/versioning/test_unit_versioning_views.py +++ b/api/tests/unit/features/versioning/test_unit_versioning_views.py @@ -8,7 +8,6 @@ VIEW_ENVIRONMENT, ) from common.projects.permissions import VIEW_PROJECT # type: ignore[import-untyped] -from core.constants import STRING from django.urls import reverse from django.utils import timezone from freezegun import freeze_time @@ -21,6 +20,7 @@ from audit.constants import ENVIRONMENT_FEATURE_VERSION_PUBLISHED_MESSAGE from audit.models import AuditLog from audit.related_object_type import RelatedObjectType +from core.constants import STRING from environments.models import Environment from features.feature_segments.limits import ( SEGMENT_OVERRIDE_LIMIT_EXCEEDED_MESSAGE, diff --git a/api/tests/unit/features/workflows/core/test_unit_workflows_models.py b/api/tests/unit/features/workflows/core/test_unit_workflows_models.py index d30ed23ca589..386c3afde3de 100644 --- a/api/tests/unit/features/workflows/core/test_unit_workflows_models.py +++ b/api/tests/unit/features/workflows/core/test_unit_workflows_models.py @@ -3,7 +3,6 @@ import freezegun import pytest -from core.helpers import get_current_site_url from django.contrib.sites.models import Site from django.db.models import Q from django.utils import timezone @@ -20,6 +19,7 @@ ) from audit.models import AuditLog from audit.related_object_type import RelatedObjectType +from core.helpers import get_current_site_url from environments.models import Environment from features.models import Feature, FeatureState from features.versioning.models import ( @@ -131,9 +131,7 @@ def test_change_request_is_approved_returns_true_when_minimum_change_request_app change_request_no_required_approvals, mocker, environment ): # Given - change_request_no_required_approvals.environment.minimum_change_request_approvals = ( - None - ) + change_request_no_required_approvals.environment.minimum_change_request_approvals = None change_request_no_required_approvals.save() # Then assert change_request_no_required_approvals.is_approved() is True diff --git a/api/tests/unit/import_export/test_unit_import_export_export.py b/api/tests/unit/import_export/test_unit_import_export_export.py index ca86e7cc9b2c..5317060227b4 100644 --- a/api/tests/unit/import_export/test_unit_import_export_export.py +++ b/api/tests/unit/import_export/test_unit_import_export_export.py @@ -5,7 +5,6 @@ from decimal import Decimal import boto3 -from core.constants import STRING from django.contrib.contenttypes.models import ContentType from django.core.management import call_command from django.core.serializers.json import DjangoJSONEncoder @@ -14,6 +13,7 @@ from mypy_boto3_dynamodb.service_resource import Table from pytest_mock import MockerFixture +from core.constants import STRING from environments.identities.models import Identity from environments.models import Environment, EnvironmentAPIKey, Webhook from features.feature_types import MULTIVARIATE diff --git a/api/tests/unit/integrations/webhook/test_unit_webhook.py b/api/tests/unit/integrations/webhook/test_unit_webhook.py index 01ff571594b8..4bfb64fa40e1 100644 --- a/api/tests/unit/integrations/webhook/test_unit_webhook.py +++ b/api/tests/unit/integrations/webhook/test_unit_webhook.py @@ -1,5 +1,4 @@ from core.constants import STRING - from environments.identities.traits.models import Trait from environments.identities.traits.serializers import TraitSerializerBasic from features.models import Feature, FeatureState diff --git a/api/tests/unit/metadata/test_serializers.py b/api/tests/unit/metadata/test_serializers.py index f47f1e8eceed..f27a33b41228 100644 --- a/api/tests/unit/metadata/test_serializers.py +++ b/api/tests/unit/metadata/test_serializers.py @@ -1,5 +1,7 @@ import pytest -from common.metadata.serializers import MetadataSerializer # type: ignore[import-untyped] +from common.metadata.serializers import ( # type: ignore[import-untyped] + MetadataSerializer, +) from metadata.models import ( FIELD_VALUE_MAX_LENGTH, diff --git a/api/tests/unit/organisations/test_unit_organisations_tasks.py b/api/tests/unit/organisations/test_unit_organisations_tasks.py index e3efa1908fd1..4296dfcff408 100644 --- a/api/tests/unit/organisations/test_unit_organisations_tasks.py +++ b/api/tests/unit/organisations/test_unit_organisations_tasks.py @@ -4,7 +4,6 @@ from unittest.mock import MagicMock, call import pytest -from core.helpers import get_current_site_url from dateutil.relativedelta import relativedelta from django.core.mail.message import EmailMultiAlternatives from django.template.loader import render_to_string @@ -13,6 +12,7 @@ from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture +from core.helpers import get_current_site_url from organisations.chargebee.metadata import ChargebeeObjMetadata from organisations.constants import ( API_USAGE_ALERT_THRESHOLDS, diff --git a/api/tests/unit/organisations/test_unit_organisations_views.py b/api/tests/unit/organisations/test_unit_organisations_views.py index 97b6f1d472fe..c93c7465c60d 100644 --- a/api/tests/unit/organisations/test_unit_organisations_views.py +++ b/api/tests/unit/organisations/test_unit_organisations_views.py @@ -710,10 +710,22 @@ def test_chargebee_webhook( organisation=subscription.organisation ) assert subscription_cache.current_billing_term_ends_at == datetime( - 2023, 12, 10, 15, 33, 9, tzinfo=timezone.utc # type: ignore[attr-defined] + 2023, + 12, + 10, + 15, + 33, + 9, + tzinfo=timezone.utc, # type: ignore[attr-defined] ) assert subscription_cache.current_billing_term_starts_at == datetime( - 2023, 11, 10, 15, 33, 9, tzinfo=timezone.utc # type: ignore[attr-defined] + 2023, + 11, + 10, + 15, + 33, + 9, + tzinfo=timezone.utc, # type: ignore[attr-defined] ) assert subscription_cache.allowed_projects is None assert subscription_cache.allowed_30d_api_calls == api_calls @@ -1235,8 +1247,8 @@ def test_when_plan_is_changed_max_seats_and_max_api_calls_are_updated( # type: # Given chargebee_email = "chargebee@test.com" url = reverse("api-v1:chargebee-webhook") - updated_at = datetime.now(tz=UTC) - timedelta( - days=1 + updated_at = ( + datetime.now(tz=UTC) - timedelta(days=1) ) # The timestamp representing the last update time, one day ago from the current time. mock_get_plan_meta_data.return_value = { diff --git a/api/tests/unit/permissions/test_unit_permissions_calculator.py b/api/tests/unit/permissions/test_unit_permissions_calculator.py index e47bfc4c84e2..fbbcce90da95 100644 --- a/api/tests/unit/permissions/test_unit_permissions_calculator.py +++ b/api/tests/unit/permissions/test_unit_permissions_calculator.py @@ -3,7 +3,10 @@ UPDATE_FEATURE_STATE, VIEW_ENVIRONMENT, ) -from common.projects.permissions import CREATE_ENVIRONMENT, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, + VIEW_PROJECT, +) from environments.permissions.models import ( EnvironmentPermissionModel, diff --git a/api/tests/unit/projects/tags/test_unit_projects_tags_permissions.py b/api/tests/unit/projects/tags/test_unit_projects_tags_permissions.py index 7fa66875fd16..0190daaae42f 100644 --- a/api/tests/unit/projects/tags/test_unit_projects_tags_permissions.py +++ b/api/tests/unit/projects/tags/test_unit_projects_tags_permissions.py @@ -1,6 +1,9 @@ from unittest import mock -from common.projects.permissions import MANAGE_TAGS, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + MANAGE_TAGS, + VIEW_PROJECT, +) from projects.models import Project from projects.tags.models import Tag diff --git a/api/tests/unit/projects/test_migrations.py b/api/tests/unit/projects/test_migrations.py index 6705f4d65108..a68b886799be 100644 --- a/api/tests/unit/projects/test_migrations.py +++ b/api/tests/unit/projects/test_migrations.py @@ -1,5 +1,8 @@ import pytest -from common.projects.permissions import CREATE_ENVIRONMENT, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + CREATE_ENVIRONMENT, + VIEW_PROJECT, +) from django.conf import settings diff --git a/api/tests/unit/segments/test_unit_segments_views.py b/api/tests/unit/segments/test_unit_segments_views.py index 23de5ba94860..a0a73bd42ea3 100644 --- a/api/tests/unit/segments/test_unit_segments_views.py +++ b/api/tests/unit/segments/test_unit_segments_views.py @@ -2,7 +2,10 @@ import random import pytest -from common.projects.permissions import MANAGE_SEGMENTS, VIEW_PROJECT # type: ignore[import-untyped] +from common.projects.permissions import ( # type: ignore[import-untyped] + MANAGE_SEGMENTS, + VIEW_PROJECT, +) from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType diff --git a/api/tests/unit/telemetry/test_unit_telemetry_models.py b/api/tests/unit/telemetry/test_unit_telemetry_models.py index 306d330145f2..33767312afe5 100644 --- a/api/tests/unit/telemetry/test_unit_telemetry_models.py +++ b/api/tests/unit/telemetry/test_unit_telemetry_models.py @@ -1,4 +1,5 @@ from django.test import override_settings + from telemetry.models import TelemetryData env = "test" diff --git a/api/tests/unit/telemetry/test_unit_telemetry_serializers.py b/api/tests/unit/telemetry/test_unit_telemetry_serializers.py index 1708de501b9a..9fa29230d622 100644 --- a/api/tests/unit/telemetry/test_unit_telemetry_serializers.py +++ b/api/tests/unit/telemetry/test_unit_telemetry_serializers.py @@ -1,8 +1,8 @@ from unittest import mock from django.test import override_settings -from telemetry.serializers import TelemetrySerializer +from telemetry.serializers import TelemetrySerializer from tests.unit.telemetry.helpers import get_example_telemetry_data diff --git a/api/tests/unit/telemetry/test_unit_telemetry_telemetry.py b/api/tests/unit/telemetry/test_unit_telemetry_telemetry.py index 74ab57c0f060..e6b25c29b8b3 100644 --- a/api/tests/unit/telemetry/test_unit_telemetry_telemetry.py +++ b/api/tests/unit/telemetry/test_unit_telemetry_telemetry.py @@ -2,8 +2,8 @@ from unittest import mock import responses -from telemetry.telemetry import SelfHostedTelemetryWrapper +from telemetry.telemetry import SelfHostedTelemetryWrapper from tests.unit.telemetry.helpers import get_example_telemetry_data diff --git a/api/tests/unit/util/mappers/test_unit_mappers_sdk.py b/api/tests/unit/util/mappers/test_unit_mappers_sdk.py index a8afa3feac2e..5c4c760b1eaf 100644 --- a/api/tests/unit/util/mappers/test_unit_mappers_sdk.py +++ b/api/tests/unit/util/mappers/test_unit_mappers_sdk.py @@ -83,7 +83,6 @@ def test_map_environment_to_sdk_document__return_expected( ], "identity_traits": [], "identity_uuid": mocker.ANY, - "dashboard_alias": None, } ], "hide_disabled_flags": None, diff --git a/api/tests/unit/webhooks/test_unit_webhooks.py b/api/tests/unit/webhooks/test_unit_webhooks.py index c028bfc387d6..3e287d8e7ed1 100644 --- a/api/tests/unit/webhooks/test_unit_webhooks.py +++ b/api/tests/unit/webhooks/test_unit_webhooks.py @@ -7,11 +7,11 @@ import pytest import responses -from core.constants import FLAGSMITH_SIGNATURE_HEADER from pytest_django.fixtures import SettingsWrapper from pytest_mock import MockerFixture from requests.exceptions import ConnectionError, Timeout +from core.constants import FLAGSMITH_SIGNATURE_HEADER from environments.models import Environment, Webhook from organisations.models import Organisation, OrganisationWebhook from webhooks.sample_webhook_data import ( diff --git a/api/users/abc.py b/api/users/abc.py index 6792b77a6b61..1c980199c0d7 100644 --- a/api/users/abc.py +++ b/api/users/abc.py @@ -31,13 +31,19 @@ def is_group_admin(self, group_id: int) -> bool: @abstractmethod def has_project_permission( - self, permission: str, project: "Project", tag_ids: list[int] = None # type: ignore[assignment] + self, + permission: str, + project: "Project", + tag_ids: list[int] = None, # type: ignore[assignment] ) -> bool: raise NotImplementedError() @abstractmethod def has_environment_permission( - self, permission: str, environment: "Environment", tag_ids: list[int] = None # type: ignore[assignment] + self, + permission: str, + environment: "Environment", + tag_ids: list[int] = None, # type: ignore[assignment] ) -> bool: raise NotImplementedError() diff --git a/api/users/models.py b/api/users/models.py index 88977fd4ff54..9ac0bfd42137 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -10,8 +10,15 @@ from django.db import models from django.db.models import Count, QuerySet from django.utils import timezone -from django_lifecycle import AFTER_CREATE, AFTER_SAVE, LifecycleModel, hook # type: ignore[import-untyped] -from django_lifecycle.conditions import WhenFieldHasChanged # type: ignore[import-untyped] +from django_lifecycle import ( # type: ignore[import-untyped] + AFTER_CREATE, + AFTER_SAVE, + LifecycleModel, + hook, +) +from django_lifecycle.conditions import ( # type: ignore[import-untyped] + WhenFieldHasChanged, +) from integrations.lead_tracking.hubspot.tasks import ( track_hubspot_lead_without_organisation, @@ -278,12 +285,17 @@ def get_user_organisation( # type: ignore[return] ) def get_permitted_projects( - self, permission_key: str, tag_ids: typing.List[int] = None # type: ignore[assignment] + self, + permission_key: str, + tag_ids: typing.List[int] = None, # type: ignore[assignment] ) -> QuerySet[Project]: return get_permitted_projects_for_user(self, permission_key, tag_ids) def has_project_permission( - self, permission: str, project: Project, tag_ids: typing.List[int] = None # type: ignore[assignment] + self, + permission: str, + project: Project, + tag_ids: typing.List[int] = None, # type: ignore[assignment] ) -> bool: if self.is_project_admin(project): return True diff --git a/api/users/serializers.py b/api/users/serializers.py index c37c07524be8..d2d0b834c468 100644 --- a/api/users/serializers.py +++ b/api/users/serializers.py @@ -1,4 +1,6 @@ -from djoser.serializers import UserSerializer as DjoserUserSerializer # type: ignore[import-untyped] +from djoser.serializers import ( # type: ignore[import-untyped] + UserSerializer as DjoserUserSerializer, +) from rest_framework import serializers from rest_framework.exceptions import ValidationError diff --git a/api/users/services.py b/api/users/services.py index f13b4c1db751..14701f4f41c8 100644 --- a/api/users/services.py +++ b/api/users/services.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -from core.helpers import get_current_site_url from django.conf import settings from djoser.email import PasswordResetEmail # type: ignore[import-untyped] +from core.helpers import get_current_site_url from users.models import FFAdminUser @@ -41,4 +41,4 @@ def _get_user_password_reset_url( user: FFAdminUser, ) -> str: _email = PasswordResetEmail(context={"user": user}) - return f'{get_current_site_url()}/{_email.get_context_data()["url"]}' + return f"{get_current_site_url()}/{_email.get_context_data()['url']}" diff --git a/api/users/tasks.py b/api/users/tasks.py index d0d4e8b67e9d..37e8475a0ade 100644 --- a/api/users/tasks.py +++ b/api/users/tasks.py @@ -1,6 +1,8 @@ from django.core.mail import send_mail from django.template.loader import render_to_string -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from integrations.lead_tracking.pipedrive.lead_tracker import ( PipedriveLeadTracker, diff --git a/api/users/views.py b/api/users/views.py index 4e88c718ed75..f8235c10d9b1 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -1,6 +1,5 @@ from contextlib import suppress -from core.helpers import get_current_site_url from django.contrib.auth.mixins import PermissionRequiredMixin from django.db.models import Prefetch, Q, QuerySet from django.http import ( @@ -21,6 +20,7 @@ from rest_framework.request import Request from rest_framework.response import Response +from core.helpers import get_current_site_url from organisations.models import Organisation, UserOrganisation from organisations.permissions.permissions import ( MANAGE_USER_GROUPS, diff --git a/api/util/drf_writable_nested/serializers.py b/api/util/drf_writable_nested/serializers.py index 87d63d49ef44..e4999b5ca978 100644 --- a/api/util/drf_writable_nested/serializers.py +++ b/api/util/drf_writable_nested/serializers.py @@ -1,4 +1,7 @@ -from drf_writable_nested import NestedCreateMixin, NestedUpdateMixin # type: ignore[attr-defined] +from drf_writable_nested import ( # type: ignore[attr-defined] + NestedCreateMixin, + NestedUpdateMixin, +) from rest_framework import serializers @@ -29,6 +32,8 @@ def update(self, instance, validated_data): # type: ignore[no-untyped-def] class DeleteBeforeUpdateWritableNestedModelSerializer( - NestedCreateMixin, NestedUpdateMixinDeleteBeforeUpdate, serializers.ModelSerializer # type: ignore[type-arg] + NestedCreateMixin, + NestedUpdateMixinDeleteBeforeUpdate, + serializers.ModelSerializer, # type: ignore[type-arg] ): pass diff --git a/api/util/logging.py b/api/util/logging.py index e5c78713313a..ae986132fcbd 100644 --- a/api/util/logging.py +++ b/api/util/logging.py @@ -6,7 +6,9 @@ from django.conf import settings from gunicorn.config import Config # type: ignore[import-untyped] -from gunicorn.instrument.statsd import Statsd as GunicornLogger # type: ignore[import-untyped] +from gunicorn.instrument.statsd import ( # type: ignore[import-untyped] + Statsd as GunicornLogger, +) class JsonFormatter(logging.Formatter): diff --git a/api/util/mappers/engine.py b/api/util/mappers/engine.py index e591ccfb2066..dde78c4ded31 100644 --- a/api/util/mappers/engine.py +++ b/api/util/mappers/engine.py @@ -16,7 +16,10 @@ MultivariateFeatureOptionModel, MultivariateFeatureStateValueModel, ) -from flag_engine.identities.models import IdentityModel, TraitModel # type: ignore[attr-defined] +from flag_engine.identities.models import ( # type: ignore[attr-defined] + IdentityModel, + TraitModel, +) from flag_engine.organisations.models import OrganisationModel from flag_engine.projects.models import ProjectModel from flag_engine.segments.models import ( @@ -27,10 +30,12 @@ from environments.constants import IDENTITY_INTEGRATIONS_RELATION_NAMES from features.versioning.models import EnvironmentFeatureVersion -from segments.models import Segment if TYPE_CHECKING: # pragma: no cover - from environments.identities.models import Identity, Trait # type: ignore[attr-defined] + from environments.identities.models import ( # type: ignore[attr-defined] + Identity, + Trait, + ) from environments.models import Environment, EnvironmentAPIKey from features.models import Feature, FeatureSegment, FeatureState from features.multivariate.models import ( @@ -41,7 +46,7 @@ from integrations.webhook.models import WebhookConfiguration from organisations.models import Organisation from projects.models import Project - from segments.models import SegmentRule + from segments.models import Segment, SegmentRule __all__ = ( diff --git a/api/webhooks/models.py b/api/webhooks/models.py index 72cd6d72ca35..0ee206d37c8d 100644 --- a/api/webhooks/models.py +++ b/api/webhooks/models.py @@ -1,6 +1,7 @@ -from core.models import AbstractBaseExportableModel, SoftDeleteExportableModel from django.db import models +from core.models import AbstractBaseExportableModel, SoftDeleteExportableModel + class AbstractBaseWebhookModel(models.Model): url = models.CharField(max_length=200) diff --git a/api/webhooks/webhooks.py b/api/webhooks/webhooks.py index 6329acec46fc..a20a1ff9f378 100644 --- a/api/webhooks/webhooks.py +++ b/api/webhooks/webhooks.py @@ -6,16 +6,18 @@ import backoff import requests -from core.constants import FLAGSMITH_SIGNATURE_HEADER -from core.signing import sign_payload from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.core.serializers.json import DjangoJSONEncoder from django.template.loader import get_template from django.utils import timezone -from task_processor.decorators import register_task_handler # type: ignore[import-untyped] +from task_processor.decorators import ( # type: ignore[import-untyped] + register_task_handler, +) from task_processor.task_run_method import TaskRunMethod # type: ignore[import-untyped] +from core.constants import FLAGSMITH_SIGNATURE_HEADER +from core.signing import sign_payload from environments.models import Environment, Webhook from organisations.models import OrganisationWebhook from projects.models import Organisation # type: ignore[attr-defined]