Compare commits

...
Author SHA1 Message Date
dependabot[bot]andstumpylog d7d4014bbd Chore(deps): Bump the pre-commit-dependencies group with 4 updates (#14636)
* Chore(deps): Bump the pre-commit-dependencies group with 4 updates

Bumps the pre-commit-dependencies group with 4 updates: [https://github.com/rbubley/mirrors-prettier](https://github.com/rbubley/mirrors-prettier), prettier, [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) and [https://github.com/tox-dev/pyproject-fmt](https://github.com/tox-dev/pyproject-fmt).


Updates `https://github.com/rbubley/mirrors-prettier` from v3.9.9 to 3.9.10
- [Commits](https://github.com/rbubley/mirrors-prettier/compare/v3.9.9...v3.9.10)

Updates `prettier` from 3.9.9 to 3.9.10

Updates `https://github.com/astral-sh/ruff-pre-commit` from v0.16.9 to 0.17.2
- [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases)
- [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.16.9...v0.17.2)

Updates `https://github.com/tox-dev/pyproject-fmt` from v2.29.4 to 2.30.3
- [Release notes](https://github.com/tox-dev/pyproject-fmt/releases)
- [Commits](https://github.com/tox-dev/pyproject-fmt/compare/v2.29.4...v2.30.3)

---
updated-dependencies:
- dependency-name: https://github.com/rbubley/mirrors-prettier
  dependency-version: 3.9.10
  dependency-type: direct:production
  dependency-group: pre-commit-dependencies
- dependency-name: prettier
  dependency-version: 3.9.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: pre-commit-dependencies
- dependency-name: https://github.com/astral-sh/ruff-pre-commit
  dependency-version: 0.17.2
  dependency-type: direct:production
  dependency-group: pre-commit-dependencies
- dependency-name: https://github.com/tox-dev/pyproject-fmt
  dependency-version: 2.30.3
  dependency-type: direct:production
  dependency-group: pre-commit-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Linting fixes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: stumpylog <797416+stumpylog@users.noreply.github.com>
2026-11-10 17:49:06 +00:00
shamoon a139bdff46 Fixhancement: add automatic clearing of sessions (#14722) 2026-11-10 08:09:25 -08:00
8 changed files with 87 additions and 16 deletions

No files matched your search

+4 -4
View File
@@ -38,7 +38,7 @@ repos:
- json
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
- repo: https://github.com/rbubley/mirrors-prettier
rev: 'v3.9.9'
rev: 'v3.9.10'
hooks:
- id: prettier
types_or:
@@ -46,16 +46,16 @@ repos:
- ts
- markdown
additional_dependencies:
- prettier@3.9.9
- prettier@3.9.10
- 'prettier-plugin-organize-imports@4.3.0'
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.9
rev: v0.17.2
hooks:
- id: ruff-check
- id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "v2.29.4"
rev: "v2.30.3"
hooks:
- id: pyproject-fmt
additional_dependencies: [tomli]
+8
View File
@@ -873,6 +873,14 @@ If both the [PAPERLESS_ACCOUNT_DEFAULT_GROUPS](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS
Defaults to 1209600 (2 weeks)
#### [`PAPERLESS_SESSION_CLEANUP_TASK_CRON=<cron expression>`](#PAPERLESS_SESSION_CLEANUP_TASK_CRON) {#PAPERLESS_SESSION_CLEANUP_TASK_CRON}
: Configures the schedule to remove expired sessions from the session store.
Django does not prune these automatically, so without this they would
accumulate indefinitely.
Defaults to `0 3 * * *`, once per day at 03:00.
#### [`PAPERLESS_TOKEN_THROTTLE_RATE=<rate>`](#PAPERLESS_TOKEN_THROTTLE_RATE) {#PAPERLESS_TOKEN_THROTTLE_RATE}
: Rate limit for the API token authentication endpoint (`/api/token/`), used to mitigate brute-force login attempts.
+1 -6
View File
@@ -212,12 +212,7 @@ extend-select = [
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
]
ignore = [
"DJ001",
"PLC0415",
"RUF012",
"SIM105",
]
ignore = [ "DJ001", "ICN001", "PLC0415", "RUF012", "SIM105" ]
# Migrations
per-file-ignores."*/migrations/*.py" = [
"E501",
+2 -6
View File
@@ -631,9 +631,7 @@ class ConsumerPlugin(
pk=self.metadata.actor_id,
).first()
if actor is not None:
from auditlog.context import ( # type: ignore[import-untyped]
set_actor,
)
from auditlog.context import set_actor # type: ignore[import-untyped]
with set_actor(actor):
original_document.save()
@@ -651,9 +649,7 @@ class ConsumerPlugin(
# Create a log entry for the version addition, if enabled
if settings.AUDIT_LOG_ENABLED:
from auditlog.models import ( # type: ignore[import-untyped]
LogEntry,
)
from auditlog.models import LogEntry # type: ignore[import-untyped]
LogEntry.objects.log_create(
instance=root_doc,
+15
View File
@@ -12,6 +12,7 @@ from celery import Task
from celery import shared_task
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from django.db import models
from django.db import transaction
from django.db.models.signals import post_save
@@ -925,3 +926,17 @@ def cleanup_expired_share_link_bundles() -> None:
)
if count:
logger.info("Deleted %s expired share link bundle(s)", count)
@shared_task
def clear_expired_sessions() -> None:
"""
Remove expired database sessions in batches to limit the size of each delete.
"""
expired = Session.objects.filter(expire_date__lt=timezone.now())
while session_keys := list(
expired.order_by("expire_date").values_list("pk", flat=True)[:500],
):
expired.filter(pk__in=session_keys).delete()
logger.info("Cleared expired sessions")
+36
View File
@@ -5,8 +5,11 @@ from unittest import mock
import pytest
from django.conf import settings
from django.contrib.sessions.models import Session
from django.db import connection
from django.test import TestCase
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils import timezone
from documents import tasks
@@ -612,3 +615,36 @@ class TestApplyAISuggestionsTask(DirectoriesMixin, TestCase):
apply_suggestions.assert_not_called()
self.assertIn("no longer exists", "".join(cm.output))
@pytest.mark.django_db
class TestClearExpiredSessions:
def test_clears_only_expired_sessions_in_batches(self) -> None:
"""
GIVEN:
- More than one batch of expired sessions and one still-valid session
WHEN:
- The clear_expired_sessions task runs
THEN:
- Only expired sessions are removed, using multiple deletes
"""
now = timezone.now()
Session.objects.bulk_create(
Session(
session_key=f"expired-{i}",
session_data="",
expire_date=now - timedelta(days=1),
)
for i in range(501)
)
Session.objects.create(
session_key="valid",
session_data="",
expire_date=now + timedelta(days=1),
)
with CaptureQueriesContext(connection) as queries:
tasks.clear_expired_sessions()
assert set(Session.objects.values_list("session_key", flat=True)) == {"valid"}
assert sum(query["sql"].startswith("DELETE FROM") for query in queries) == 2
+11
View File
@@ -165,6 +165,17 @@ def parse_beat_schedule() -> dict:
"expires": 23.0 * 60.0 * 60.0,
},
},
{
"name": "Clear expired sessions",
"env_key": "PAPERLESS_SESSION_CLEANUP_TASK_CRON",
# Default daily at 03:00
"env_default": "0 3 * * *",
"task": "documents.tasks.clear_expired_sessions",
"options": {
# 1 hour before default schedule sends again
"expires": 23.0 * 60.0 * 60.0,
},
},
]
for task in tasks:
# Either get the environment setting or use the default
@@ -182,6 +182,7 @@ def make_expected_schedule(
workflow_expire = 59.0 * 60.0
llm_index_expire = 23.0 * 60.0 * 60.0
share_link_cleanup_expire = 23.0 * 60.0 * 60.0
session_cleanup_expire = 23.0 * 60.0 * 60.0
schedule: dict[str, Any] = {
"Check all e-mail accounts": {
@@ -248,6 +249,14 @@ def make_expected_schedule(
"headers": {"trigger_source": "scheduled"},
},
},
"Clear expired sessions": {
"task": "documents.tasks.clear_expired_sessions",
"schedule": crontab(minute=0, hour="3"),
"options": {
"expires": session_cleanup_expire,
"headers": {"trigger_source": "scheduled"},
},
},
}
overrides = overrides or {}
@@ -298,6 +307,7 @@ class TestParseBeatSchedule:
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
"PAPERLESS_LLM_INDEX_TASK_CRON": "disable",
"PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON": "disable",
"PAPERLESS_SESSION_CLEANUP_TASK_CRON": "disable",
},
{},
id="all-disabled",