mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
packages/django-postgres-cache: Initial implementation of postgres cache (#16653)
* start db cache Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update codeowners Signed-off-by: Jens Langhammer <jens@goauthentik.io> * handle db error in keys Signed-off-by: Jens Langhammer <jens@goauthentik.io> * implement rest of the methods Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix unrelated warning on startup for cache Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix migrations? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add readme Signed-off-by: Jens Langhammer <jens@goauthentik.io> * dynamic dependency...? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * types Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rip out django_redis Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix get default Signed-off-by: Jens Langhammer <jens@goauthentik.io> * some cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> * simplify to use ORM Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove old migrations that use cache instead of doing dynamic things Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix migration Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Update packages/django-postgres-cache/django_postgres_cache/models.py Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * Update packages/django-postgres-cache/django_postgres_cache/migrations/0001_initial.py Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * fix redis imports Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * more redis removal Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> * lint Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space> Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
@@ -24,6 +24,7 @@ Makefile @goauthentik/infrastructure
|
||||
.editorconfig @goauthentik/infrastructure
|
||||
CODEOWNERS @goauthentik/infrastructure
|
||||
# Backend packages
|
||||
packages/django-postgres-cache @goauthentik/backend
|
||||
packages/django-dramatiq-postgres @goauthentik/backend
|
||||
# Web packages
|
||||
packages/docusaurus-config @goauthentik/frontend
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from django.dispatch import receiver
|
||||
|
||||
from authentik.admin.tasks import _set_prom_info
|
||||
from authentik.root.signals import post_startup
|
||||
|
||||
|
||||
@receiver(post_startup)
|
||||
def post_startup_admin_metrics(sender, **_):
|
||||
_set_prom_info()
|
||||
@@ -71,6 +71,3 @@ def update_latest_version():
|
||||
except (RequestException, IndexError) as exc:
|
||||
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT)
|
||||
raise exc
|
||||
|
||||
|
||||
_set_prom_info()
|
||||
|
||||
@@ -13,14 +13,6 @@ import authentik.core.models
|
||||
import authentik.lib.models
|
||||
|
||||
|
||||
def migrate_sessions(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
|
||||
session_keys = cache.keys(KEY_PREFIX + "*")
|
||||
cache.delete_many(session_keys)
|
||||
|
||||
|
||||
def fix_duplicates(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
Token = apps.get_model("authentik_core", "token")
|
||||
@@ -151,9 +143,6 @@ class Migration(migrations.Migration):
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_sessions,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="meta_launch_url",
|
||||
|
||||
@@ -7,15 +7,10 @@ from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_K
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.utils.timezone import now, timedelta
|
||||
from authentik.lib.migrations import progress_bar
|
||||
from authentik.root.middleware import ClientIPMiddleware
|
||||
|
||||
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
|
||||
|
||||
class PickleSerializer:
|
||||
"""
|
||||
Simple wrapper around pickle to be used in signing.dumps()/loads() and
|
||||
@@ -83,27 +78,6 @@ def _migrate_session(
|
||||
)
|
||||
|
||||
|
||||
def migrate_redis_sessions(apps, schema_editor):
|
||||
from django.core.cache import caches
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
cache = caches[SESSION_CACHE_ALIAS]
|
||||
|
||||
# Not a redis cache, skipping
|
||||
if not hasattr(cache, "keys"):
|
||||
return
|
||||
|
||||
print("\nMigrating Redis sessions to database, this might take a couple of minutes...")
|
||||
for key, session_data in progress_bar(cache.get_many(cache.keys(f"{KEY_PREFIX}*")).items()):
|
||||
_migrate_session(
|
||||
apps=apps,
|
||||
db_alias=db_alias,
|
||||
session_key=key.removeprefix(KEY_PREFIX),
|
||||
session_data=session_data,
|
||||
expires=now() + timedelta(seconds=cache.ttl(key)),
|
||||
)
|
||||
|
||||
|
||||
def migrate_database_sessions(apps, schema_editor):
|
||||
DjangoSession = apps.get_model("sessions", "Session")
|
||||
db_alias = schema_editor.connection.alias
|
||||
@@ -231,10 +205,6 @@ class Migration(migrations.Migration):
|
||||
"verbose_name_plural": "Authenticated Sessions",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_redis_sessions,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_database_sessions,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
|
||||
@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_postgres_cache.tasks import clear_expired_cache
|
||||
from dramatiq.actor import actor
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
@@ -32,6 +33,7 @@ def clean_expired_models():
|
||||
obj.expire_action()
|
||||
LOGGER.debug("Expired models", model=cls, amount=amount)
|
||||
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
|
||||
clear_expired_cache()
|
||||
|
||||
|
||||
@actor(description=_("Remove temporary users created by SAML Sources."))
|
||||
|
||||
@@ -7,14 +7,11 @@ from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation, ValidationError
|
||||
from django.db import DatabaseError, InternalError, OperationalError, ProgrammingError
|
||||
from django.http.response import Http404
|
||||
from django_redis.exceptions import ConnectionInterrupted
|
||||
from docker.errors import DockerException
|
||||
from dramatiq.errors import Retry
|
||||
from h11 import LocalProtocolError
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from psycopg.errors import Error
|
||||
from redis.exceptions import ConnectionError as RedisConnectionError
|
||||
from redis.exceptions import RedisError, ResponseError
|
||||
from rest_framework.exceptions import APIException
|
||||
from sentry_sdk import HttpTransport, get_current_scope
|
||||
from sentry_sdk import init as sentry_sdk_init
|
||||
@@ -22,7 +19,6 @@ from sentry_sdk.api import set_tag
|
||||
from sentry_sdk.integrations.argv import ArgvIntegration
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.dramatiq import DramatiqIntegration
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
from sentry_sdk.integrations.socket import SocketIntegration
|
||||
from sentry_sdk.integrations.stdlib import StdlibIntegration
|
||||
from sentry_sdk.integrations.threading import ThreadingIntegration
|
||||
@@ -58,11 +54,6 @@ ignored_classes = (
|
||||
ProgrammingError,
|
||||
SuspiciousOperation,
|
||||
ValidationError,
|
||||
# Redis errors
|
||||
RedisConnectionError,
|
||||
ConnectionInterrupted,
|
||||
RedisError,
|
||||
ResponseError,
|
||||
# websocket errors
|
||||
WebSocketException,
|
||||
LocalProtocolError,
|
||||
@@ -110,7 +101,6 @@ def sentry_init(**sentry_init_kwargs):
|
||||
ArgvIntegration(),
|
||||
DjangoIntegration(transaction_style="function_name", cache_spans=True),
|
||||
DramatiqIntegration(),
|
||||
RedisIntegration(),
|
||||
SocketIntegration(),
|
||||
StdlibIntegration(),
|
||||
ThreadingIntegration(propagate_hub=True),
|
||||
@@ -157,9 +147,7 @@ def before_send(event: dict, hint: dict) -> dict | None:
|
||||
if event["logger"] in [
|
||||
"asyncio",
|
||||
"multiprocessing",
|
||||
"django_redis",
|
||||
"django.security.DisallowedHost",
|
||||
"django_redis.cache",
|
||||
"paramiko.transport",
|
||||
]:
|
||||
return None
|
||||
|
||||
@@ -11,8 +11,6 @@ from django.dispatch import Signal
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.views import View
|
||||
from django_prometheus.exports import ExportToDjangoView
|
||||
from django_redis import get_redis_connection
|
||||
from redis.exceptions import RedisError
|
||||
|
||||
monitoring_set = Signal()
|
||||
|
||||
@@ -44,19 +42,17 @@ class LiveView(View):
|
||||
|
||||
|
||||
class ReadyView(View):
|
||||
"""View for readiness probe, always returns Http 200, unless sql or redis is down"""
|
||||
"""View for readiness probe, always returns Http 200, unless sql is down"""
|
||||
|
||||
def check_db(self):
|
||||
for db_conn in connections.all():
|
||||
# Force connection reload
|
||||
db_conn.connect()
|
||||
_ = db_conn.cursor()
|
||||
|
||||
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
||||
try:
|
||||
for db_conn in connections.all():
|
||||
# Force connection reload
|
||||
db_conn.connect()
|
||||
_ = db_conn.cursor()
|
||||
self.check_db()
|
||||
except OperationalError: # pragma: no cover
|
||||
return HttpResponse(status=503)
|
||||
try:
|
||||
redis_conn = get_redis_connection()
|
||||
redis_conn.ping()
|
||||
except RedisError: # pragma: no cover
|
||||
return HttpResponse(status=503)
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@@ -10,7 +10,7 @@ from sentry_sdk import set_tag
|
||||
from xmlsec import enable_debug_trace
|
||||
|
||||
from authentik import authentik_version
|
||||
from authentik.lib.config import CONFIG, django_db_config, redis_url
|
||||
from authentik.lib.config import CONFIG, django_db_config
|
||||
from authentik.lib.logging import get_logger_config, structlog_configure
|
||||
from authentik.lib.sentry import sentry_init
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
@@ -73,6 +73,7 @@ TENANT_APPS = [
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"pgtrigger",
|
||||
"django_postgres_cache",
|
||||
"authentik.admin",
|
||||
"authentik.api",
|
||||
"authentik.core",
|
||||
@@ -227,20 +228,11 @@ REST_FRAMEWORK = {
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db")),
|
||||
"TIMEOUT": CONFIG.get_int("cache.timeout", 300),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
"KEY_PREFIX": "authentik_cache",
|
||||
"BACKEND": "django_postgres_cache.backend.DatabaseCache",
|
||||
"KEY_FUNCTION": "django_tenants.cache.make_key",
|
||||
"REVERSE_KEY_FUNCTION": "django_tenants.cache.reverse_key",
|
||||
}
|
||||
}
|
||||
DJANGO_REDIS_SCAN_ITERSIZE = 1000
|
||||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
||||
SESSION_ENGINE = "authentik.core.sessions"
|
||||
# Configured via custom SessionMiddleware
|
||||
# SESSION_COOKIE_SAMESITE = "None"
|
||||
|
||||
@@ -13,12 +13,10 @@ from django_dramatiq_postgres.middleware import HTTPServer
|
||||
from django_dramatiq_postgres.middleware import (
|
||||
MetricsMiddleware as BaseMetricsMiddleware,
|
||||
)
|
||||
from django_redis import get_redis_connection
|
||||
from dramatiq.broker import Broker
|
||||
from dramatiq.message import Message
|
||||
from dramatiq.middleware import Middleware
|
||||
from psycopg.errors import Error
|
||||
from redis.exceptions import RedisError
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import authentik_full_version
|
||||
@@ -31,7 +29,7 @@ from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
LOGGER = get_logger()
|
||||
HEALTHCHECK_LOGGER = get_logger("authentik.worker").bind()
|
||||
DB_ERRORS = (OperationalError, Error, RedisError)
|
||||
DB_ERRORS = (OperationalError, Error)
|
||||
|
||||
|
||||
class CurrentTask(BaseCurrentTask):
|
||||
@@ -188,8 +186,6 @@ class _healthcheck_handler(BaseHTTPRequestHandler):
|
||||
# Force connection reload
|
||||
db_conn.connect()
|
||||
_ = db_conn.cursor()
|
||||
redis_conn = get_redis_connection()
|
||||
redis_conn.ping()
|
||||
self.send_response(200)
|
||||
except DB_ERRORS: # pragma: no cover
|
||||
self.send_response(503)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# flake8: noqa
|
||||
from redis import Redis
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from lifecycle.migrate import BaseMigration
|
||||
|
||||
SQL_STATEMENT = """BEGIN TRANSACTION;
|
||||
@@ -106,17 +104,3 @@ class Migration(BaseMigration):
|
||||
def run(self):
|
||||
with self.con.transaction():
|
||||
self.cur.execute(SQL_STATEMENT)
|
||||
# We also need to clean the cache to make sure no pickeled objects still exist
|
||||
for db in [
|
||||
CONFIG.get("redis.message_queue_db"),
|
||||
CONFIG.get("redis.cache_db"),
|
||||
CONFIG.get("redis.ws_db"),
|
||||
]:
|
||||
redis = Redis(
|
||||
host=CONFIG.get("redis.host"),
|
||||
port=6379,
|
||||
db=db,
|
||||
username=CONFIG.get("redis.username"),
|
||||
password=CONFIG.get("redis.password"),
|
||||
)
|
||||
redis.flushall()
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
"""This file needs to be run from the root of the project to correctly
|
||||
import authentik. This is done by the dockerfile."""
|
||||
|
||||
from sys import exit as sysexit
|
||||
from time import sleep
|
||||
|
||||
from psycopg import OperationalError, connect
|
||||
from redis import Redis
|
||||
from redis.exceptions import RedisError
|
||||
|
||||
from authentik.lib.config import CONFIG, redis_url
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
CHECK_THRESHOLD = 30
|
||||
|
||||
@@ -40,24 +39,6 @@ def check_postgres():
|
||||
CONFIG.log("info", "PostgreSQL connection successful")
|
||||
|
||||
|
||||
def check_redis():
|
||||
url = CONFIG.get("cache.url") or redis_url(CONFIG.get("redis.db"))
|
||||
attempt = 0
|
||||
while True:
|
||||
if attempt >= CHECK_THRESHOLD:
|
||||
sysexit(1)
|
||||
try:
|
||||
redis = Redis.from_url(url)
|
||||
redis.ping()
|
||||
break
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})")
|
||||
finally:
|
||||
attempt += 1
|
||||
CONFIG.log("info", "Redis Connection successful")
|
||||
|
||||
|
||||
def wait_for_db():
|
||||
CONFIG.log("info", "Starting authentik bootstrap")
|
||||
# Sanity check, ensure SECRET_KEY is set before we even check for database connectivity
|
||||
@@ -69,7 +50,6 @@ def wait_for_db():
|
||||
CONFIG.log("info", "----------------------------------------------------------------------")
|
||||
sysexit(1)
|
||||
check_postgres()
|
||||
check_redis()
|
||||
CONFIG.log("info", "Finished authentik bootstrap")
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,12 @@ class DjangoDramatiqPostgres(AppConfig):
|
||||
verbose_name = "Django Dramatiq postgres"
|
||||
|
||||
def ready(self) -> None:
|
||||
old_broker = dramatiq.get_broker()
|
||||
try:
|
||||
old_broker = dramatiq.get_broker()
|
||||
except ModuleNotFoundError:
|
||||
old_broker = None
|
||||
|
||||
if len(old_broker.actors) != 0:
|
||||
if old_broker is not None and len(old_broker.actors) != 0:
|
||||
raise ImproperlyConfigured(
|
||||
"Actors were previously registered. "
|
||||
"Make sure your actors are not imported too early."
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# django-postgres-cache
|
||||
|
||||
### Use in migrations
|
||||
|
||||
Migrations that use the cache with this installed need to depend on the migration to create the cache entry table:
|
||||
|
||||
```python
|
||||
dependencies = [
|
||||
# ...other requirements
|
||||
("django_postgres_cache", "0001_initial"),
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DjangoPostgresCache(AppConfig):
|
||||
name = "django_postgres_cache"
|
||||
verbose_name = "Django Postgres cache"
|
||||
@@ -0,0 +1,51 @@
|
||||
from typing import Any
|
||||
|
||||
from django.core.cache.backends.db import DatabaseCache as BaseDatabaseCache
|
||||
from django.db.utils import ProgrammingError
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.timezone import now
|
||||
|
||||
from django_postgres_cache.models import CacheEntry
|
||||
|
||||
|
||||
class DatabaseCache(BaseDatabaseCache):
|
||||
|
||||
def __init__(self, table: str, params: dict[str, Any]) -> None:
|
||||
super().__init__(table, params)
|
||||
self.reverse_key_func = import_string(params["REVERSE_KEY_FUNCTION"])
|
||||
self._table = CacheEntry._meta.db_table
|
||||
self.cache_model_class = CacheEntry
|
||||
|
||||
def _cull(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Stubbed out cull method as we cull in a background task"""
|
||||
pass
|
||||
|
||||
def get(self, key: str, default: Any | None = None, version: int | None = None) -> Any:
|
||||
try:
|
||||
return super().get(key, default=default, version=version)
|
||||
except ProgrammingError:
|
||||
return default
|
||||
|
||||
def keys(self, keys_pattern: str, version: int | None = None) -> list[str]:
|
||||
try:
|
||||
return self._keys(keys_pattern, version=version)
|
||||
except ProgrammingError:
|
||||
return []
|
||||
|
||||
def _keys(self, keys_pattern: str, version: int | None = None) -> list[str]:
|
||||
keys_pattern = self.make_key(keys_pattern.replace("*", ".*"), version=version)
|
||||
|
||||
return [
|
||||
self.reverse_key_func(key)
|
||||
for key in CacheEntry.objects.filter(cache_key__regex=keys_pattern).values_list(
|
||||
"cache_key", flat=True
|
||||
)
|
||||
]
|
||||
|
||||
def ttl(self, key: str, version: int | None = None) -> int | None:
|
||||
"""Get TTL left for a given key and version"""
|
||||
key = self.make_and_validate_key(key, version=version)
|
||||
entry = CacheEntry.objects.filter(cache_key=key).first()
|
||||
if not entry:
|
||||
return None
|
||||
return int((entry.expires - now()).total_seconds())
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.12 on 2025-09-06 16:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CacheEntry",
|
||||
fields=[
|
||||
("cache_key", models.TextField(primary_key=True, serialize=False)),
|
||||
("value", models.TextField()),
|
||||
("expires", models.DateTimeField(db_index=True)),
|
||||
],
|
||||
options={
|
||||
"default_permissions": [],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CacheEntry(models.Model):
|
||||
|
||||
cache_key = models.TextField(primary_key=True)
|
||||
value = models.TextField()
|
||||
expires = models.DateTimeField(db_index=True)
|
||||
|
||||
class Meta:
|
||||
default_permissions = []
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Cache entry '{self.cache_key}'"
|
||||
@@ -0,0 +1,11 @@
|
||||
from django.utils.timezone import now
|
||||
|
||||
from authentik.lib.utils.db import chunked_queryset
|
||||
from django_postgres_cache.models import CacheEntry
|
||||
|
||||
|
||||
def clear_expired_cache() -> None:
|
||||
# FIXME: this is currently imported from the main project,
|
||||
# do we copy it here to make it independent?
|
||||
for obj in chunked_queryset(CacheEntry.objects.filter(expires__lt=now())):
|
||||
obj.delete()
|
||||
@@ -0,0 +1,46 @@
|
||||
[project]
|
||||
name = "django-postgres-cache"
|
||||
version = "0.1.0"
|
||||
description = "Improved Django Postgres Cache"
|
||||
requires-python = ">=3.9,<3.14"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
authors = [{ name = "Authentik Security Inc.", email = "hello@goauthentik.io" }]
|
||||
keywords = ["django", "cache", "postgres"]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Web Environment",
|
||||
"Framework :: Django",
|
||||
"Framework :: Django :: 4.2",
|
||||
"Framework :: Django :: 5.0",
|
||||
"Framework :: Django :: 5.1",
|
||||
"Framework :: Django :: 5.2",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: POSIX",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"django >=4.2,<6.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/goauthentik/authentik/tree/main/packages/django-postgres-cache"
|
||||
Documentation = "https://github.com/goauthentik/authentik/tree/main/packages/django-postgres-cache"
|
||||
Repository = "https://github.com/goauthentik/authentik/tree/main/packages/django-postgres-cache"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.setuptools.packages]
|
||||
find = {}
|
||||
+6
-2
@@ -16,13 +16,13 @@ dependencies = [
|
||||
"django-countries==7.6.1",
|
||||
"django-cte==2.0.0",
|
||||
"django-dramatiq-postgres",
|
||||
"django-postgres-cache",
|
||||
"django-filter==25.1",
|
||||
"django-guardian==3.0.3",
|
||||
"django-model-utils==5.0.0",
|
||||
"django-pglock==1.7.2",
|
||||
"django-pgtrigger==4.15.2",
|
||||
"django-prometheus==2.4.1",
|
||||
"django-redis==6.0.0",
|
||||
"django-storages[s3]==1.14.6",
|
||||
"django-tenants==3.8.0",
|
||||
"djangoql==0.18.1",
|
||||
@@ -120,11 +120,15 @@ no-binary-package = [
|
||||
[tool.uv.sources]
|
||||
djangorestframework = { git = "https://github.com/goauthentik/django-rest-framework", rev = "896722bab969fabc74a08b827da59409cf9f1a4e" }
|
||||
django-dramatiq-postgres = { workspace = true }
|
||||
django-postgres-cache = { workspace = true }
|
||||
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c" }
|
||||
channels-postgres = { git = "https://github.com/rissson/channels_postgres", rev = "93ed24e3c5317d7ccf7e8b1ce913c0f365d1728f" }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["packages/django-dramatiq-postgres"]
|
||||
members = [
|
||||
"packages/django-dramatiq-postgres",
|
||||
"packages/django-postgres-cache",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
ak = "lifecycle.ak:main"
|
||||
|
||||
@@ -6,6 +6,7 @@ requires-python = "==3.13.*"
|
||||
members = [
|
||||
"authentik",
|
||||
"django-dramatiq-postgres",
|
||||
"django-postgres-cache",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -178,8 +179,8 @@ dependencies = [
|
||||
{ name = "django-model-utils" },
|
||||
{ name = "django-pglock" },
|
||||
{ name = "django-pgtrigger" },
|
||||
{ name = "django-postgres-cache" },
|
||||
{ name = "django-prometheus" },
|
||||
{ name = "django-redis" },
|
||||
{ name = "django-storages", extra = ["s3"] },
|
||||
{ name = "django-tenants" },
|
||||
{ name = "djangoql" },
|
||||
@@ -280,8 +281,8 @@ requires-dist = [
|
||||
{ name = "django-model-utils", specifier = "==5.0.0" },
|
||||
{ name = "django-pglock", specifier = "==1.7.2" },
|
||||
{ name = "django-pgtrigger", specifier = "==4.15.2" },
|
||||
{ name = "django-postgres-cache", editable = "packages/django-postgres-cache" },
|
||||
{ name = "django-prometheus", specifier = "==2.4.1" },
|
||||
{ name = "django-redis", specifier = "==6.0.0" },
|
||||
{ name = "django-storages", extras = ["s3"], specifier = "==1.14.6" },
|
||||
{ name = "django-tenants", specifier = "==3.8.0" },
|
||||
{ name = "djangoql", specifier = "==0.18.1" },
|
||||
@@ -1041,6 +1042,17 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/40/97/81a4779e73f09d347d006b866b85b4be7774543d9f6eb6fe49a1303802af/django_pgtrigger-4.15.2-py3-none-any.whl", hash = "sha256:77ac6bd44fe5df0307e2630e3294cc68dcb25c9ec1a6bb523667546d28c80bea", size = 36434, upload-time = "2025-04-29T20:38:20.7Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-postgres-cache"
|
||||
version = "0.1.0"
|
||||
source = { editable = "packages/django-postgres-cache" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "django", specifier = ">=4.2,<6.0" }]
|
||||
|
||||
[[package]]
|
||||
name = "django-prometheus"
|
||||
version = "2.4.1"
|
||||
@@ -1054,19 +1066,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/50/9c5e022fa92574e5d20606687f15a2aa255e10512a17d11a8216fa117f72/django_prometheus-2.4.1-py2.py3-none-any.whl", hash = "sha256:7fe5af7f7c9ad9cd8a429fe0f3f1bf651f0e244f77162147869eab7ec09cc5e7", size = 29541, upload-time = "2025-06-25T15:45:35.433Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-redis"
|
||||
version = "6.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "django" },
|
||||
{ name = "redis" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/53/dbcfa1e528e0d6c39947092625b2c89274b5d88f14d357cee53c4d6dbbd4/django_redis-6.0.0.tar.gz", hash = "sha256:2d9cb12a20424a4c4dde082c6122f486628bae2d9c2bee4c0126a4de7fda00dd", size = 56904, upload-time = "2025-06-17T18:15:46.376Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/79/055dfcc508cfe9f439d9f453741188d633efa9eab90fc78a67b0ab50b137/django_redis-6.0.0-py3-none-any.whl", hash = "sha256:20bf0063a8abee567eb5f77f375143c32810c8700c0674ced34737f8de4e36c0", size = 33687, upload-time = "2025-06-17T18:15:34.165Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-storages"
|
||||
version = "1.14.6"
|
||||
|
||||
Reference in New Issue
Block a user