remove celery everywhere (almost)
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
		
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -100,9 +100,6 @@ ipython_config.py | ||||
| # pyenv | ||||
| .python-version | ||||
|  | ||||
| # celery beat schedule file | ||||
| celerybeat-schedule | ||||
|  | ||||
| # SageMath parsed files | ||||
| *.sage.py | ||||
|  | ||||
| @ -166,8 +163,6 @@ dmypy.json | ||||
|  | ||||
| # pyenv | ||||
|  | ||||
| # celery beat schedule file | ||||
|  | ||||
| # SageMath parsed files | ||||
|  | ||||
| # Environments | ||||
|  | ||||
| @ -1,57 +0,0 @@ | ||||
| """authentik administration overview""" | ||||
|  | ||||
| from socket import gethostname | ||||
|  | ||||
| from django.conf import settings | ||||
| from drf_spectacular.utils import extend_schema, inline_serializer | ||||
| from packaging.version import parse | ||||
| from rest_framework.fields import BooleanField, CharField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
|  | ||||
| from authentik import get_full_version | ||||
| from authentik.rbac.permissions import HasPermission | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
|  | ||||
| class WorkerView(APIView): | ||||
|     """Get currently connected worker count.""" | ||||
|  | ||||
|     permission_classes = [HasPermission("authentik_rbac.view_system_info")] | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses=inline_serializer( | ||||
|             "Worker", | ||||
|             fields={ | ||||
|                 "worker_id": CharField(), | ||||
|                 "version": CharField(), | ||||
|                 "version_matching": BooleanField(), | ||||
|             }, | ||||
|             many=True, | ||||
|         ) | ||||
|     ) | ||||
|     def get(self, request: Request) -> Response: | ||||
|         """Get currently connected worker count.""" | ||||
|         raw: list[dict[str, dict]] = CELERY_APP.control.ping(timeout=0.5) | ||||
|         our_version = parse(get_full_version()) | ||||
|         response = [] | ||||
|         for worker in raw: | ||||
|             key = list(worker.keys())[0] | ||||
|             version = worker[key].get("version") | ||||
|             version_matching = False | ||||
|             if version: | ||||
|                 version_matching = parse(version) == our_version | ||||
|             response.append( | ||||
|                 {"worker_id": key, "version": version, "version_matching": version_matching} | ||||
|             ) | ||||
|         # In debug we run with `task_always_eager`, so tasks are ran on the main process | ||||
|         if settings.DEBUG:  # pragma: no cover | ||||
|             response.append( | ||||
|                 { | ||||
|                     "worker_id": f"authentik-debug@{gethostname()}", | ||||
|                     "version": get_full_version(), | ||||
|                     "version_matching": True, | ||||
|                 } | ||||
|             ) | ||||
|         return Response(response) | ||||
| @ -5,7 +5,6 @@ from packaging.version import parse | ||||
| from prometheus_client import Gauge | ||||
|  | ||||
| from authentik import get_full_version | ||||
| from authentik.root.celery import CELERY_APP | ||||
| from authentik.root.monitoring import monitoring_set | ||||
|  | ||||
| GAUGE_WORKERS = Gauge( | ||||
| @ -21,15 +20,16 @@ _version = parse(get_full_version()) | ||||
| @receiver(monitoring_set) | ||||
| def monitoring_set_workers(sender, **kwargs): | ||||
|     """Set worker gauge""" | ||||
|     raw: list[dict[str, dict]] = CELERY_APP.control.ping(timeout=0.5) | ||||
|     worker_version_count = {} | ||||
|     for worker in raw: | ||||
|         key = list(worker.keys())[0] | ||||
|         version = worker[key].get("version") | ||||
|         version_matching = False | ||||
|         if version: | ||||
|             version_matching = parse(version) == _version | ||||
|         worker_version_count.setdefault(version, {"count": 0, "matching": version_matching}) | ||||
|         worker_version_count[version]["count"] += 1 | ||||
|     for version, stats in worker_version_count.items(): | ||||
|         GAUGE_WORKERS.labels(version, stats["matching"]).set(stats["count"]) | ||||
|     # TODO | ||||
|     # raw: list[dict[str, dict]] = app.control.ping(timeout=0.5) | ||||
|     # worker_version_count = {} | ||||
|     # for worker in raw: | ||||
|     #     key = list(worker.keys())[0] | ||||
|     #     version = worker[key].get("version") | ||||
|     #     version_matching = False | ||||
|     #     if version: | ||||
|     #         version_matching = parse(version) == _version | ||||
|     #     worker_version_count.setdefault(version, {"count": 0, "matching": version_matching}) | ||||
|     #     worker_version_count[version]["count"] += 1 | ||||
|     # for version, stats in worker_version_count.items(): | ||||
|     #     GAUGE_WORKERS.labels(version, stats["matching"]).set(stats["count"]) | ||||
|  | ||||
| @ -7,7 +7,6 @@ from authentik.admin.api.metrics import AdministrationMetricsViewSet | ||||
| from authentik.admin.api.system import SystemView | ||||
| from authentik.admin.api.version import VersionView | ||||
| from authentik.admin.api.version_history import VersionHistoryViewSet | ||||
| from authentik.admin.api.workers import WorkerView | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("admin/apps", AppsViewSet, "apps"), | ||||
| @ -19,6 +18,5 @@ api_urlpatterns = [ | ||||
|     ), | ||||
|     path("admin/version/", VersionView.as_view(), name="admin_version"), | ||||
|     ("admin/version/history", VersionHistoryViewSet, "version_history"), | ||||
|     path("admin/workers/", WorkerView.as_view(), name="admin_workers"), | ||||
|     path("admin/system/", SystemView.as_view(), name="admin_system"), | ||||
| ] | ||||
|  | ||||
| @ -57,7 +57,6 @@ from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import ( | ||||
|     EndpointDeviceConnection, | ||||
| ) | ||||
| from authentik.events.logs import LogEvent, capture_logs | ||||
| from authentik.events.models import SystemTask | ||||
| from authentik.events.utils import cleanse_dict | ||||
| from authentik.flows.models import FlowToken, Stage | ||||
| from authentik.lib.models import SerializerModel | ||||
| @ -119,7 +118,6 @@ def excluded_models() -> list[type[Model]]: | ||||
|         SCIMProviderGroup, | ||||
|         SCIMProviderUser, | ||||
|         Tenant, | ||||
|         SystemTask, | ||||
|         Task, | ||||
|         ConnectionToken, | ||||
|         AuthorizationCode, | ||||
|  | ||||
| @ -1,104 +0,0 @@ | ||||
| """Tasks API""" | ||||
|  | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.contrib import messages | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from drf_spectacular.types import OpenApiTypes | ||||
| from drf_spectacular.utils import OpenApiResponse, extend_schema | ||||
| from rest_framework.decorators import action | ||||
| from rest_framework.fields import ( | ||||
|     CharField, | ||||
|     ChoiceField, | ||||
|     DateTimeField, | ||||
|     FloatField, | ||||
|     SerializerMethodField, | ||||
| ) | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ReadOnlyModelViewSet | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.core.api.utils import ModelSerializer | ||||
| from authentik.events.logs import LogEventSerializer | ||||
| from authentik.events.models import SystemTask, TaskStatus | ||||
| from authentik.rbac.decorators import permission_required | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class SystemTaskSerializer(ModelSerializer): | ||||
|     """Serialize TaskInfo and TaskResult""" | ||||
|  | ||||
|     name = CharField() | ||||
|     full_name = SerializerMethodField() | ||||
|     uid = CharField(required=False) | ||||
|     description = CharField() | ||||
|     start_timestamp = DateTimeField(read_only=True) | ||||
|     finish_timestamp = DateTimeField(read_only=True) | ||||
|     duration = FloatField(read_only=True) | ||||
|  | ||||
|     status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus]) | ||||
|     messages = LogEventSerializer(many=True) | ||||
|  | ||||
|     def get_full_name(self, instance: SystemTask) -> str: | ||||
|         """Get full name with UID""" | ||||
|         if instance.uid: | ||||
|             return f"{instance.name}:{instance.uid}" | ||||
|         return instance.name | ||||
|  | ||||
|     class Meta: | ||||
|         model = SystemTask | ||||
|         fields = [ | ||||
|             "uuid", | ||||
|             "name", | ||||
|             "full_name", | ||||
|             "uid", | ||||
|             "description", | ||||
|             "start_timestamp", | ||||
|             "finish_timestamp", | ||||
|             "duration", | ||||
|             "status", | ||||
|             "messages", | ||||
|             "expires", | ||||
|             "expiring", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class SystemTaskViewSet(ReadOnlyModelViewSet): | ||||
|     """Read-only view set that returns all background tasks""" | ||||
|  | ||||
|     queryset = SystemTask.objects.all() | ||||
|     serializer_class = SystemTaskSerializer | ||||
|     filterset_fields = ["name", "uid", "status"] | ||||
|     ordering = ["name", "uid", "status"] | ||||
|     search_fields = ["name", "description", "uid", "status"] | ||||
|  | ||||
|     @permission_required(None, ["authentik_events.run_task"]) | ||||
|     @extend_schema( | ||||
|         request=OpenApiTypes.NONE, | ||||
|         responses={ | ||||
|             204: OpenApiResponse(description="Task retried successfully"), | ||||
|             404: OpenApiResponse(description="Task not found"), | ||||
|             500: OpenApiResponse(description="Failed to retry task"), | ||||
|         }, | ||||
|     ) | ||||
|     @action(detail=True, methods=["POST"], permission_classes=[]) | ||||
|     def run(self, request: Request, pk=None) -> Response: | ||||
|         """Run task""" | ||||
|         task: SystemTask = self.get_object() | ||||
|         try: | ||||
|             task_module = import_module(task.task_call_module) | ||||
|             task_func = getattr(task_module, task.task_call_func) | ||||
|             LOGGER.info("Running task", task=task_func) | ||||
|             task_func.delay(*task.task_call_args, **task.task_call_kwargs) | ||||
|             messages.success( | ||||
|                 self.request, | ||||
|                 _("Successfully started task {name}.".format_map({"name": task.name})), | ||||
|             ) | ||||
|             return Response(status=204) | ||||
|         except (ImportError, AttributeError) as exc:  # pragma: no cover | ||||
|             LOGGER.warning("Failed to run task, remove state", task=task.name, exc=exc) | ||||
|             # if we get an import error, the module path has probably changed | ||||
|             task.delete() | ||||
|             return Response(status=500) | ||||
| @ -1,12 +1,11 @@ | ||||
| """authentik events app""" | ||||
|  | ||||
| from celery.schedules import crontab | ||||
| from prometheus_client import Gauge, Histogram | ||||
|  | ||||
| from authentik.blueprints.apps import ManagedAppConfig | ||||
| from authentik.lib.config import CONFIG, ENV_PREFIX | ||||
| from authentik.lib.utils.reflection import path_to_class | ||||
| from authentik.root.celery import CELERY_APP | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
| from authentik.tasks.schedules.lib import ScheduleSpec | ||||
|  | ||||
| # TODO: Deprecated metric - remove in 2024.2 or later | ||||
| GAUGE_TASKS = Gauge( | ||||
| @ -35,6 +34,17 @@ class AuthentikEventsConfig(ManagedAppConfig): | ||||
|     verbose_name = "authentik Events" | ||||
|     default = True | ||||
|  | ||||
|     @property | ||||
|     def tenant_schedule_specs(self) -> list[ScheduleSpec]: | ||||
|         from authentik.events.tasks import notification_cleanup | ||||
|  | ||||
|         return [ | ||||
|             ScheduleSpec( | ||||
|                 actor=notification_cleanup, | ||||
|                 crontab=f"{fqdn_rand('notification_cleanup')} */8 * * *", | ||||
|             ), | ||||
|         ] | ||||
|  | ||||
|     @ManagedAppConfig.reconcile_global | ||||
|     def check_deprecations(self): | ||||
|         """Check for config deprecations""" | ||||
| @ -56,29 +66,3 @@ class AuthentikEventsConfig(ManagedAppConfig): | ||||
|                 replacement_env=replace_env, | ||||
|                 message=msg, | ||||
|             ).save() | ||||
|  | ||||
|     @ManagedAppConfig.reconcile_tenant | ||||
|     def run_scheduled_tasks(self): | ||||
|         """Run schedule tasks which are behind schedule (only applies | ||||
|         to tasks of which we keep metrics)""" | ||||
|         from authentik.events.models import TaskStatus | ||||
|         from authentik.events.system_tasks import SystemTask as CelerySystemTask | ||||
|  | ||||
|         for task in CELERY_APP.conf["beat_schedule"].values(): | ||||
|             schedule = task["schedule"] | ||||
|             if not isinstance(schedule, crontab): | ||||
|                 continue | ||||
|             task_class: CelerySystemTask = path_to_class(task["task"]) | ||||
|             if not isinstance(task_class, CelerySystemTask): | ||||
|                 continue | ||||
|             db_task = task_class.db() | ||||
|             if not db_task: | ||||
|                 continue | ||||
|             due, _ = schedule.is_due(db_task.finish_timestamp) | ||||
|             if due or db_task.status == TaskStatus.UNKNOWN: | ||||
|                 self.logger.debug("Running past-due scheduled task", task=task["task"]) | ||||
|                 task_class.apply_async( | ||||
|                     args=task.get("args", None), | ||||
|                     kwargs=task.get("kwargs", None), | ||||
|                     **task.get("options", {}), | ||||
|                 ) | ||||
|  | ||||
							
								
								
									
										16
									
								
								authentik/events/migrations/0010_delete_systemtask.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								authentik/events/migrations/0010_delete_systemtask.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| # Generated by Django 5.1.9 on 2025-06-06 13:25 | ||||
|  | ||||
| from django.db import migrations | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_events", "0009_remove_notificationtransport_webhook_mapping_and_more"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.DeleteModel( | ||||
|             name="SystemTask", | ||||
|         ), | ||||
|     ] | ||||
| @ -10,7 +10,7 @@ from smtplib import SMTPException | ||||
| from uuid import uuid4 | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.db import connection, models | ||||
| from django.db import models | ||||
| from django.db.models import Count, ExpressionWrapper, F | ||||
| from django.db.models.fields import DurationField | ||||
| from django.db.models.functions import Extract | ||||
| @ -32,7 +32,6 @@ from authentik.core.middleware import ( | ||||
|     SESSION_KEY_IMPERSONATE_USER, | ||||
| ) | ||||
| from authentik.core.models import ExpiringModel, Group, PropertyMapping, User | ||||
| from authentik.events.apps import GAUGE_TASKS, SYSTEM_TASK_STATUS, SYSTEM_TASK_TIME | ||||
| from authentik.events.context_processors.base import get_context_processors | ||||
| from authentik.events.utils import ( | ||||
|     cleanse_dict, | ||||
| @ -654,73 +653,3 @@ class NotificationWebhookMapping(PropertyMapping): | ||||
|     class Meta: | ||||
|         verbose_name = _("Webhook Mapping") | ||||
|         verbose_name_plural = _("Webhook Mappings") | ||||
|  | ||||
|  | ||||
| class TaskStatus(models.TextChoices): | ||||
|     """Possible states of tasks""" | ||||
|  | ||||
|     UNKNOWN = "unknown" | ||||
|     SUCCESSFUL = "successful" | ||||
|     WARNING = "warning" | ||||
|     ERROR = "error" | ||||
|  | ||||
|  | ||||
| class SystemTask(SerializerModel, ExpiringModel): | ||||
|     """Info about a system task running in the background along with details to restart the task""" | ||||
|  | ||||
|     uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) | ||||
|     name = models.TextField() | ||||
|     uid = models.TextField(null=True) | ||||
|  | ||||
|     start_timestamp = models.DateTimeField(default=now) | ||||
|     finish_timestamp = models.DateTimeField(default=now) | ||||
|     duration = models.FloatField(default=0) | ||||
|  | ||||
|     status = models.TextField(choices=TaskStatus.choices) | ||||
|  | ||||
|     description = models.TextField(null=True) | ||||
|     messages = models.JSONField() | ||||
|  | ||||
|     task_call_module = models.TextField() | ||||
|     task_call_func = models.TextField() | ||||
|     task_call_args = models.JSONField(default=list) | ||||
|     task_call_kwargs = models.JSONField(default=dict) | ||||
|  | ||||
|     @property | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         from authentik.events.api.tasks import SystemTaskSerializer | ||||
|  | ||||
|         return SystemTaskSerializer | ||||
|  | ||||
|     def update_metrics(self): | ||||
|         """Update prometheus metrics""" | ||||
|         # TODO: Deprecated metric - remove in 2024.2 or later | ||||
|         GAUGE_TASKS.labels( | ||||
|             tenant=connection.schema_name, | ||||
|             task_name=self.name, | ||||
|             task_uid=self.uid or "", | ||||
|             status=self.status.lower(), | ||||
|         ).set(self.duration) | ||||
|         SYSTEM_TASK_TIME.labels( | ||||
|             tenant=connection.schema_name, | ||||
|             task_name=self.name, | ||||
|             task_uid=self.uid or "", | ||||
|         ).observe(self.duration) | ||||
|         SYSTEM_TASK_STATUS.labels( | ||||
|             tenant=connection.schema_name, | ||||
|             task_name=self.name, | ||||
|             task_uid=self.uid or "", | ||||
|             status=self.status.lower(), | ||||
|         ).inc() | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         return f"System Task {self.name}" | ||||
|  | ||||
|     class Meta: | ||||
|         unique_together = (("name", "uid"),) | ||||
|         # Remove "add", "change" and "delete" permissions as those are not used | ||||
|         default_permissions = ["view"] | ||||
|         permissions = [("run_task", _("Run task"))] | ||||
|         verbose_name = _("System Task") | ||||
|         verbose_name_plural = _("System Tasks") | ||||
|         indexes = ExpiringModel.Meta.indexes | ||||
|  | ||||
| @ -1,13 +0,0 @@ | ||||
| """Event Settings""" | ||||
|  | ||||
| from celery.schedules import crontab | ||||
|  | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
|  | ||||
| CELERY_BEAT_SCHEDULE = { | ||||
|     "events_notification_cleanup": { | ||||
|         "task": "authentik.events.tasks.notification_cleanup", | ||||
|         "schedule": crontab(minute=fqdn_rand("notification_cleanup"), hour="*/8"), | ||||
|         "options": {"queue": "authentik_scheduled"}, | ||||
|     }, | ||||
| } | ||||
| @ -12,13 +12,11 @@ from rest_framework.request import Request | ||||
|  | ||||
| from authentik.core.models import AuthenticatedSession, User | ||||
| from authentik.core.signals import login_failed, password_changed | ||||
| from authentik.events.apps import SYSTEM_TASK_STATUS | ||||
| from authentik.events.models import Event, EventAction, NotificationRule, SystemTask | ||||
| from authentik.events.models import Event, EventAction, NotificationRule | ||||
| from authentik.events.tasks import event_trigger_handler, gdpr_cleanup | ||||
| from authentik.flows.models import Stage | ||||
| from authentik.flows.planner import PLAN_CONTEXT_OUTPOST, PLAN_CONTEXT_SOURCE, FlowPlan | ||||
| from authentik.flows.views.executor import SESSION_KEY_PLAN | ||||
| from authentik.root.monitoring import monitoring_set | ||||
| from authentik.stages.invitation.models import Invitation | ||||
| from authentik.stages.invitation.signals import invitation_used | ||||
| from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS | ||||
| @ -123,11 +121,3 @@ def event_user_pre_delete_cleanup(sender, instance: User, **_): | ||||
|     """If gdpr_compliance is enabled, remove all the user's events""" | ||||
|     if get_current_tenant().gdpr_compliance: | ||||
|         gdpr_cleanup.send(instance.pk) | ||||
|  | ||||
|  | ||||
| @receiver(monitoring_set) | ||||
| def monitoring_system_task(sender, **_): | ||||
|     """Update metrics when task is saved""" | ||||
|     SYSTEM_TASK_STATUS.clear() | ||||
|     for task in SystemTask.objects.all(): | ||||
|         task.update_metrics() | ||||
|  | ||||
| @ -1,133 +0,0 @@ | ||||
| """Monitored tasks""" | ||||
|  | ||||
| from datetime import datetime, timedelta | ||||
| from time import perf_counter | ||||
| from typing import Any | ||||
|  | ||||
| from django.utils.timezone import now | ||||
| from structlog.stdlib import BoundLogger, get_logger | ||||
| from tenant_schemas_celery.task import TenantTask | ||||
|  | ||||
| from authentik.events.logs import LogEvent | ||||
| from authentik.events.models import Event, EventAction, TaskStatus | ||||
| from authentik.events.models import SystemTask as DBSystemTask | ||||
| from authentik.events.utils import sanitize_item | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
|  | ||||
|  | ||||
| class SystemTask(TenantTask): | ||||
|     """Task which can save its state to the cache""" | ||||
|  | ||||
|     logger: BoundLogger | ||||
|  | ||||
|     # For tasks that should only be listed if they failed, set this to False | ||||
|     save_on_success: bool | ||||
|  | ||||
|     _status: TaskStatus | ||||
|     _messages: list[LogEvent] | ||||
|  | ||||
|     _uid: str | None | ||||
|     # Precise start time from perf_counter | ||||
|     _start_precise: float | None = None | ||||
|     _start: datetime | None = None | ||||
|  | ||||
|     def __init__(self, *args, **kwargs) -> None: | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self._status = TaskStatus.SUCCESSFUL | ||||
|         self.save_on_success = True | ||||
|         self._uid = None | ||||
|         self._status = None | ||||
|         self._messages = [] | ||||
|         self.result_timeout_hours = 6 | ||||
|  | ||||
|     def set_uid(self, uid: str): | ||||
|         """Set UID, so in the case of an unexpected error its saved correctly""" | ||||
|         self._uid = uid | ||||
|  | ||||
|     def set_status(self, status: TaskStatus, *messages: LogEvent): | ||||
|         """Set result for current run, will overwrite previous result.""" | ||||
|         self._status = status | ||||
|         self._messages = list(messages) | ||||
|         for idx, msg in enumerate(self._messages): | ||||
|             if not isinstance(msg, LogEvent): | ||||
|                 self._messages[idx] = LogEvent(msg, logger=self.__name__, log_level="info") | ||||
|  | ||||
|     def set_error(self, exception: Exception, *messages: LogEvent): | ||||
|         """Set result to error and save exception""" | ||||
|         self._status = TaskStatus.ERROR | ||||
|         self._messages = list(messages) | ||||
|         self._messages.extend( | ||||
|             [LogEvent(exception_to_string(exception), logger=self.__name__, log_level="error")] | ||||
|         ) | ||||
|  | ||||
|     def before_start(self, task_id, args, kwargs): | ||||
|         self._start_precise = perf_counter() | ||||
|         self._start = now() | ||||
|         self.logger = get_logger().bind(task_id=task_id) | ||||
|         return super().before_start(task_id, args, kwargs) | ||||
|  | ||||
|     def db(self) -> DBSystemTask | None: | ||||
|         """Get DB object for latest task""" | ||||
|         return DBSystemTask.objects.filter( | ||||
|             name=self.__name__, | ||||
|             uid=self._uid, | ||||
|         ).first() | ||||
|  | ||||
|     def after_return(self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo): | ||||
|         super().after_return(status, retval, task_id, args, kwargs, einfo=einfo) | ||||
|         if not self._status: | ||||
|             return | ||||
|         if self._status == TaskStatus.SUCCESSFUL and not self.save_on_success: | ||||
|             DBSystemTask.objects.filter( | ||||
|                 name=self.__name__, | ||||
|                 uid=self._uid, | ||||
|             ).delete() | ||||
|             return | ||||
|         DBSystemTask.objects.update_or_create( | ||||
|             name=self.__name__, | ||||
|             uid=self._uid, | ||||
|             defaults={ | ||||
|                 "description": self.__doc__, | ||||
|                 "start_timestamp": self._start or now(), | ||||
|                 "finish_timestamp": now(), | ||||
|                 "duration": max(perf_counter() - self._start_precise, 0), | ||||
|                 "task_call_module": self.__module__, | ||||
|                 "task_call_func": self.__name__, | ||||
|                 "task_call_args": sanitize_item(args), | ||||
|                 "task_call_kwargs": sanitize_item(kwargs), | ||||
|                 "status": self._status, | ||||
|                 "messages": sanitize_item(self._messages), | ||||
|                 "expires": now() + timedelta(hours=self.result_timeout_hours), | ||||
|                 "expiring": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def on_failure(self, exc, task_id, args, kwargs, einfo): | ||||
|         super().on_failure(exc, task_id, args, kwargs, einfo=einfo) | ||||
|         if not self._status: | ||||
|             self.set_error(exc) | ||||
|         DBSystemTask.objects.update_or_create( | ||||
|             name=self.__name__, | ||||
|             uid=self._uid, | ||||
|             defaults={ | ||||
|                 "description": self.__doc__, | ||||
|                 "start_timestamp": self._start or now(), | ||||
|                 "finish_timestamp": now(), | ||||
|                 "duration": max(perf_counter() - self._start_precise, 0), | ||||
|                 "task_call_module": self.__module__, | ||||
|                 "task_call_func": self.__name__, | ||||
|                 "task_call_args": sanitize_item(args), | ||||
|                 "task_call_kwargs": sanitize_item(kwargs), | ||||
|                 "status": self._status, | ||||
|                 "messages": sanitize_item(self._messages), | ||||
|                 "expires": now() + timedelta(hours=self.result_timeout_hours + 3), | ||||
|                 "expiring": True, | ||||
|             }, | ||||
|         ) | ||||
|         Event.new( | ||||
|             EventAction.SYSTEM_TASK_EXCEPTION, | ||||
|             message=f"Task {self.__name__} encountered an error: {exception_to_string(exc)}", | ||||
|         ).save() | ||||
|  | ||||
|     def run(self, *args, **kwargs): | ||||
|         raise NotImplementedError | ||||
| @ -1,104 +0,0 @@ | ||||
| """Test Monitored tasks""" | ||||
|  | ||||
| # from json import loads | ||||
|  | ||||
| from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
|  | ||||
| # from authentik.core.tasks import clean_expired_models | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.events.models import SystemTask as DBSystemTask | ||||
| from authentik.events.models import TaskStatus | ||||
| from authentik.events.system_tasks import SystemTask | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
|  | ||||
| class TestSystemTasks(APITestCase): | ||||
|     """Test Monitored tasks""" | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.user = create_test_admin_user() | ||||
|         self.client.force_login(self.user) | ||||
|  | ||||
|     def test_failed_successful_remove_state(self): | ||||
|         """Test that a task with `save_on_success` set to `False` that failed saves | ||||
|         a state, and upon successful completion will delete the state""" | ||||
|         should_fail = True | ||||
|         uid = generate_id() | ||||
|  | ||||
|         @CELERY_APP.task( | ||||
|             bind=True, | ||||
|             base=SystemTask, | ||||
|         ) | ||||
|         def test_task(self: SystemTask): | ||||
|             self.save_on_success = False | ||||
|             self.set_uid(uid) | ||||
|             self.set_status(TaskStatus.ERROR if should_fail else TaskStatus.SUCCESSFUL) | ||||
|  | ||||
|         # First test successful run | ||||
|         should_fail = False | ||||
|         test_task.delay().get() | ||||
|         self.assertIsNone(DBSystemTask.objects.filter(name="test_task", uid=uid).first()) | ||||
|  | ||||
|         # Then test failed | ||||
|         should_fail = True | ||||
|         test_task.delay().get() | ||||
|         task = DBSystemTask.objects.filter(name="test_task", uid=uid).first() | ||||
|         self.assertEqual(task.status, TaskStatus.ERROR) | ||||
|  | ||||
|         # Then after that, the state should be removed | ||||
|         should_fail = False | ||||
|         test_task.delay().get() | ||||
|         self.assertIsNone(DBSystemTask.objects.filter(name="test_task", uid=uid).first()) | ||||
|  | ||||
|     # | ||||
|     # def test_tasks(self): | ||||
|     #     """Test Task API""" | ||||
|     #     clean_expired_models.send() | ||||
|     #     response = self.client.get(reverse("authentik_api:systemtask-list")) | ||||
|     #     self.assertEqual(response.status_code, 200) | ||||
|     #     body = loads(response.content) | ||||
|     #     self.assertTrue(any(task["name"] == "clean_expired_models" for task in body["results"])) | ||||
|     # | ||||
|     # def test_tasks_single(self): | ||||
|     #     """Test Task API (read single)""" | ||||
|     #     clean_expired_models.delay().get() | ||||
|     #     task = DBSystemTask.objects.filter(name="clean_expired_models").first() | ||||
|     #     response = self.client.get( | ||||
|     #         reverse( | ||||
|     #             "authentik_api:systemtask-detail", | ||||
|     #             kwargs={"pk": str(task.pk)}, | ||||
|     #         ) | ||||
|     #     ) | ||||
|     #     self.assertEqual(response.status_code, 200) | ||||
|     #     body = loads(response.content) | ||||
|     #     self.assertEqual(body["status"], TaskStatus.SUCCESSFUL.value) | ||||
|     #     self.assertEqual(body["name"], "clean_expired_models") | ||||
|     #     response = self.client.get( | ||||
|     #         reverse("authentik_api:systemtask-detail", kwargs={"pk": "qwerqwer"}) | ||||
|     #     ) | ||||
|     #     self.assertEqual(response.status_code, 404) | ||||
|     # | ||||
|     # def test_tasks_run(self): | ||||
|     #     """Test Task API (run)""" | ||||
|     #     clean_expired_models.delay().get() | ||||
|     #     task = DBSystemTask.objects.filter(name="clean_expired_models").first() | ||||
|     #     response = self.client.post( | ||||
|     #         reverse( | ||||
|     #             "authentik_api:systemtask-run", | ||||
|     #             kwargs={"pk": str(task.pk)}, | ||||
|     #         ) | ||||
|     #     ) | ||||
|     #     self.assertEqual(response.status_code, 204) | ||||
|  | ||||
|     def test_tasks_run_404(self): | ||||
|         """Test Task API (run, 404)""" | ||||
|         response = self.client.post( | ||||
|             reverse( | ||||
|                 "authentik_api:systemtask-run", | ||||
|                 kwargs={"pk": "qwerqewrqrqewrqewr"}, | ||||
|             ) | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 404) | ||||
| @ -5,13 +5,11 @@ from authentik.events.api.notification_mappings import NotificationWebhookMappin | ||||
| from authentik.events.api.notification_rules import NotificationRuleViewSet | ||||
| from authentik.events.api.notification_transports import NotificationTransportViewSet | ||||
| from authentik.events.api.notifications import NotificationViewSet | ||||
| from authentik.events.api.tasks import SystemTaskViewSet | ||||
|  | ||||
| api_urlpatterns = [ | ||||
|     ("events/events", EventViewSet), | ||||
|     ("events/notifications", NotificationViewSet), | ||||
|     ("events/transports", NotificationTransportViewSet), | ||||
|     ("events/rules", NotificationRuleViewSet), | ||||
|     ("events/system_tasks", SystemTaskViewSet), | ||||
|     ("propertymappings/notification", NotificationWebhookMappingViewSet), | ||||
| ] | ||||
|  | ||||
| @ -88,7 +88,6 @@ def get_logger_config(): | ||||
|         "authentik": global_level, | ||||
|         "django": "WARNING", | ||||
|         "django.request": "ERROR", | ||||
|         "celery": "WARNING", | ||||
|         "selenium": "WARNING", | ||||
|         "docker": "WARNING", | ||||
|         "urllib3": "WARNING", | ||||
|  | ||||
| @ -3,8 +3,6 @@ | ||||
| from asyncio.exceptions import CancelledError | ||||
| from typing import Any | ||||
|  | ||||
| from billiard.exceptions import SoftTimeLimitExceeded, WorkerLostError | ||||
| from celery.exceptions import CeleryError | ||||
| from channels_redis.core import ChannelFull | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation, ValidationError | ||||
| @ -21,7 +19,6 @@ from sentry_sdk import HttpTransport, get_current_scope | ||||
| from sentry_sdk import init as sentry_sdk_init | ||||
| from sentry_sdk.api import set_tag | ||||
| from sentry_sdk.integrations.argv import ArgvIntegration | ||||
| from sentry_sdk.integrations.celery import CeleryIntegration | ||||
| from sentry_sdk.integrations.django import DjangoIntegration | ||||
| from sentry_sdk.integrations.redis import RedisIntegration | ||||
| from sentry_sdk.integrations.socket import SocketIntegration | ||||
| @ -71,7 +68,6 @@ def sentry_init(**sentry_init_kwargs): | ||||
|             ArgvIntegration(), | ||||
|             StdlibIntegration(), | ||||
|             DjangoIntegration(transaction_style="function_name", cache_spans=True), | ||||
|             CeleryIntegration(), | ||||
|             RedisIntegration(), | ||||
|             ThreadingIntegration(propagate_hub=True), | ||||
|             SocketIntegration(), | ||||
| @ -132,10 +128,6 @@ def before_send(event: dict, hint: dict) -> dict | None: | ||||
|         LocalProtocolError, | ||||
|         # rest_framework error | ||||
|         APIException, | ||||
|         # celery errors | ||||
|         WorkerLostError, | ||||
|         CeleryError, | ||||
|         SoftTimeLimitExceeded, | ||||
|         # custom baseclass | ||||
|         SentryIgnoredException, | ||||
|         # ldap errors | ||||
| @ -161,8 +153,6 @@ def before_send(event: dict, hint: dict) -> dict | None: | ||||
|             "django_redis", | ||||
|             "django.security.DisallowedHost", | ||||
|             "django_redis.cache", | ||||
|             "celery.backends.redis", | ||||
|             "celery.worker", | ||||
|             "paramiko.transport", | ||||
|         ]: | ||||
|             return None | ||||
|  | ||||
| @ -9,7 +9,6 @@ from rest_framework.response import Response | ||||
|  | ||||
| from authentik.core.api.utils import ModelSerializer, PassiveSerializer | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.events.api.tasks import SystemTaskSerializer | ||||
| from authentik.events.logs import LogEvent, LogEventSerializer | ||||
| from authentik.lib.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.lib.utils.reflection import class_to_path | ||||
| @ -20,7 +19,6 @@ class SyncStatusSerializer(PassiveSerializer): | ||||
|     """Provider sync status""" | ||||
|  | ||||
|     is_running = BooleanField(read_only=True) | ||||
|     tasks = SystemTaskSerializer(many=True, read_only=True) | ||||
|  | ||||
|  | ||||
| class SyncObjectSerializer(PassiveSerializer): | ||||
| @ -48,12 +46,7 @@ class OutgoingSyncProviderStatusMixin: | ||||
|     sync_task: type[Actor] = None | ||||
|     sync_objects_task: type[Actor] = None | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: SyncStatusSerializer(), | ||||
|             404: OpenApiResponse(description="Task not found"), | ||||
|         } | ||||
|     ) | ||||
|     @extend_schema(responses={200: SyncStatusSerializer()}) | ||||
|     @action( | ||||
|         methods=["GET"], | ||||
|         detail=True, | ||||
| @ -64,16 +57,8 @@ class OutgoingSyncProviderStatusMixin: | ||||
|     def sync_status(self, request: Request, pk: int) -> Response: | ||||
|         """Get provider's sync status""" | ||||
|         provider: OutgoingSyncProvider = self.get_object() | ||||
|         # TODO: fixme | ||||
|         tasks = list( | ||||
|             get_objects_for_user(request.user, "authentik_events.view_systemtask").filter( | ||||
|                 name=self.sync_task.__name__, | ||||
|                 uid=slugify(provider.name), | ||||
|             ) | ||||
|         ) | ||||
|         with provider.sync_lock as lock_acquired: | ||||
|             status = { | ||||
|                 "tasks": tasks, | ||||
|                 # If we could not acquire the lock, it means a task is using it, and thus is running | ||||
|                 "is_running": not lock_acquired, | ||||
|             } | ||||
|  | ||||
| @ -3,13 +3,11 @@ | ||||
| from json import loads | ||||
|  | ||||
| from django.test import TestCase | ||||
| from django.utils.text import slugify | ||||
| from jsonschema import validate | ||||
| from requests_mock import Mocker | ||||
|  | ||||
| from authentik.blueprints.tests import apply_blueprint | ||||
| from authentik.core.models import Application, Group, User | ||||
| from authentik.events.models import SystemTask | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.sync.outgoing.base import SAFE_METHODS | ||||
| from authentik.providers.scim.models import SCIMMapping, SCIMProvider | ||||
| @ -433,9 +431,10 @@ class SCIMUserTests(TestCase): | ||||
|             self.assertEqual(mock.call_count, 3) | ||||
|             for request in mock.request_history: | ||||
|                 self.assertIn(request.method, SAFE_METHODS) | ||||
|         task = SystemTask.objects.filter(uid=slugify(self.provider.name)).first() | ||||
|         self.assertIsNotNone(task) | ||||
|         drop_msg = task.messages[3] | ||||
|         drop_msg = {} | ||||
|         # task = Task.objects.filter(uid=slugify(self.provider.name)).first() | ||||
|         # self.assertIsNotNone(task) | ||||
|         # drop_msg = task.messages[3] | ||||
|         self.assertEqual(drop_msg["event"], "Dropping mutating request due to dry run") | ||||
|         self.assertIsNotNone(drop_msg["attributes"]["url"]) | ||||
|         self.assertIsNotNone(drop_msg["attributes"]["body"]) | ||||
|  | ||||
| @ -343,25 +343,6 @@ USE_TZ = True | ||||
|  | ||||
| LOCALE_PATHS = ["./locale"] | ||||
|  | ||||
| CELERY = { | ||||
|     "task_soft_time_limit": 600, | ||||
|     "worker_max_tasks_per_child": 50, | ||||
|     "worker_concurrency": CONFIG.get_int("worker.concurrency"), | ||||
|     "beat_schedule": {}, | ||||
|     "beat_scheduler": "authentik.tenants.scheduler:TenantAwarePersistentScheduler", | ||||
|     "task_create_missing_queues": True, | ||||
|     "task_default_queue": "authentik", | ||||
|     "broker_url": CONFIG.get("broker.url") or redis_url(CONFIG.get("redis.db")), | ||||
|     "result_backend": CONFIG.get("result_backend.url") or redis_url(CONFIG.get("redis.db")), | ||||
|     "broker_transport_options": CONFIG.get_dict_from_b64_json( | ||||
|         "broker.transport_options", {"retry_policy": {"timeout": 5.0}} | ||||
|     ), | ||||
|     "result_backend_transport_options": CONFIG.get_dict_from_b64_json( | ||||
|         "result_backend.transport_options", {"retry_policy": {"timeout": 5.0}} | ||||
|     ), | ||||
|     "redis_retry_on_timeout": True, | ||||
| } | ||||
|  | ||||
| # Sentry integration | ||||
| env = get_env() | ||||
| _ERROR_REPORTING = CONFIG.get_bool("error_reporting.enabled", False) | ||||
| @ -436,7 +417,6 @@ _DISALLOWED_ITEMS = [ | ||||
|     "INSTALLED_APPS", | ||||
|     "MIDDLEWARE", | ||||
|     "AUTHENTICATION_BACKENDS", | ||||
|     "CELERY", | ||||
| ] | ||||
|  | ||||
| SILENCED_SYSTEM_CHECKS = [ | ||||
| @ -459,7 +439,6 @@ def _update_settings(app_path: str): | ||||
|         TENANT_APPS.extend(getattr(settings_module, "TENANT_APPS", [])) | ||||
|         MIDDLEWARE.extend(getattr(settings_module, "MIDDLEWARE", [])) | ||||
|         AUTHENTICATION_BACKENDS.extend(getattr(settings_module, "AUTHENTICATION_BACKENDS", [])) | ||||
|         CELERY["beat_schedule"].update(getattr(settings_module, "CELERY_BEAT_SCHEDULE", {})) | ||||
|         for _attr in dir(settings_module): | ||||
|             if not _attr.startswith("__") and _attr not in _DISALLOWED_ITEMS: | ||||
|                 globals()[_attr] = getattr(settings_module, _attr) | ||||
| @ -468,7 +447,6 @@ def _update_settings(app_path: str): | ||||
|  | ||||
|  | ||||
| if DEBUG: | ||||
|     CELERY["task_always_eager"] = True | ||||
|     REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].append( | ||||
|         "rest_framework.renderers.BrowsableAPIRenderer" | ||||
|     ) | ||||
|  | ||||
| @ -36,7 +36,6 @@ class PytestTestRunner(DiscoverRunner):  # pragma: no cover | ||||
|             self.args.append("--capture=no") | ||||
|  | ||||
|         settings.TEST = True | ||||
|         settings.CELERY["task_always_eager"] = True | ||||
|         CONFIG.set("events.context_processors.geoip", "tests/GeoLite2-City-Test.mmdb") | ||||
|         CONFIG.set("events.context_processors.asn", "tests/GeoLite2-ASN-Test.mmdb") | ||||
|         CONFIG.set("blueprints_dir", "./blueprints") | ||||
|  | ||||
| @ -12,7 +12,6 @@ from rest_framework.viewsets import ModelViewSet | ||||
| from authentik.core.api.sources import SourceSerializer | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.events.api.tasks import SystemTaskSerializer | ||||
| from authentik.sources.kerberos.models import KerberosSource | ||||
| from authentik.sources.kerberos.tasks import CACHE_KEY_STATUS | ||||
|  | ||||
| @ -56,7 +55,6 @@ class KerberosSyncStatusSerializer(PassiveSerializer): | ||||
|     """Kerberos Source sync status""" | ||||
|  | ||||
|     is_running = BooleanField(read_only=True) | ||||
|     tasks = SystemTaskSerializer(many=True, read_only=True) | ||||
|  | ||||
|  | ||||
| class KerberosSourceViewSet(UsedByMixin, ModelViewSet): | ||||
| @ -88,11 +86,7 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet): | ||||
|     ] | ||||
|     ordering = ["name"] | ||||
|  | ||||
|     @extend_schema( | ||||
|         responses={ | ||||
|             200: KerberosSyncStatusSerializer(), | ||||
|         } | ||||
|     ) | ||||
|     @extend_schema(responses={200: KerberosSyncStatusSerializer()}) | ||||
|     @action( | ||||
|         methods=["GET"], | ||||
|         detail=True, | ||||
| @ -103,15 +97,8 @@ class KerberosSourceViewSet(UsedByMixin, ModelViewSet): | ||||
|     def sync_status(self, request: Request, slug: str) -> Response: | ||||
|         """Get source's sync status""" | ||||
|         source: KerberosSource = self.get_object() | ||||
|         tasks = list( | ||||
|             get_objects_for_user(request.user, "authentik_events.view_systemtask").filter( | ||||
|                 name="kerberos_sync", | ||||
|                 uid__startswith=source.slug, | ||||
|             ) | ||||
|         ) | ||||
|         with source.sync_lock as lock_acquired: | ||||
|             status = { | ||||
|                 "tasks": tasks, | ||||
|                 "is_running": not lock_acquired, | ||||
|             } | ||||
|         return Response(KerberosSyncStatusSerializer(status).data) | ||||
|  | ||||
| @ -8,8 +8,7 @@ from django.test import TestCase | ||||
| from authentik.blueprints.tests import apply_blueprint | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.events.models import Event, EventAction, SystemTask | ||||
| from authentik.events.system_tasks import TaskStatus | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.generators import generate_id, generate_key | ||||
| from authentik.lib.sync.outgoing.exceptions import StopSync | ||||
| from authentik.lib.utils.reflection import class_to_path | ||||
| @ -56,8 +55,6 @@ class LDAPSyncTests(TestCase): | ||||
|         connection = MagicMock(return_value=mock_ad_connection(LDAP_PASSWORD)) | ||||
|         with patch("authentik.sources.ldap.models.LDAPSource.connection", connection): | ||||
|             ldap_sync_page.send(self.source.pk, class_to_path(UserLDAPSynchronizer), "foo") | ||||
|         task = SystemTask.objects.filter(name="ldap_sync", uid="ldap:users:foo").first() | ||||
|         self.assertEqual(task.status, TaskStatus.ERROR) | ||||
|  | ||||
|     def test_sync_error(self): | ||||
|         """Test user sync""" | ||||
|  | ||||
| @ -1,22 +0,0 @@ | ||||
| """Tenant-aware Celery beat scheduler""" | ||||
|  | ||||
| from tenant_schemas_celery.scheduler import ( | ||||
|     TenantAwarePersistentScheduler as BaseTenantAwarePersistentScheduler, | ||||
| ) | ||||
| from tenant_schemas_celery.scheduler import TenantAwareScheduleEntry | ||||
|  | ||||
|  | ||||
| class TenantAwarePersistentScheduler(BaseTenantAwarePersistentScheduler): | ||||
|     """Tenant-aware Celery beat scheduler""" | ||||
|  | ||||
|     @classmethod | ||||
|     def get_queryset(cls): | ||||
|         return super().get_queryset().filter(ready=True) | ||||
|  | ||||
|     def apply_entry(self, entry: TenantAwareScheduleEntry, producer=None): | ||||
|         # https://github.com/maciej-gol/tenant-schemas-celery/blob/master/tenant_schemas_celery/scheduler.py#L85 | ||||
|         # When (as by default) no tenant schemas are set, the public schema is excluded | ||||
|         # so we need to explicitly include it here, otherwise the task is not executed | ||||
|         if entry.tenant_schemas is None: | ||||
|             entry.tenant_schemas = self.get_queryset().values_list("schema_name", flat=True) | ||||
|         return super().apply_entry(entry, producer) | ||||
| @ -79,10 +79,6 @@ elif [[ "$1" == "worker" ]]; then | ||||
|     set_mode "worker" | ||||
|     shift | ||||
|     check_if_root "python -m manage worker $@" | ||||
| elif [[ "$1" == "worker-status" ]]; then | ||||
|     wait_for_db | ||||
|     celery -A authentik.root.celery flower \ | ||||
|         --port=9000 | ||||
| elif [[ "$1" == "bash" ]]; then | ||||
|     /bin/bash | ||||
| elif [[ "$1" == "test-all" ]]; then | ||||
|  | ||||
| @ -6,7 +6,6 @@ authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }] | ||||
| requires-python = "==3.13.*" | ||||
| dependencies = [ | ||||
|     "argon2-cffi==25.1.0", | ||||
|     "celery==5.5.3", | ||||
|     "channels==4.2.2", | ||||
|     "channels-redis==4.2.1", | ||||
|     "cron-converter==1.2.1", | ||||
| @ -35,7 +34,6 @@ dependencies = [ | ||||
|     "dumb-init==1.2.5.post1", | ||||
|     "duo-client==5.5.0", | ||||
|     "fido2==2.0.0", | ||||
|     "flower==2.0.1", | ||||
|     "geoip2==5.1.0", | ||||
|     "geopy==2.4.1", | ||||
|     "google-api-python-client==2.171.0", | ||||
| @ -65,7 +63,6 @@ dependencies = [ | ||||
|     "structlog==25.4.0", | ||||
|     "swagger-spec-validator==3.0.4", | ||||
|     "tenacity==9.1.2", | ||||
|     "tenant-schemas-celery==3.0.0", | ||||
|     "twilio==9.6.2", | ||||
|     "ua-parser==1.0.1", | ||||
|     "unidecode==1.4.0", | ||||
| @ -230,7 +227,7 @@ show_missing = true | ||||
| DJANGO_SETTINGS_MODULE = "authentik.root.settings" | ||||
| python_files = ["tests.py", "test_*.py", "*_tests.py"] | ||||
| junit_family = "xunit2" | ||||
| addopts = "-p no:celery -p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib" | ||||
| addopts = "-p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib" | ||||
| filterwarnings = [ | ||||
|     "ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning", | ||||
|     "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning", | ||||
|  | ||||
							
								
								
									
										191
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										191
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							| @ -69,18 +69,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "amqp" | ||||
| version = "5.3.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "vine" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "annotated-types" | ||||
| version = "0.7.0" | ||||
| @ -169,7 +157,6 @@ version = "2025.6.0" | ||||
| source = { editable = "." } | ||||
| dependencies = [ | ||||
|     { name = "argon2-cffi" }, | ||||
|     { name = "celery" }, | ||||
|     { name = "channels" }, | ||||
|     { name = "channels-redis" }, | ||||
|     { name = "cron-converter" }, | ||||
| @ -198,7 +185,6 @@ dependencies = [ | ||||
|     { name = "dumb-init" }, | ||||
|     { name = "duo-client" }, | ||||
|     { name = "fido2" }, | ||||
|     { name = "flower" }, | ||||
|     { name = "geoip2" }, | ||||
|     { name = "geopy" }, | ||||
|     { name = "google-api-python-client" }, | ||||
| @ -228,7 +214,6 @@ dependencies = [ | ||||
|     { name = "structlog" }, | ||||
|     { name = "swagger-spec-validator" }, | ||||
|     { name = "tenacity" }, | ||||
|     { name = "tenant-schemas-celery" }, | ||||
|     { name = "twilio" }, | ||||
|     { name = "ua-parser" }, | ||||
|     { name = "unidecode" }, | ||||
| @ -271,7 +256,6 @@ dev = [ | ||||
| [package.metadata] | ||||
| requires-dist = [ | ||||
|     { name = "argon2-cffi", specifier = "==25.1.0" }, | ||||
|     { name = "celery", specifier = "==5.5.3" }, | ||||
|     { name = "channels", specifier = "==4.2.2" }, | ||||
|     { name = "channels-redis", specifier = "==4.2.1" }, | ||||
|     { name = "cron-converter", specifier = "==1.2.1" }, | ||||
| @ -300,7 +284,6 @@ requires-dist = [ | ||||
|     { name = "dumb-init", specifier = "==1.2.5.post1" }, | ||||
|     { name = "duo-client", specifier = "==5.5.0" }, | ||||
|     { name = "fido2", specifier = "==2.0.0" }, | ||||
|     { name = "flower", specifier = "==2.0.1" }, | ||||
|     { name = "geoip2", specifier = "==5.1.0" }, | ||||
|     { name = "geopy", specifier = "==2.4.1" }, | ||||
|     { name = "google-api-python-client", specifier = "==2.171.0" }, | ||||
| @ -330,7 +313,6 @@ requires-dist = [ | ||||
|     { name = "structlog", specifier = "==25.4.0" }, | ||||
|     { name = "swagger-spec-validator", specifier = "==3.0.4" }, | ||||
|     { name = "tenacity", specifier = "==9.1.2" }, | ||||
|     { name = "tenant-schemas-celery", specifier = "==3.0.0" }, | ||||
|     { name = "twilio", specifier = "==9.6.2" }, | ||||
|     { name = "ua-parser", specifier = "==1.0.1" }, | ||||
|     { name = "unidecode", specifier = "==1.4.0" }, | ||||
| @ -549,15 +531,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "billiard" | ||||
| version = "4.2.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031, upload-time = "2024-09-21T13:40:22.491Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766, upload-time = "2024-09-21T13:40:20.188Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "black" | ||||
| version = "25.1.0" | ||||
| @ -652,25 +625,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/9b/ef/1c4698cac96d792005ef0611832f38eaee477c275ab4b02cbfc4daba7ad3/cbor2-5.6.5-py3-none-any.whl", hash = "sha256:3038523b8fc7de312bb9cdcbbbd599987e64307c4db357cd2030c472a6c7d468", size = 23752, upload-time = "2024-10-09T12:26:23.167Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "celery" | ||||
| version = "5.5.3" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "billiard" }, | ||||
|     { name = "click" }, | ||||
|     { name = "click-didyoumean" }, | ||||
|     { name = "click-plugins" }, | ||||
|     { name = "click-repl" }, | ||||
|     { name = "kombu" }, | ||||
|     { name = "python-dateutil" }, | ||||
|     { name = "vine" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "certifi" | ||||
| version = "2025.4.26" | ||||
| @ -769,43 +723,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "click-didyoumean" | ||||
| version = "0.3.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "click" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "click-plugins" | ||||
| version = "1.1.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "click" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164, upload-time = "2019-04-04T04:27:04.82Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497, upload-time = "2019-04-04T04:27:03.36Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "click-repl" | ||||
| version = "0.3.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "click" }, | ||||
|     { name = "prompt-toolkit" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "codespell" | ||||
| version = "2.4.1" | ||||
| @ -1312,22 +1229,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/4c/7d/a1dba174d7ec4b6b8d6360eed0ac3a4a4e2aa45f234e903592d3184c6c3f/fido2-2.0.0-py3-none-any.whl", hash = "sha256:685f54a50a57e019c6156e2dd699802a603e3abf70bab334f26affdd4fb8d4f7", size = 224761, upload-time = "2025-05-20T09:44:59.029Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "flower" | ||||
| version = "2.0.1" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "celery" }, | ||||
|     { name = "humanize" }, | ||||
|     { name = "prometheus-client" }, | ||||
|     { name = "pytz" }, | ||||
|     { name = "tornado" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/09/a1/357f1b5d8946deafdcfdd604f51baae9de10aafa2908d0b7322597155f92/flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", size = 3220408, upload-time = "2023-08-13T14:37:46.073Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/a6/ff/ee2f67c0ff146ec98b5df1df637b2bc2d17beeb05df9f427a67bd7a7d79c/flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2", size = 383553, upload-time = "2023-08-13T14:37:41.552Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "freezegun" | ||||
| version = "1.5.1" | ||||
| @ -1650,15 +1551,6 @@ http2 = [ | ||||
|     { name = "h2" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "humanize" | ||||
| version = "4.12.3" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/22/d1/bbc4d251187a43f69844f7fd8941426549bbe4723e8ff0a7441796b0789f/humanize-4.12.3.tar.gz", hash = "sha256:8430be3a615106fdfceb0b2c1b41c4c98c6b0fc5cc59663a5539b111dd325fb0", size = 80514, upload-time = "2025-04-30T11:51:07.98Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/a0/1e/62a2ec3104394a2975a2629eec89276ede9dbe717092f6966fcf963e1bf0/humanize-4.12.3-py3-none-any.whl", hash = "sha256:2cbf6370af06568fa6d2da77c86edb7886f3160ecd19ee1ffef07979efc597f6", size = 128487, upload-time = "2025-04-30T11:51:06.468Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "hyperframe" | ||||
| version = "6.1.0" | ||||
| @ -1849,20 +1741,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/b5/ba/d86d465c589cbed8f872e187e90abbac73eb1453483477771e87e7ee8376/k5test-0.10.4-py2.py3-none-any.whl", hash = "sha256:33de7ff10bf99155fe8ee5d5976798ad1db6237214306dadf5a0ae9d6bb0ad03", size = 11954, upload-time = "2024-03-20T02:48:24.502Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kombu" | ||||
| version = "5.5.3" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "amqp" }, | ||||
|     { name = "tzdata" }, | ||||
|     { name = "vine" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/60/0a/128b65651ed8120460fc5af754241ad595eac74993115ec0de4f2d7bc459/kombu-5.5.3.tar.gz", hash = "sha256:021a0e11fcfcd9b0260ef1fb64088c0e92beb976eb59c1dfca7ddd4ad4562ea2", size = 461784, upload-time = "2025-04-16T12:46:17.014Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/5d/35/1407fb0b2f5b07b50cbaf97fce09ad87d3bfefbf64f7171a8651cd8d2f68/kombu-5.5.3-py3-none-any.whl", hash = "sha256:5b0dbceb4edee50aa464f59469d34b97864be09111338cfb224a10b6a163909b", size = 209921, upload-time = "2025-04-16T12:46:15.139Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kubernetes" | ||||
| version = "32.0.1" | ||||
| @ -2398,18 +2276,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/32/ae/ec06af4fe3ee72d16973474f122541746196aaa16cea6f66d18b963c6177/prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094", size = 58694, upload-time = "2025-06-02T14:29:00.068Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "prompt-toolkit" | ||||
| version = "3.0.51" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "wcwidth" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "propcache" | ||||
| version = "0.3.1" | ||||
| @ -2798,15 +2664,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/20/44/70f24d3cac3f5d13b2fbe24542a732e3f529e564615bb3656665eb7b78e4/python_kadmin_rs-0.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80e3e0093dffe98f759b74ef25f6cdd8ab7b109f2af0f158572705ad31bb8699", size = 1617374, upload-time = "2025-04-02T11:34:07.595Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pytz" | ||||
| version = "2025.2" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "pywin32" | ||||
| version = "310" | ||||
| @ -3190,36 +3047,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tenant-schemas-celery" | ||||
| version = "3.0.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| dependencies = [ | ||||
|     { name = "celery" }, | ||||
| ] | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/d0/fe/cfe19eb7cc3ad8e39d7df7b7c44414bf665b6ac6660c998eb498f89d16c6/tenant_schemas_celery-3.0.0.tar.gz", hash = "sha256:6be3ae1a5826f262f0f3dd343c6a85a34a1c59b89e04ae37de018f36562fed55", size = 15954, upload-time = "2024-05-19T11:16:41.837Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/db/2c/376e1e641ad08b374c75d896468a7be2e6906ce3621fd0c9f9dc09ff1963/tenant_schemas_celery-3.0.0-py3-none-any.whl", hash = "sha256:ca0f69e78ef698eb4813468231df5a0ab6a660c08e657b65f5ac92e16887eec8", size = 18108, upload-time = "2024-05-19T11:16:39.92Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tornado" | ||||
| version = "6.4.2" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135, upload-time = "2024-11-22T03:06:38.036Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299, upload-time = "2024-11-22T03:06:20.162Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253, upload-time = "2024-11-22T03:06:22.39Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602, upload-time = "2024-11-22T03:06:24.214Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972, upload-time = "2024-11-22T03:06:25.559Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173, upload-time = "2024-11-22T03:06:27.584Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892, upload-time = "2024-11-22T03:06:28.933Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334, upload-time = "2024-11-22T03:06:30.428Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261, upload-time = "2024-11-22T03:06:32.458Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463, upload-time = "2024-11-22T03:06:34.71Z" }, | ||||
|     { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907, upload-time = "2024-11-22T03:06:36.71Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "trio" | ||||
| version = "0.30.0" | ||||
| @ -3429,15 +3256,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "vine" | ||||
| version = "5.1.0" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "watchdog" | ||||
| version = "6.0.0" | ||||
| @ -3495,15 +3313,6 @@ wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload-time = "2025-04-08T10:35:52.458Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "wcwidth" | ||||
| version = "0.2.13" | ||||
| source = { registry = "https://pypi.org/simple" } | ||||
| sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } | ||||
| wheels = [ | ||||
|     { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "webauthn" | ||||
| version = "2.5.2" | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Marc 'risson' Schmitt
					Marc 'risson' Schmitt