Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions api/app/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,3 @@
"""

ENABLE_POSTPONE_DECORATOR = False

DEBUG = True
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After the upgrade of django-debug-toolbar, this was causing issues because we only include the debug toolbar if DEBUG is True (see here). As I understand it, it's because we don't also add it to installed_apps here which is now required (but seemingly wasn't in older versions of the package).

I decided the better solution here was simply to remove the DEBUG = True as I'm not sure why it existed here for tests in the first place.

2 changes: 1 addition & 1 deletion api/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
),
]

if settings.DEBUG:
if settings.DEBUG: # pragma: no cover
urlpatterns = [
re_path(r"^__debug__/", include("debug_toolbar.urls")),
] + urlpatterns
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2.9 on 2025-12-31 15:34

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("app_analytics", "0006_add_labels"),
]

operations = [
migrations.RenameIndex(
model_name="apiusageraw",
new_name="app_analyti_environ_b61cad_idx",
old_fields=("environment_id", "created_at"),
),
Comment on lines +13 to +17
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Caused by deprecation of Model.Meta.index_together. I've researched and, as I understand it, it requires a lock on the metadata, but no the table or index itself.

Since we don't reference the name of the index directly (although we might in specific oracle migrations 🤔) we shouldn't have any schema mismatch issues.

]
2 changes: 1 addition & 1 deletion api/app_analytics/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class APIUsageRaw(models.Model):
labels = HStoreField(default=dict)

class Meta:
index_together = (("environment_id", "created_at"),)
indexes = [models.Index(fields=["environment_id", "created_at"])]


class AbstractBucket(LifecycleModelMixin, models.Model): # type: ignore[misc]
Expand Down
2 changes: 1 addition & 1 deletion api/audit/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def _get_organisation(self) -> Organisation | None:
Since we're applying the base filters to the query set
"""
return (
return ( # type: ignore[no-any-return]
self.request.user.organisations.filter( # type: ignore[union-attr]
userorganisation__role=OrganisationRole.ADMIN
)
Expand Down
2 changes: 2 additions & 0 deletions api/custom_auth/mfa/trench/command/remove_backup_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ def remove_backup_code_command(user_id: Any, method_name: str, code: str) -> Non
.values_list("_backup_codes", flat=True)
.first()
)
if serialized_codes is None: # pragma: no cover
return
Comment on lines +14 to +15
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added for typing reasons.

codes = MFAMethod._BACKUP_CODES_DELIMITER.join(
_remove_code_from_set( # type: ignore[arg-type]
backup_codes=set(serialized_codes.split(MFAMethod._BACKUP_CODES_DELIMITER)),
Expand Down
7 changes: 4 additions & 3 deletions api/custom_auth/mfa/trench/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def post(request: Request, method: str) -> Response:
try:
user = request.user
mfa = create_mfa_method_command(
user_id=user.id,
user_id=user.id, # type: ignore[arg-type]
name=method,
)
except MFAValidationError as cause:
Expand All @@ -57,7 +57,7 @@ def post(request: Request, method: str) -> Response:
if not serializer.is_valid():
return Response(status=HTTP_400_BAD_REQUEST, data=serializer.errors)
backup_codes = activate_mfa_method_command(
user_id=request.user.id,
user_id=request.user.id, # type: ignore[arg-type]
name=method,
code=serializer.validated_data["code"],
)
Expand All @@ -71,7 +71,8 @@ class MFAMethodDeactivationView(APIView):
def post(request: Request, method: str) -> Response:
try:
deactivate_mfa_method_command(
mfa_method_name=method, user_id=request.user.id
mfa_method_name=method,
user_id=request.user.id, # type: ignore[arg-type]
)
return Response(status=HTTP_204_NO_CONTENT)
except MFAValidationError as cause:
Expand Down
2 changes: 1 addition & 1 deletion api/environments/identities/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def with_context(
self,
integrations: "Iterable[IntegrationConfig] | None" = None,
extra_select_related: "Iterable[str] | None" = None,
extra_prefetch_related: "Iterable[str | Prefetch] | None" = None,
extra_prefetch_related: "Iterable[str | Prefetch] | None" = None, # type: ignore[type-arg]
) -> "QuerySet[Identity]":
from integrations.integration import IDENTITY_INTEGRATIONS

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.2.9 on 2025-12-31 15:29
from common.migrations.helpers import PostgresOnlyRunSQL
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("identities", "0005_revert_sanitized_identifiers"),
]

operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RenameIndex(
model_name="identity",
new_name="environment_environ_341dc9_idx",
old_fields=("environment", "created_date"),
),
],
database_operations=[
PostgresOnlyRunSQL(
'ALTER INDEX "environments_identity_environment_id_created_date_idx" RENAME TO "environment_environ_341dc9_idx"',
reverse_sql='ALTER INDEX "environment_environ_341dc9_idx" RENAME TO "environments_identity_environment_id_created_date_idx"'
)
],
)
]
2 changes: 1 addition & 1 deletion api/environments/identities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Meta:
# Note that the environment / created_date index is added only to postgres, so we can add it concurrently to
# avoid any downtime. If people using MySQL / Oracle have issues with poor performance on the identities table,
# we can provide them the SQL to add it manually in a small window of downtime.
index_together = (("environment", "created_date"),)
indexes = [models.Index(fields=["environment", "created_date"])]

def natural_key(self): # type: ignore[no-untyped-def]
return self.identifier, self.environment.api_key
Expand Down
2 changes: 1 addition & 1 deletion api/environments/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def filter_for_document_builder( # type: ignore[no-untyped-def]
self,
*args,
extra_select_related: list[str] | None = None,
extra_prefetch_related: list[Prefetch | str] | None = None,
extra_prefetch_related: list[Prefetch | str] | None = None, # type: ignore[type-arg]
**kwargs,
):
return (
Expand Down
8 changes: 4 additions & 4 deletions api/features/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ class FeatureAdmin(SimpleHistoryAdmin): # type: ignore[misc]
class FeatureSegmentAdmin(admin.ModelAdmin): # type: ignore[type-arg]
model = FeatureSegment

def add_view(self, *args, **kwargs): # type: ignore[no-untyped-def]
self.exclude = ("priority",)
def add_view(self, *args, **kwargs): # type: ignore[no-untyped-def] # pragma: no cover
self.exclude = ("priority",) # type: ignore[misc]
return super(FeatureSegmentAdmin, self).add_view(*args, **kwargs)

def change_view(self, *args, **kwargs): # type: ignore[no-untyped-def]
self.exclude = ()
def change_view(self, *args, **kwargs): # type: ignore[no-untyped-def] # pragma: no cover
self.exclude = () # type: ignore[misc]
return super(FeatureSegmentAdmin, self).change_view(*args, **kwargs)


Expand Down
2 changes: 1 addition & 1 deletion api/features/feature_health/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def dismiss_feature_health_event(
FeatureHealthEvent.objects.create(
feature=feature_health_event.feature,
environment=feature_health_event.environment,
type=FeatureHealthEventType.HEALTHY,
type=FeatureHealthEventType.HEALTHY.value,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one was somewhat odd. I can't see any indication of any changes to this behaviour in the notes here, but looking at this test I do think it was wrong previously anyway.

The error that I was seeing was that it couldn't create the event with the type as the literal string "FeatureHealthEventType.HEALTHY" which makes sense - perhaps Django 5 is more strict on enforcing the choices behaviour. Either way, I think this change is a good step.

reason=json.dumps(reason),
provider_name=feature_health_event.provider_name,
external_id=feature_health_event.external_id,
Expand Down
2 changes: 1 addition & 1 deletion api/features/feature_states/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ def has_permission(self, request: Request, view: APIView) -> bool:
except Environment.DoesNotExist:
return False

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
UPDATE_FEATURE_STATE, environment
)
8 changes: 4 additions & 4 deletions api/features/import_export/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def has_permission(self, request: Request, view: APIView) -> bool:
return False

environment = Environment.objects.get(id=request.data["environment_id"])
return request.user.is_environment_admin(environment) # type: ignore[union-attr]
return request.user.is_environment_admin(environment) # type: ignore[union-attr,no-any-return]


class DownloadFeatureExportPermissions(IsAuthenticated):
Expand All @@ -39,7 +39,7 @@ def has_permission(self, request: Request, view: APIView) -> bool:

feature_export = FeatureExport.objects.get(id=view.kwargs["feature_export_id"])

return request.user.is_environment_admin(feature_export.environment) # type: ignore[union-attr]
return request.user.is_environment_admin(feature_export.environment) # type: ignore[union-attr,no-any-return]


class FeatureExportListPermissions(IsAuthenticated):
Expand All @@ -50,7 +50,7 @@ def has_permission(self, request: Request, view: ListAPIView) -> bool: # type:
project = Project.objects.get(id=view.kwargs["project_pk"])
# The user will only see environment feature exports
# that the user is an environment admin.
return request.user.has_project_permission(VIEW_PROJECT, project) # type: ignore[union-attr]
return request.user.has_project_permission(VIEW_PROJECT, project) # type: ignore[union-attr,no-any-return]


class FeatureImportListPermissions(IsAuthenticated):
Expand All @@ -61,4 +61,4 @@ def has_permission(self, request: Request, view: ListAPIView) -> bool: # type:
project = Project.objects.get(id=view.kwargs["project_pk"])
# The user will only see environment feature imports
# that the user is an environment admin.
return request.user.has_project_permission(VIEW_PROJECT, project) # type: ignore[union-attr]
return request.user.has_project_permission(VIEW_PROJECT, project) # type: ignore[union-attr,no-any-return]
10 changes: 5 additions & 5 deletions api/features/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ 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]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
required_permission,
environment,
tag_ids=tag_ids, # type: ignore[arg-type]
tag_ids=tag_ids,
)
return False

Expand All @@ -144,10 +144,10 @@ def has_object_permission(
if permission in TAG_SUPPORTED_ENVIRONMENT_PERMISSIONS:
tag_ids = list(obj.feature.tags.values_list("id", flat=True))

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission,
environment=obj.environment, # type: ignore[arg-type]
tag_ids=tag_ids, # type: ignore[arg-type]
environment=obj.environment,
tag_ids=tag_ids,
)


Expand Down
22 changes: 11 additions & 11 deletions api/features/versioning/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ
feature = Feature.objects.get(id=feature_id, project=environment.project)
tag_ids = list(feature.tags.values_list("id", flat=True))

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=required_permission,
environment=environment,
tag_ids=tag_ids, # type: ignore[arg-type]
tag_ids=tag_ids,
)

def has_object_permission(
Expand All @@ -53,10 +53,10 @@ def has_object_permission(
if required_permission in TAG_SUPPORTED_ENVIRONMENT_PERMISSIONS:
tag_ids = list(obj.feature.tags.values_list("id", flat=True))

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=required_permission,
environment=obj.environment,
tag_ids=tag_ids, # type: ignore[arg-type]
tag_ids=tag_ids,
)


Expand All @@ -73,11 +73,11 @@ def has_permission(self, request: Request, view: GenericViewSet) -> bool: # typ
environment = Environment.objects.get(id=environment_pk)

if view.action == "list":
return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=VIEW_ENVIRONMENT, environment=environment
)

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=UPDATE_FEATURE_STATE, environment=environment
)

Expand All @@ -87,13 +87,13 @@ def has_object_permission(
view: GenericViewSet, # type: ignore[override,type-arg]
obj: FeatureState,
) -> bool:
if view.action == "retrieve":
return request.user.has_environment_permission( # type: ignore[union-attr]
if view.action == "retrieve": # pragma: no cover
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=VIEW_ENVIRONMENT,
environment=obj.environment, # type: ignore[arg-type]
environment=obj.environment,
)

return request.user.has_environment_permission( # type: ignore[union-attr]
return request.user.has_environment_permission( # type: ignore[union-attr,no-any-return]
permission=UPDATE_FEATURE_STATE,
environment=obj.environment, # type: ignore[arg-type]
environment=obj.environment,
)
6 changes: 3 additions & 3 deletions api/features/versioning/versioning_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def get_environment_flags_list(
additional_filters: Q = None, # type: ignore[assignment]
additional_select_related_args: typing.Iterable[str] = None, # type: ignore[assignment]
additional_prefetch_related_args: typing.Iterable[
typing.Union[str, Prefetch]
typing.Union[str, Prefetch[typing.Any]]
] = None, # type: ignore[assignment]
from_replica: bool = False,
) -> list[FeatureState]:
Expand Down Expand Up @@ -64,7 +64,7 @@ def get_environment_flags_dict(
additional_filters: Q = None, # type: ignore[assignment]
additional_select_related_args: typing.Iterable[str] = None, # type: ignore[assignment]
additional_prefetch_related_args: typing.Iterable[
typing.Union[str, Prefetch]
typing.Union[str, Prefetch[typing.Any]]
] = None, # type: ignore[assignment]
key_function: typing.Callable[[FeatureState], tuple] = None, # type: ignore[type-arg,assignment]
from_replica: bool = False,
Expand Down Expand Up @@ -507,7 +507,7 @@ def _get_feature_states_queryset(
additional_filters: Q = None, # type: ignore[assignment]
additional_select_related_args: typing.Iterable[str] = None, # type: ignore[assignment]
additional_prefetch_related_args: typing.Iterable[
typing.Union[str, Prefetch]
typing.Union[str, Prefetch[typing.Any]]
] = None, # type: ignore[assignment]
from_replica: bool = False,
) -> QuerySet[FeatureState]:
Expand Down
11 changes: 4 additions & 7 deletions api/organisations/chargebee/webhook_handlers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from datetime import datetime
from datetime import timezone as dttz

from django.utils import timezone
from rest_framework import status
Expand Down Expand Up @@ -129,7 +130,7 @@ def process_subscription(request: Request) -> Response: # noqa: C901
cancellation_date = subscription.get("current_term_end")
if cancellation_date is not None:
cancellation_date = datetime.fromtimestamp(cancellation_date).replace(
tzinfo=timezone.utc # type: ignore[attr-defined]
tzinfo=dttz.utc
)
else:
cancellation_date = timezone.now()
Expand Down Expand Up @@ -168,9 +169,7 @@ def process_subscription(request: Request) -> Response: # noqa: C901
else:
osic_defaults["current_billing_term_ends_at"] = datetime.fromtimestamp(
current_term_end
).replace(
tzinfo=timezone.utc # type: ignore[attr-defined]
)
).replace(tzinfo=dttz.utc)

if "current_term_start" in subscription:
current_term_start = subscription["current_term_start"]
Expand All @@ -179,9 +178,7 @@ def process_subscription(request: Request) -> Response: # noqa: C901
else:
osic_defaults["current_billing_term_starts_at"] = datetime.fromtimestamp(
current_term_start
).replace(
tzinfo=timezone.utc # type: ignore[attr-defined]
)
).replace(tzinfo=dttz.utc)

OrganisationSubscriptionInformationCache.objects.update_or_create(
organisation_id=existing_subscription.organisation_id,
Expand Down
2 changes: 1 addition & 1 deletion api/organisations/permissions/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,4 @@ def has_permission(self, request: Request, view: View) -> bool:
return False

# All organisation users can see api usage notifications.
return request.user.belongs_to(view.kwargs.get("organisation_pk")) # type: ignore[union-attr]
return request.user.belongs_to(view.kwargs.get("organisation_pk")) # type: ignore[union-attr,no-any-return]
Loading
Loading