diff --git a/api/app/settings/test.py b/api/app/settings/test.py index 23c9f96ea7de..1f0ab33f395c 100644 --- a/api/app/settings/test.py +++ b/api/app/settings/test.py @@ -66,5 +66,3 @@ """ ENABLE_POSTPONE_DECORATOR = False - -DEBUG = True diff --git a/api/app/urls.py b/api/app/urls.py index 69fd350419eb..d5b68f85f6ca 100644 --- a/api/app/urls.py +++ b/api/app/urls.py @@ -49,7 +49,7 @@ ), ] -if settings.DEBUG: +if settings.DEBUG: # pragma: no cover urlpatterns = [ re_path(r"^__debug__/", include("debug_toolbar.urls")), ] + urlpatterns diff --git a/api/app_analytics/migrations/0007_rename_environment_id_created_at_index.py b/api/app_analytics/migrations/0007_rename_environment_id_created_at_index.py new file mode 100644 index 000000000000..f2783cbae638 --- /dev/null +++ b/api/app_analytics/migrations/0007_rename_environment_id_created_at_index.py @@ -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"), + ), + ] diff --git a/api/app_analytics/models.py b/api/app_analytics/models.py index aa55e2e17cbc..98f21eb4e0d2 100644 --- a/api/app_analytics/models.py +++ b/api/app_analytics/models.py @@ -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] diff --git a/api/audit/views.py b/api/audit/views.py index 994868dbbabd..1e65870382df 100644 --- a/api/audit/views.py +++ b/api/audit/views.py @@ -108,7 +108,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 ) diff --git a/api/custom_auth/mfa/trench/command/remove_backup_code.py b/api/custom_auth/mfa/trench/command/remove_backup_code.py index 120a11ed1bbd..873f97b7110a 100644 --- a/api/custom_auth/mfa/trench/command/remove_backup_code.py +++ b/api/custom_auth/mfa/trench/command/remove_backup_code.py @@ -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 codes = MFAMethod._BACKUP_CODES_DELIMITER.join( _remove_code_from_set( # type: ignore[arg-type] backup_codes=set(serialized_codes.split(MFAMethod._BACKUP_CODES_DELIMITER)), diff --git a/api/custom_auth/mfa/trench/views/base.py b/api/custom_auth/mfa/trench/views/base.py index 66f3f783667c..8078e74b258d 100644 --- a/api/custom_auth/mfa/trench/views/base.py +++ b/api/custom_auth/mfa/trench/views/base.py @@ -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: @@ -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"], ) @@ -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: diff --git a/api/environments/identities/managers.py b/api/environments/identities/managers.py index a0e5fb2f7db2..4a34bccbb592 100644 --- a/api/environments/identities/managers.py +++ b/api/environments/identities/managers.py @@ -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 diff --git a/api/environments/identities/migrations/0006_rename_environment_created_date_index.py b/api/environments/identities/migrations/0006_rename_environment_created_date_index.py new file mode 100644 index 000000000000..dafee6a41087 --- /dev/null +++ b/api/environments/identities/migrations/0006_rename_environment_created_date_index.py @@ -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"' + ) + ], + ) + ] diff --git a/api/environments/identities/models.py b/api/environments/identities/models.py index 73b374f619ac..441248990516 100644 --- a/api/environments/identities/models.py +++ b/api/environments/identities/models.py @@ -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 diff --git a/api/environments/managers.py b/api/environments/managers.py index d191316ff236..db7790736929 100644 --- a/api/environments/managers.py +++ b/api/environments/managers.py @@ -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 ( diff --git a/api/features/admin.py b/api/features/admin.py index 9d0a8099232c..1fc7261b559c 100644 --- a/api/features/admin.py +++ b/api/features/admin.py @@ -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) diff --git a/api/features/feature_health/services.py b/api/features/feature_health/services.py index cc4b48c574f1..11958603c40f 100644 --- a/api/features/feature_health/services.py +++ b/api/features/feature_health/services.py @@ -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, reason=json.dumps(reason), provider_name=feature_health_event.provider_name, external_id=feature_health_event.external_id, diff --git a/api/features/feature_states/permissions.py b/api/features/feature_states/permissions.py index d7bf46e80d9f..fc434c587e05 100644 --- a/api/features/feature_states/permissions.py +++ b/api/features/feature_states/permissions.py @@ -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 ) diff --git a/api/features/import_export/permissions.py b/api/features/import_export/permissions.py index cd004156366c..237697cb8320 100644 --- a/api/features/import_export/permissions.py +++ b/api/features/import_export/permissions.py @@ -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): @@ -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): @@ -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): @@ -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] diff --git a/api/features/permissions.py b/api/features/permissions.py index 72fbb439028c..bf19cca8cc1f 100644 --- a/api/features/permissions.py +++ b/api/features/permissions.py @@ -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 @@ -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, ) diff --git a/api/features/versioning/permissions.py b/api/features/versioning/permissions.py index f0d362593e45..e7072cf1e617 100644 --- a/api/features/versioning/permissions.py +++ b/api/features/versioning/permissions.py @@ -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( @@ -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, ) @@ -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 ) @@ -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, ) diff --git a/api/features/versioning/versioning_service.py b/api/features/versioning/versioning_service.py index b8d5d40d47ff..e027692d384c 100644 --- a/api/features/versioning/versioning_service.py +++ b/api/features/versioning/versioning_service.py @@ -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]: @@ -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, @@ -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]: diff --git a/api/organisations/chargebee/webhook_handlers.py b/api/organisations/chargebee/webhook_handlers.py index 242b0db3ead6..ab32b7f4b2d8 100644 --- a/api/organisations/chargebee/webhook_handlers.py +++ b/api/organisations/chargebee/webhook_handlers.py @@ -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 @@ -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() @@ -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"] @@ -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, diff --git a/api/organisations/permissions/permissions.py b/api/organisations/permissions/permissions.py index 5d9fa9c16e39..d31508c1a523 100644 --- a/api/organisations/permissions/permissions.py +++ b/api/organisations/permissions/permissions.py @@ -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] diff --git a/api/poetry.lock b/api/poetry.lock index c3e9df2a0ec0..64f640095a6e 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -925,7 +925,7 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -1268,18 +1268,18 @@ Django = ">=4.2" [[package]] name = "django" -version = "4.2.27" +version = "5.2.10" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "auth-controller", "dev", "ldap", "workflows"] files = [ - {file = "django-4.2.27-py3-none-any.whl", hash = "sha256:f393a394053713e7d213984555c5b7d3caeee78b2ccb729888a0774dff6c11a8"}, - {file = "django-4.2.27.tar.gz", hash = "sha256:b865fbe0f4a3d1ee36594c5efa42b20db3c8bbb10dff0736face1c6e4bda5b92"}, + {file = "django-5.2.10-py3-none-any.whl", hash = "sha256:cf85067a64250c95d5f9067b056c5eaa80591929f7e16fbcd997746e40d6c45c"}, + {file = "django-5.2.10.tar.gz", hash = "sha256:74df100784c288c50a2b5cad59631d71214f40f72051d5af3fdf220c20bdbbbe"}, ] [package.dependencies] -asgiref = ">=3.6.0,<4" +asgiref = ">=3.8.1" sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} @@ -1349,19 +1349,19 @@ Django = ">=2.2" [[package]] name = "django-debug-toolbar" -version = "3.2.4" +version = "6.2.0" description = "A configurable set of panels that display various debug information about the current request/response." optional = false -python-versions = ">=3.6" -groups = ["main"] +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "django-debug-toolbar-3.2.4.tar.gz", hash = "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9"}, - {file = "django_debug_toolbar-3.2.4-py3-none-any.whl", hash = "sha256:6b633b6cfee24f232d73569870f19aa86c819d750e7f3e833f2344a9eb4b4409"}, + {file = "django_debug_toolbar-6.2.0-py3-none-any.whl", hash = "sha256:1575461954e6befa720e999dec13fe4f1cc8baf40b6c3ac2aec5f340c0f9c85f"}, + {file = "django_debug_toolbar-6.2.0.tar.gz", hash = "sha256:dc1c174d8fb0ea01435e02d9ceef735cf62daf37c1a6a5692d33b4127327679b"}, ] [package.dependencies] -Django = ">=2.2" -sqlparse = ">=0.2.0" +django = ">=4.2.9" +sqlparse = ">=0.2" [[package]] name = "django-environ" @@ -1453,18 +1453,18 @@ Django = ">=3.2" [[package]] name = "django-multiselectfield" -version = "0.1.12" +version = "1.0.1" description = "Django multiple select field" optional = false python-versions = "*" groups = ["auth-controller"] files = [ - {file = "django-multiselectfield-0.1.12.tar.gz", hash = "sha256:d0a4c71568fb2332c71478ffac9f8708e01314a35cf923dfd7a191343452f9f9"}, - {file = "django_multiselectfield-0.1.12-py3-none-any.whl", hash = "sha256:c270faa7f80588214c55f2d68cbddb2add525c2aa830c216b8a198de914eb470"}, + {file = "django_multiselectfield-1.0.1-py3-none-any.whl", hash = "sha256:18dc14801f7eca844a48e21cba6d8ec35b9b581f2373bbb2cb75e6994518259a"}, + {file = "django_multiselectfield-1.0.1.tar.gz", hash = "sha256:3f8b4fff3e07d4a91c8bb4b809bc35caeb22b41769b606f4c9edc53b8d72a667"}, ] [package.dependencies] -django = ">=1.4" +django = ">=3.2" [[package]] name = "django-ordered-model" @@ -1552,21 +1552,16 @@ six = "*" [[package]] name = "django-softdelete" -version = "0.10.5" +version = "0.11.5" description = "Soft delete support for Django ORM, with undelete." optional = false python-versions = "*" groups = ["main"] files = [ - {file = "django-softdelete-0.10.5.tar.gz", hash = "sha256:7b1a31c889ed482b42c932a6d69f3653351e5699a52ebed2cfb3427a0fb7150f"}, - {file = "django_softdelete-0.10.5-py3-none-any.whl", hash = "sha256:7ddb0af2118619e7813fea4335e486d49b63ca7b0fd38d0b5ab3723cda40c577"}, + {file = "django_softdelete-0.11.5-py3-none-any.whl", hash = "sha256:72d086ea4572b85a7fe027dcf58625e091202c924c58aca2e3d4f7e6dde4c72f"}, + {file = "django_softdelete-0.11.5.tar.gz", hash = "sha256:d2e1c11cad2e664f2ec0644b9e23c7e8da4e59851fab312be69b5cf6ce12c7ab"}, ] -[package.dependencies] -setuptools = "*" -six = "*" -wheel = "*" - [[package]] name = "django-storages" version = "1.10.1" @@ -1592,56 +1587,43 @@ sftp = ["paramiko"] [[package]] name = "django-stubs" -version = "5.1.3" +version = "5.2.8" description = "Mypy stubs for Django" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "django_stubs-5.1.3-py3-none-any.whl", hash = "sha256:716758ced158b439213062e52de6df3cff7c586f9f9ad7ab59210efbea5dfe78"}, - {file = "django_stubs-5.1.3.tar.gz", hash = "sha256:8c230bc5bebee6da282ba8a27ad1503c84a0c4cd2f46e63d149e76d2a63e639a"}, + {file = "django_stubs-5.2.8-py3-none-any.whl", hash = "sha256:a3c63119fd7062ac63d58869698d07c9e5ec0561295c4e700317c54e8d26716c"}, + {file = "django_stubs-5.2.8.tar.gz", hash = "sha256:9bba597c9a8ed8c025cae4696803d5c8be1cf55bfc7648a084cbf864187e2f8b"}, ] [package.dependencies] -asgiref = "*" django = "*" -django-stubs-ext = ">=5.1.3" -types-PyYAML = "*" +django-stubs-ext = ">=5.2.8" +types-pyyaml = "*" typing-extensions = ">=4.11.0" [package.extras] -compatible-mypy = ["mypy (>=1.12,<1.16)"] +compatible-mypy = ["mypy (>=1.13,<1.20)"] oracle = ["oracledb"] -redis = ["redis"] +redis = ["redis", "types-redis"] [[package]] name = "django-stubs-ext" -version = "5.1.3" +version = "5.2.8" description = "Monkey-patching and extensions for django-stubs" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "django_stubs_ext-5.1.3-py3-none-any.whl", hash = "sha256:64561fbc53e963cc1eed2c8eb27e18b8e48dcb90771205180fe29fc8a59e55fd"}, - {file = "django_stubs_ext-5.1.3.tar.gz", hash = "sha256:3e60f82337f0d40a362f349bf15539144b96e4ceb4dbd0239be1cd71f6a74ad0"}, + {file = "django_stubs_ext-5.2.8-py3-none-any.whl", hash = "sha256:1dd5470c9675591362c78a157a3cf8aec45d0e7a7f0cf32f227a1363e54e0652"}, + {file = "django_stubs_ext-5.2.8.tar.gz", hash = "sha256:b39938c46d7a547cd84e4a6378dbe51a3dd64d70300459087229e5fee27e5c6b"}, ] [package.dependencies] django = "*" typing-extensions = "*" -[[package]] -name = "django-templated-mail" -version = "1.1.1" -description = "Send emails using Django template system." -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "django-templated-mail-1.1.1.tar.gz", hash = "sha256:8db807effebb42a532622e2d142dfd453dafcd0d7794c4c3332acb90656315f9"}, - {file = "django_templated_mail-1.1.1-py3-none-any.whl", hash = "sha256:f7127e1e31d7cad4e6c4b4801d25814d4b8782627ead76f4a75b3b7650687556"}, -] - [[package]] name = "django-test-migrations" version = "1.5.0" @@ -1771,19 +1753,19 @@ markdown = ["types-Markdown (>=0.1.5)"] [[package]] name = "djoser" -version = "2.3.0" +version = "2.3.3" description = "REST implementation of Django authentication system." optional = false -python-versions = "<4.0,>=3.8" +python-versions = "<4.0,>=3.9" groups = ["main"] files = [ - {file = "djoser-2.3.0-py3-none-any.whl", hash = "sha256:6af76cb8d73f8a879ab417c1251f5aa0242c4e3c4a8fee3a6393f1126874b269"}, - {file = "djoser-2.3.0.tar.gz", hash = "sha256:c46e4b609348b824ba138aba914ca8a89bb42b7d23975c34e2702ea04b13c989"}, + {file = "djoser-2.3.3-py3-none-any.whl", hash = "sha256:b97d233b626c26ebccb09f5614420873ad78b8b1fb1459c76475b05319bae567"}, + {file = "djoser-2.3.3.tar.gz", hash = "sha256:6ceeea9898cbdd585f1daa1ee9d46270600c0401dcd2d1db6f7894782006f6a6"}, ] [package.dependencies] -django = ">=3.0.0" -django-templated-mail = ">=1.1.1,<2.0.0" +django = ">=3.2" +djangorestframework = ">=3.14" djangorestframework-simplejwt = ">=5.0,<6.0" social-auth-app-django = ">=5.0.0,<6.0.0" @@ -1988,13 +1970,13 @@ files = [] develop = false [package.dependencies] -django-multiselectfield = "0.1.12" +django-multiselectfield = ">=1.0.1,<2" [package.source] type = "git" url = "https://github.com/flagsmith/flagsmith-auth-controller" -reference = "v0.1.4" -resolved_reference = "f5dfd44935c41bb6d777deb06c7e06b3e44210ad" +reference = "v0.2.0" +resolved_reference = "b7fa1f42c333b443763548ea1fe0054f07cdf641" [[package]] name = "flagsmith-common" @@ -2052,7 +2034,7 @@ semver = ">=3.0.1" type = "git" url = "https://github.com/Flagsmith/flagsmith-engine" reference = "fix/missing-export" -resolved_reference = "f3780d62117211990f51529e58a380b9894f24fb" +resolved_reference = "7a15176f25bd00c3d93a8e56238382f7818fa557" [[package]] name = "flagsmith-ldap" @@ -2117,28 +2099,26 @@ files = [ [[package]] name = "google-api-core" -version = "2.29.0" +version = "2.11.1" description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9"}, - {file = "google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7"}, + {file = "google-api-core-2.11.1.tar.gz", hash = "sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a"}, + {file = "google_api_core-2.11.1-py3-none-any.whl", hash = "sha256:d92a5a92dc36dd4f4b9ee4e55528a90e432b059f93aee6ad857f9de8cc7ae94a"}, ] [package.dependencies] -google-auth = ">=2.14.1,<3.0.0" -googleapis-common-protos = ">=1.56.2,<2.0.0" -proto-plus = ">=1.22.3,<2.0.0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" -requests = ">=2.18.0,<3.0.0" +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" [package.extras] -async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.0)"] -grpc = ["grpcio (>=1.33.2,<2.0.0)", "grpcio (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio (>=1.75.1,<2.0.0) ; python_version >= \"3.14\"", "grpcio-status (>=1.33.2,<2.0.0)", "grpcio-status (>=1.49.1,<2.0.0) ; python_version >= \"3.11\"", "grpcio-status (>=1.75.1,<2.0.0) ; python_version >= \"3.14\""] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" @@ -2521,21 +2501,21 @@ files = [ [[package]] name = "googleapis-common-protos" -version = "1.72.0" +version = "1.60.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, - {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, + {file = "googleapis-common-protos-1.60.0.tar.gz", hash = "sha256:e73ebb404098db405ba95d1e1ae0aa91c3e15a71da031a2eeb6b2e23e7bc3708"}, + {file = "googleapis_common_protos-1.60.0-py2.py3-none-any.whl", hash = "sha256:69f9bbcc6acde92cab2db95ce30a70bd2b81d20b12eff3f1aabaffcbe8a93918"}, ] [package.dependencies] -protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" [package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0)"] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "gunicorn" @@ -2893,16 +2873,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -3278,42 +3248,25 @@ files = [ [package.extras] twisted = ["twisted"] -[[package]] -name = "proto-plus" -version = "1.27.0" -description = "Beautiful, Pythonic protocol buffers" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82"}, - {file = "proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<7.0.0" - -[package.extras] -testing = ["google-api-core (>=1.31.5)"] - [[package]] name = "protobuf" -version = "6.33.5" +version = "4.25.8" description = "" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, - {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, - {file = "protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd"}, - {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0"}, - {file = "protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c"}, - {file = "protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a"}, - {file = "protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02"}, - {file = "protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c"}, + {file = "protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0"}, + {file = "protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9"}, + {file = "protobuf-4.25.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca809b42f4444f144f2115c4c1a747b9a404d590f18f37e9402422033e464e0f"}, + {file = "protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9ad7ef62d92baf5a8654fbb88dac7fa5594cfa70fd3440488a5ca3bfc6d795a7"}, + {file = "protobuf-4.25.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:83e6e54e93d2b696a92cad6e6efc924f3850f82b52e1563778dfab8b355101b0"}, + {file = "protobuf-4.25.8-cp38-cp38-win32.whl", hash = "sha256:27d498ffd1f21fb81d987a041c32d07857d1d107909f5134ba3350e1ce80a4af"}, + {file = "protobuf-4.25.8-cp38-cp38-win_amd64.whl", hash = "sha256:d552c53d0415449c8d17ced5c341caba0d89dbf433698e1436c8fa0aae7808a3"}, + {file = "protobuf-4.25.8-cp39-cp39-win32.whl", hash = "sha256:077ff8badf2acf8bc474406706ad890466274191a48d0abd3bd6987107c9cde5"}, + {file = "protobuf-4.25.8-cp39-cp39-win_amd64.whl", hash = "sha256:f4510b93a3bec6eba8fd8f1093e9d7fb0d4a24d1a81377c10c0e5bbfe9e4ed24"}, + {file = "protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59"}, + {file = "protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd"}, ] [[package]] @@ -4144,7 +4097,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -4152,16 +4104,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -4178,7 +4122,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -4186,7 +4129,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -5209,21 +5151,6 @@ markupsafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] -[[package]] -name = "wheel" -version = "0.41.1" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "wheel-0.41.1-py3-none-any.whl", hash = "sha256:473219bd4cbedc62cea0cb309089b593e47c15c4a2531015f94e4e3b9a0f6981"}, - {file = "wheel-0.41.1.tar.gz", hash = "sha256:12b911f083e876e10c595779709f8a88a59f45aacc646492a67fe9ef796c1b47"}, -] - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] - [[package]] name = "whitenoise" version = "6.0.0" @@ -5399,4 +5326,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">3.11,<3.13" -content-hash = "624fae53a86a490cc3a58e59a1f3840251a47278613cf8fad7e30a31b525fc4d" +content-hash = "dc124122fbec690a393443bd3b099130f2aa23c1bdc20f03ad7ced83f549f8c4" diff --git a/api/projects/code_references/permissions.py b/api/projects/code_references/permissions.py index d590a1720cc5..94744f10e2bd 100644 --- a/api/projects/code_references/permissions.py +++ b/api/projects/code_references/permissions.py @@ -14,7 +14,7 @@ def has_permission(self, request: Request, view: APIView) -> bool: assert not isinstance(request.user, AnonymousUser) project = Project.objects.get(id=view.kwargs["project_pk"]) - return request.user.has_project_permission(VIEW_PROJECT, project) + return request.user.has_project_permission(VIEW_PROJECT, project) # type: ignore[no-any-return] class SubmitFeatureFlagCodeReferences(_BaseCodeReferencePermission): diff --git a/api/projects/models.py b/api/projects/models.py index bf21604a4f95..69443c19f1fa 100644 --- a/api/projects/models.py +++ b/api/projects/models.py @@ -113,7 +113,7 @@ class Project(LifecycleModelMixin, SoftDeleteExportableModel): # type: ignore[d class Meta: ordering = ["id"] - def __str__(self): # type: ignore[no-untyped-def] + def __str__(self): # type: ignore[no-untyped-def] # pragma: no cover return "Project %s" % self.name @property diff --git a/api/pyproject.toml b/api/pyproject.toml index bf2dbcb69db5..2ece2a513d5f 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -102,7 +102,7 @@ enabled = true [tool.poetry.dependencies] python = ">3.11,<3.13" -django = "~4.2.27" +django = ">=5,<6" rudder-sdk-python = "~2.0.2" segment-analytics-python = "~2.2.3" backoff = "~2.2.1" @@ -129,7 +129,6 @@ python-http-client = "~3.3.7" django-health-check = "~3.18.2" django-admin-sso = "~5.2.0" drf-spectacular = "~0.28.0" -django-debug-toolbar = "~3.2.1" sentry-sdk = "~2.8.0" environs = "^14.1.1" django-lifecycle = "~1.2.4" @@ -142,9 +141,9 @@ asgiref = "~3.8.1" djangorestframework-api-key = "~2.2.0" pymemcache = "~4.0.0" google-re2 = "^1.0" -django-softdelete = "~0.10.5" +django-softdelete = "^0.11.5" simplejson = "~3.19.1" -djoser = "~2.3.0" +djoser = "^2.3.0" django-storages = "~1.10.1" django-environ = "~0.4.5" influxdb-client = "~1.50.0" @@ -176,7 +175,7 @@ django_cockroachdb = "~4.2" optional = true [tool.poetry.group.auth-controller.dependencies] -flagsmith-auth-controller = { git = "https://github.com/flagsmith/flagsmith-auth-controller", tag = "v0.1.4" } +flagsmith-auth-controller = { git = "https://github.com/flagsmith/flagsmith-auth-controller", tag = "v0.2.0" } [tool.poetry.group.saml] optional = true @@ -245,6 +244,7 @@ ruff = "^0.9.7" flagsmith-common = { version = "*", extras = ["test-tools"] } pytest-responses = "^0.5.1" diff-cover = "^10.1.0" +django-debug-toolbar = "*" [build-system] requires = ["poetry>=2.0.0"] diff --git a/api/tests/unit/environments/dynamodb/test_unit_migrator.py b/api/tests/unit/environments/dynamodb/test_unit_migrator.py index 7b46b4f990b6..a1815879b424 100644 --- a/api/tests/unit/environments/dynamodb/test_unit_migrator.py +++ b/api/tests/unit/environments/dynamodb/test_unit_migrator.py @@ -1,4 +1,4 @@ -from pytest_django.asserts import assertQuerysetEqual as assert_queryset_equal +from pytest_django.asserts import assertQuerySetEqual as assert_queryset_equal from environments.dynamodb.migrator import IdentityMigrator from environments.dynamodb.types import ( diff --git a/api/tests/unit/environments/test_unit_environments_models.py b/api/tests/unit/environments/test_unit_environments_models.py index 6cdca25b44db..2f17845bfc7e 100644 --- a/api/tests/unit/environments/test_unit_environments_models.py +++ b/api/tests/unit/environments/test_unit_environments_models.py @@ -12,7 +12,7 @@ from django.utils import timezone from mypy_boto3_dynamodb.service_resource import Table from pytest_django import DjangoAssertNumQueries -from pytest_django.asserts import assertQuerysetEqual as assert_queryset_equal +from pytest_django.asserts import assertQuerySetEqual as assert_queryset_equal from pytest_mock import MockerFixture from audit.models import AuditLog diff --git a/api/tests/unit/organisations/test_unit_organisations_views.py b/api/tests/unit/organisations/test_unit_organisations_views.py index 0aabd6ce7012..e7593a6cc0af 100644 --- a/api/tests/unit/organisations/test_unit_organisations_views.py +++ b/api/tests/unit/organisations/test_unit_organisations_views.py @@ -1,5 +1,6 @@ import json from datetime import datetime, timedelta +from datetime import timezone as dttz from typing import Type from unittest import mock from unittest.mock import MagicMock @@ -785,7 +786,7 @@ def test_chargebee_webhook( 15, 33, 9, - tzinfo=timezone.utc, # type: ignore[attr-defined] + tzinfo=dttz.utc, ) assert subscription_cache.current_billing_term_starts_at == datetime( 2023, @@ -794,7 +795,7 @@ def test_chargebee_webhook( 15, 33, 9, - tzinfo=timezone.utc, # type: ignore[attr-defined] + tzinfo=dttz.utc, ) assert subscription_cache.allowed_projects is None assert subscription_cache.allowed_30d_api_calls == api_calls @@ -836,9 +837,7 @@ def test_when_subscription_is_set_to_non_renewing_then_cancellation_date_set_and subscription.refresh_from_db() assert subscription.cancellation_date == datetime.utcfromtimestamp( current_term_end - ).replace( - tzinfo=timezone.utc # type: ignore[attr-defined] - ) + ).replace(tzinfo=dttz.utc) # and assert len(mail.outbox) == 1 @@ -915,9 +914,7 @@ def test_when_subscription_is_cancelled_then_cancellation_date_set_and_alert_sen subscription.refresh_from_db() assert subscription.cancellation_date == datetime.utcfromtimestamp( current_term_end - ).replace( - tzinfo=timezone.utc # type: ignore[attr-defined] - ) + ).replace(tzinfo=dttz.utc) # and assert len(mail.outbox) == 1 diff --git a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py index 102b6d4eeb93..21bd46c1fa1d 100644 --- a/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py +++ b/api/tests/unit/sales_dashboard/test_unit_sales_dashboard_views.py @@ -96,7 +96,7 @@ def test_list_organisations_search_by_name( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[index] def test_list_organisations_search_by_subscription_id( @@ -114,7 +114,7 @@ def test_list_organisations_search_by_subscription_id( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[index] def test_list_organisations_search_by_user_email( @@ -132,7 +132,7 @@ def test_list_organisations_search_by_user_email( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[index] def test_list_organisations_search_by_user_email_for_non_existent_user( @@ -152,7 +152,7 @@ def test_list_organisations_search_by_user_email_for_non_existent_user( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [] # type: ignore[index] def test_list_organisations_search_by_domain( @@ -171,7 +171,7 @@ def test_list_organisations_search_by_domain( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[index] def test_list_organisations_filter_plan( @@ -190,7 +190,7 @@ def test_list_organisations_filter_plan( # Then assert response.status_code == 200 - assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[attr-defined] + assert list(response.context_data["organisation_list"]) == [organisation] # type: ignore[index] def test_list_organisations_fails_if_not_staff( diff --git a/api/users/migrations/0045_add_through_fields_metadata_for_django_5_upgrade.py b/api/users/migrations/0045_add_through_fields_metadata_for_django_5_upgrade.py new file mode 100644 index 000000000000..0121c29750a0 --- /dev/null +++ b/api/users/migrations/0045_add_through_fields_metadata_for_django_5_upgrade.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.9 on 2025-12-31 15:38 +# Note: This migration is a no-op, and just adds the `through_fields` attribute +# seemingly needed by the state in django5 + + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0044_remove_users_from_groups_in_orgs_they_do_not_belong_to"), + ] + + operations = [ + migrations.AlterField( + model_name="userpermissiongroup", + name="users", + field=models.ManyToManyField( + blank=True, + related_name="permission_groups", + through="users.UserPermissionGroupMembership", + through_fields=["userpermissiongroup", "ffadminuser"], # type: ignore[arg-type] + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/api/users/models.py b/api/users/models.py index ddc59b2f5c40..e3189af3239e 100644 --- a/api/users/models.py +++ b/api/users/models.py @@ -1,4 +1,6 @@ import logging +import secrets +import string import typing import uuid @@ -105,6 +107,13 @@ def get_by_natural_key(self, email): # type: ignore[no-untyped-def] # Used to allow case insensitive login return self.get(email__iexact=email) + def make_random_password( + self, + length: int = 10, + allowed_chars: str = string.ascii_letters + string.digits, + ) -> str: + return "".join(secrets.choice(allowed_chars) for _ in range(length)) + class FFAdminUser(LifecycleModel, AbstractUser): # type: ignore[django-manager-missing,misc] organisations = models.ManyToManyField(