Merge branch 'dev' into feature-pnpm

This commit is contained in:
shamoon 2025-03-11 00:07:52 -07:00
commit 556bfb3b6f
No known key found for this signature in database
22 changed files with 587 additions and 589 deletions

View File

@ -1,18 +1,18 @@
codecov:
require_ci_to_pass: true
# https://docs.codecov.com/docs/flags#recommended-automatic-flag-management
# Require each flag to have 1 upload before notification
flag_management:
individual_flags:
- name: backend
# https://docs.codecov.com/docs/components
component_management:
individual_components:
- component_id: backend
paths:
- src/
- name: frontend
- src/**
- component_id: frontend
paths:
- src-ui/
- src-ui/**
# https://docs.codecov.com/docs/pull-request-comments
# codecov will only comment if coverage changes
comment:
layout: "header, diff, components, flags, files"
require_changes: true
# https://docs.codecov.com/docs/javascript-bundle-analysis
require_bundle_changes: true

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [shamoon, stumpylog]

View File

@ -89,3 +89,50 @@ updates:
- "major"
- "minor"
- "patch"
# Update Dockerfile in root directory
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "paperless-ngx/ci-cd"
labels:
- "ci-cd"
- "dependencies"
commit-message:
prefix: "docker"
include: "scope"
# Update Docker Compose files in docker/compose directory
- package-ecosystem: "docker-compose"
directory: "/docker/compose/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "paperless-ngx/ci-cd"
labels:
- "ci-cd"
- "dependencies"
commit-message:
prefix: "docker-compose"
include: "scope"
groups:
# Individual groups for each image
gotenberg:
patterns:
- "docker.io/gotenberg/gotenberg*"
tika:
patterns:
- "docker.io/apache/tika*"
redis:
patterns:
- "docker.io/library/redis*"
mariadb:
patterns:
- "docker.io/library/mariadb*"
postgres:
patterns:
- "docker.io/library/postgres*"

View File

@ -162,16 +162,20 @@ jobs:
--frozen \
pytest
-
name: Upload coverage
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
uses: actions/upload-artifact@v4
name: Upload backend test results to Codecov
if: always()
uses: codecov/test-results-action@v1
with:
name: backend-coverage-report
path: |
coverage.xml
junit.xml
retention-days: 7
if-no-files-found: error
token: ${{ secrets.CODECOV_TOKEN }}
flags: backend-python-${{ matrix.python-version }}
files: junit.xml
-
name: Upload backend coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: backend-python-${{ matrix.python-version }}
files: coverage.xml
-
name: Stop containers
if: always()
@ -179,7 +183,7 @@ jobs:
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down
install-frontend-depedendencies:
install-frontend-dependencies:
name: "Install Frontend Dependencies"
runs-on: ubuntu-24.04
needs:
@ -218,7 +222,7 @@ jobs:
name: "Frontend Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
runs-on: ubuntu-24.04
needs:
- install-frontend-depedendencies
- install-frontend-dependencies
strategy:
fail-fast: false
matrix:
@ -253,111 +257,33 @@ jobs:
run: cd src-ui && pnpm run lint
-
name: Run Jest unit tests
env:
JEST_JUNIT_OUTPUT_FILE: junit-report-${{ matrix.shard-index }}.xml
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
-
name: Upload Jest coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: jest-coverage-report-${{ matrix.shard-index }}
path: |
src-ui/coverage/coverage-final.json
src-ui/coverage/lcov.info
src-ui/coverage/clover.xml
retention-days: 7
if-no-files-found: error
-
name: Run Playwright e2e tests
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
-
name: Upload Playwright test results
name: Upload frontend test results to Codecov
uses: codecov/test-results-action@v1
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ matrix.shard-index }}
path: src-ui/playwright-report
retention-days: 7
-
name: Upload frontend test results
if: always()
uses: actions/upload-artifact@v4
with:
name: junit-report-${{ matrix.shard-index }}
path: src-ui/junit-report-${{ matrix.shard-index }}.xml
retention-days: 7
tests-coverage-upload:
name: "Upload to Codecov"
runs-on: ubuntu-24.04
needs:
- tests-backend
- tests-frontend
steps:
-
uses: actions/checkout@v4
-
name: Download frontend jest coverage
uses: actions/download-artifact@v4
with:
path: src-ui/coverage/
pattern: jest-coverage-report-*
-
name: Download frontend playwright coverage
uses: actions/download-artifact@v4
with:
path: src-ui/coverage/
pattern: playwright-report-*
merge-multiple: true
-
name: Download frontend test results
uses: actions/download-artifact@v4
with:
path: src-ui/junit/
pattern: junit-report-*
merge-multiple: true
token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/
-
name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend
flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/coverage/
# dont include backend coverage files here
files: '!coverage.xml'
-
name: Upload frontend test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend
directory: src-ui/junit/
-
name: Download backend coverage
uses: actions/download-artifact@v4
with:
name: backend-coverage-report
path: src/
-
name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
# future expansion
flags: backend
directory: src/
-
name: Upload backend test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: backend
directory: src/
frontend-bundle-analysis:
name: "Frontend Bundle Analysis"
runs-on: ubuntu-24.04
needs:
- tests-frontend
steps:
- uses: actions/checkout@v4
-
name: Install pnpm
uses: pnpm/action-setup@v4

View File

@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs
# Comments:
# - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.6.3-python3.12-bookworm-slim AS s6-overlay-base
FROM ghcr.io/astral-sh/uv:0.6.5-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6

View File

@ -38,7 +38,7 @@ services:
- redisdata:/data
db:
image: docker.io/library/postgres:16
image: docker.io/library/postgres:17
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data

View File

@ -38,7 +38,7 @@ services:
- redisdata:/data
db:
image: docker.io/library/postgres:16
image: docker.io/library/postgres:17
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data

View File

@ -34,7 +34,7 @@ services:
- redisdata:/data
db:
image: docker.io/library/postgres:16
image: docker.io/library/postgres:17
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data

View File

@ -37,7 +37,7 @@ dependencies = [
"djangorestframework~=3.15",
"djangorestframework-guardian~=0.3.0",
"drf-spectacular~=0.28",
"drf-spectacular-sidecar~=2025.2.1",
"drf-spectacular-sidecar~=2025.3.1",
"drf-writable-nested~=0.7.1",
"filelock~=3.17.0",
"flower~=2.0.1",
@ -48,7 +48,7 @@ dependencies = [
"jinja2~=3.1.5",
"langdetect~=1.0.9",
"nltk~=3.9.1",
"ocrmypdf~=16.9.0",
"ocrmypdf~=16.10.0",
"pathvalidate~=3.2.3",
"pdf2image~=1.17.0",
"python-dateutil~=2.9.0",
@ -73,9 +73,9 @@ optional-dependencies.mariadb = [
"mysqlclient~=2.2.7",
]
optional-dependencies.postgres = [
"psycopg[c]==3.2.4",
"psycopg[c]==3.2.5",
# Direct dependency for proper resolution of the pre-built wheels
"psycopg-c==3.2.4",
"psycopg-c==3.2.5",
]
optional-dependencies.webserver = [
"granian~=1.7.6",
@ -343,8 +343,8 @@ environments = [
[tool.uv.sources]
# Markers are chosen to select these almost exclusively when building the Docker image
psycopg-c = [
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
]
zxing-cpp = [
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },

View File

@ -36,7 +36,13 @@ export const routes: Routes = [
component: AppFrameComponent,
canDeactivate: [DirtyDocGuard],
children: [
{ path: 'dashboard', component: DashboardComponent },
{
path: 'dashboard',
component: DashboardComponent,
data: {
componentName: 'AppFrameComponent',
},
},
{
path: 'documents',
component: DocumentListComponent,
@ -47,6 +53,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Document,
},
componentName: 'DocumentListComponent',
},
},
{
@ -59,6 +66,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.SavedView,
},
componentName: 'DocumentListComponent',
},
},
{
@ -70,6 +78,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Document,
},
componentName: 'DocumentDetailComponent',
},
},
{
@ -81,6 +90,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Document,
},
componentName: 'DocumentDetailComponent',
},
},
{
@ -92,6 +102,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Document,
},
componentName: 'DocumentAsnComponent',
},
},
{
@ -103,6 +114,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Tag,
},
componentName: 'TagListComponent',
},
},
{
@ -114,6 +126,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.DocumentType,
},
componentName: 'DocumentTypeListComponent',
},
},
{
@ -125,6 +138,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Correspondent,
},
componentName: 'CorrespondentListComponent',
},
},
{
@ -136,6 +150,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.StoragePath,
},
componentName: 'StoragePathListComponent',
},
},
{
@ -144,6 +159,7 @@ export const routes: Routes = [
canActivate: [PermissionsGuard],
data: {
requireAdmin: true,
componentName: 'LogsComponent',
},
},
{
@ -155,6 +171,7 @@ export const routes: Routes = [
action: PermissionAction.Delete,
type: PermissionType.Document,
},
componentName: 'TrashComponent',
},
},
// redirect old paths
@ -180,6 +197,7 @@ export const routes: Routes = [
action: PermissionAction.Change,
type: PermissionType.UISettings,
},
componentName: 'SettingsComponent',
},
},
{
@ -192,6 +210,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.UISettings,
},
componentName: 'SettingsComponent',
},
},
{
@ -203,6 +222,7 @@ export const routes: Routes = [
action: PermissionAction.Change,
type: PermissionType.AppConfig,
},
componentName: 'ConfigComponent',
},
},
{
@ -214,6 +234,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.PaperlessTask,
},
componentName: 'TasksComponent',
},
},
{
@ -225,6 +246,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.CustomField,
},
componentName: 'CustomFieldsComponent',
},
},
{
@ -236,6 +258,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.Workflow,
},
componentName: 'WorkflowsComponent',
},
},
{
@ -247,6 +270,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.MailAccount,
},
componentName: 'MailComponent',
},
},
{
@ -258,6 +282,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.User,
},
componentName: 'UsersAndGroupsComponent',
},
},
{
@ -269,6 +294,7 @@ export const routes: Routes = [
action: PermissionAction.View,
type: PermissionType.SavedView,
},
componentName: 'SavedViewsComponent',
},
},
],

View File

@ -29,7 +29,7 @@ describe('ComponentRouterService', () => {
eventsSubject.next(
new ActivationStart({
url: 'test-url',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)
@ -41,13 +41,13 @@ describe('ComponentRouterService', () => {
eventsSubject.next(
new ActivationStart({
url: 'test-url-1',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)
eventsSubject.next(
new ActivationStart({
url: 'test-url-2',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)
@ -59,13 +59,13 @@ describe('ComponentRouterService', () => {
eventsSubject.next(
new ActivationStart({
url: 'test-url-1',
component: { name: 'TestComponent1' },
data: { componentName: 'TestComponent1' },
} as any)
)
eventsSubject.next(
new ActivationStart({
url: 'test-url-2',
component: { name: 'TestComponent2' },
data: { componentName: 'TestComponent2' },
} as any)
)
@ -76,13 +76,13 @@ describe('ComponentRouterService', () => {
eventsSubject.next(
new ActivationStart({
url: 'test-url-1',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)
eventsSubject.next(
new ActivationStart({
url: 'test-url-2',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)
@ -93,7 +93,7 @@ describe('ComponentRouterService', () => {
eventsSubject.next(
new ActivationStart({
url: 'test-url',
component: { name: 'TestComponent' },
data: { componentName: 'TestComponent' },
} as any)
)

View File

@ -17,11 +17,11 @@ export class ComponentRouterService {
.subscribe((event: ActivationStart) => {
if (
this.componentHistory[this.componentHistory.length - 1] !==
event.snapshot.component.name &&
!EXCLUDE_COMPONENTS.includes(event.snapshot.component.name)
event.snapshot.data.componentName &&
!EXCLUDE_COMPONENTS.includes(event.snapshot.data.componentName)
) {
this.history.push(event.snapshot.url.toString())
this.componentHistory.push(event.snapshot.component.name)
this.componentHistory.push(event.snapshot.data.componentName)
} else {
// Update the URL of the current component in case the same component was loaded via a different URL
this.history[this.history.length - 1] = event.snapshot.url.toString()

View File

@ -61,7 +61,7 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
),
)
Path(settings.SCRATCH_DIR).mkdir(parents=True, exists_ok=True)
Path(settings.SCRATCH_DIR).mkdir(parents=True, exist_ok=True)
for f in sorted(documents):
if not f.endswith("gpg"):

View File

@ -43,6 +43,7 @@ from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import MatchingModel
from documents.models import Note
from documents.models import PaperlessTask
from documents.models import SavedView
from documents.models import SavedViewFilterRule
@ -861,6 +862,22 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
]
class BasicUserSerializer(serializers.ModelSerializer):
# Different than paperless.serializers.UserSerializer
class Meta:
model = User
fields = ["id", "username", "first_name", "last_name"]
class NotesSerializer(serializers.ModelSerializer):
user = BasicUserSerializer()
class Meta:
model = Note
fields = ["id", "note", "created", "user"]
ordering = ["-created"]
class DocumentSerializer(
OwnedObjectSerializer,
NestedUpdateMixin,
@ -876,6 +893,8 @@ class DocumentSerializer(
created_date = serializers.DateField(required=False)
page_count = SerializerMethodField()
notes = NotesSerializer(many=True, required=False)
custom_fields = CustomFieldInstanceSerializer(
many=True,
allow_null=False,

View File

@ -0,0 +1,18 @@
{% extends "paperless-ngx/base.html" %}
{% load i18n %}
{% block head_title %}
{% trans "Paperless-ngx account inactive" %}
{% endblock head_title %}
{% block form_top_content %}
<h4>{% translate "Account inactive." %}</h4>
{% endblock form_top_content %}
{% block form_content %}
{% url 'account_login' as login_url %}
<p>{% translate "This account is inactive." %}</p>
<div class="d-grid mt-3">
<a class="btn btn-lg btn-primary" href="{{ login_url }}">{% translate "Return to login" %}</a>
</div>
{% endblock form_content %}

View File

@ -2170,8 +2170,10 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
GIVEN:
- A document with a single note
WHEN:
- API request for document
- API request for document notes is made
THEN:
- Note is included in the document response
- The associated note is returned
"""
doc = Document.objects.create(
@ -2185,6 +2187,18 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
user=self.user,
)
response = self.client.get(
f"/api/documents/{doc.pk}/",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
resp_data = response.json()
self.assertEqual(len(resp_data["notes"]), 1)
self.assertEqual(resp_data["notes"][0]["note"], note.note)
self.assertEqual(resp_data["notes"][0]["user"]["username"], self.user.username)
response = self.client.get(
f"/api/documents/{doc.pk}/notes/",
format="json",

View File

@ -4,6 +4,7 @@ from pathlib import Path
from django.conf import settings
from documents.tests.utils import DirectoriesMixin
from documents.tests.utils import TestMigrations
@ -14,7 +15,7 @@ def source_path_before(self):
return os.path.join(settings.ORIGINALS_DIR, fname)
class TestMigrateDocumentPageCount(TestMigrations):
class TestMigrateDocumentPageCount(DirectoriesMixin, TestMigrations):
migrate_from = "1052_document_transaction_id"
migrate_to = "1053_document_page_count"

View File

@ -803,33 +803,6 @@ class DocumentViewSet(
except (FileNotFoundError, Document.DoesNotExist):
raise Http404
def getNotes(self, doc):
return [
{
"id": c.pk,
"note": c.note,
"created": c.created,
"user": {
"id": c.user.id,
"username": c.user.username,
"first_name": c.user.first_name,
"last_name": c.user.last_name,
},
}
for c in Note.objects.select_related("user")
.only(
"pk",
"note",
"created",
"user__id",
"user__username",
"user__first_name",
"user__last_name",
)
.filter(document=doc)
.order_by("-created")
]
@action(
methods=["get", "post", "delete"],
detail=True,
@ -854,9 +827,11 @@ class DocumentViewSet(
except Document.DoesNotExist:
raise Http404
serializer = self.get_serializer(doc)
if request.method == "GET":
try:
notes = self.getNotes(doc)
notes = serializer.to_representation(doc).get("notes")
return Response(notes)
except Exception as e:
logger.warning(f"An error occurred retrieving notes: {e!s}")
@ -897,7 +872,7 @@ class DocumentViewSet(
index.add_or_update_document(doc)
notes = self.getNotes(doc)
notes = serializer.to_representation(doc).get("notes")
return Response(notes)
except Exception as e:
@ -934,7 +909,9 @@ class DocumentViewSet(
index.add_or_update_document(doc)
return Response(self.getNotes(doc))
notes = serializer.to_representation(doc).get("notes")
return Response(notes)
return Response(
{

File diff suppressed because it is too large Load Diff

View File

@ -278,10 +278,15 @@ urlpatterns = [
include(
[
# see allauth/account/urls.py
# login, logout, signup
# login, logout, signup, account_inactive
path("login/", allauth_account_views.login, name="account_login"),
path("logout/", allauth_account_views.logout, name="account_logout"),
path("signup/", allauth_account_views.signup, name="account_signup"),
path(
"account_inactive/",
allauth_account_views.account_inactive,
name="account_inactive",
),
# password reset
path(
"password/",

View File

@ -32,6 +32,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from documents.index import DelayedQuery
from documents.permissions import PaperlessObjectPermissions
from paperless.filters import GroupFilterSet
from paperless.filters import UserFilterSet
@ -66,17 +67,17 @@ class StandardPagination(PageNumberPagination):
)
def get_all_result_ids(self):
ids = []
if hasattr(self.page.paginator.object_list, "saved_results"):
results_page = self.page.paginator.object_list.saved_results[0]
if results_page is not None:
for i in range(len(results_page.results.docs())):
try:
fields = results_page.results.fields(i)
if "id" in fields:
ids.append(fields["id"])
except Exception:
pass
query = self.page.paginator.object_list
if isinstance(query, DelayedQuery):
try:
ids = [
query.searcher.ixreader.stored_fields(
doc_num,
)["id"]
for doc_num in query.saved_results.get(0).results.docs()
]
except Exception:
pass
else:
ids = self.page.paginator.object_list.values_list("pk", flat=True)
return ids

58
uv.lock generated
View File

@ -880,14 +880,14 @@ wheels = [
[[package]]
name = "drf-spectacular-sidecar"
version = "2025.2.1"
version = "2025.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7e/4c/6c945509d578f04d19eee2ffeb25bc37e6aeb9713688a87fc4f3b02bc32b/drf_spectacular_sidecar-2025.2.1.tar.gz", hash = "sha256:ca9507c5fe708680d6b8eda96928f3cf3ffc07e8d67678778bcd2e80f9f2baae", size = 2390256 }
sdist = { url = "https://files.pythonhosted.org/packages/80/5c/85671ba50957e0ab45e2828ca713cd7b5d0d237c398936c1b45bd0286b72/drf_spectacular_sidecar-2025.3.1.tar.gz", hash = "sha256:7425940a409fb68a46c9b024eb504098fab5b4d73d12573efcaef048445d678f", size = 2401986 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/8a/b66f58e92c5752bbc241bb04ce76d2bf92c676af4cb8b94edac3a0e0c701/drf_spectacular_sidecar-2025.2.1-py3-none-any.whl", hash = "sha256:674e1336810c7cf117442300b5c213c4fee1e984ba48689502c947a6ecd25d0c", size = 2409274 },
{ url = "https://files.pythonhosted.org/packages/5e/09/7382eb93a09f33fac72a8af1fc986f43681d15dd5fda750b238a8aace0e8/drf_spectacular_sidecar-2025.3.1-py3-none-any.whl", hash = "sha256:6b9c96204c8e45a06c39928dad704b18536d3253b61591c478540b63db6bdcea", size = 2422301 },
]
[[package]]
@ -1803,7 +1803,7 @@ wheels = [
[[package]]
name = "ocrmypdf"
version = "16.9.0"
version = "16.10.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecation", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@ -1816,9 +1816,9 @@ dependencies = [
{ name = "pluggy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/07/d8810415b98718a39b1161720acd925b6ef15743227012809f6da9d3b7bc/ocrmypdf-16.9.0.tar.gz", hash = "sha256:d000a2294cd1478d4bbfe15df5172327f77f4139bb5307404bc53be9bd81f039", size = 6804849 }
sdist = { url = "https://files.pythonhosted.org/packages/07/cf/d207aea8442a8e5a63b16faae89af2b9e3474d8d5925a5ea8c4f10f73fa9/ocrmypdf-16.10.0.tar.gz", hash = "sha256:d5b907a7b92951f1f3617f0f5ca002d866143d94fd168546a70e51756bf6412e", size = 6809110 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/69/e40431c5410eaf0c55588d0d256e27ee32af7622cb7116ac99a2e868bd2f/ocrmypdf-16.9.0-py3-none-any.whl", hash = "sha256:33fec95450727b0d9482ee3851e45dd0219ff8d52a14fd45a8d3d0c71875584e", size = 161863 },
{ url = "https://files.pythonhosted.org/packages/33/47/10058f54f593f5e618a6796fc3e8dc3e19536128f832e2d3d6e4943e9834/ocrmypdf-16.10.0-py3-none-any.whl", hash = "sha256:5093b9b058e7278b17c0b0978eb5175063b7a5511e3b9068257ece063d91ce8f", size = 162336 },
]
[[package]]
@ -1904,9 +1904,9 @@ mariadb = [
]
postgres = [
{ name = "psycopg", extra = ["c"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version != '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version != '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or sys_platform == 'darwin'" },
{ name = "psycopg-c", version = "3.2.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version != '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version != '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or sys_platform == 'darwin'" },
{ name = "psycopg-c", version = "3.2.5", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.5", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
]
webserver = [
{ name = "granian", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@ -1994,7 +1994,7 @@ requires-dist = [
{ name = "djangorestframework", specifier = "~=3.15" },
{ name = "djangorestframework-guardian", specifier = "~=0.3.0" },
{ name = "drf-spectacular", specifier = "~=0.28" },
{ name = "drf-spectacular-sidecar", specifier = "~=2025.2.1" },
{ name = "drf-spectacular-sidecar", specifier = "~=2025.3.1" },
{ name = "drf-writable-nested", specifier = "~=0.7.1" },
{ name = "filelock", specifier = "~=3.17.0" },
{ name = "flower", specifier = "~=2.0.1" },
@ -2007,13 +2007,13 @@ requires-dist = [
{ name = "langdetect", specifier = "~=1.0.9" },
{ name = "mysqlclient", marker = "extra == 'mariadb'", specifier = "~=2.2.7" },
{ name = "nltk", specifier = "~=3.9.1" },
{ name = "ocrmypdf", specifier = "~=16.9.0" },
{ name = "ocrmypdf", specifier = "~=16.10.0" },
{ name = "pathvalidate", specifier = "~=3.2.3" },
{ name = "pdf2image", specifier = "~=1.17.0" },
{ name = "psycopg", extras = ["c"], marker = "extra == 'postgres'", specifier = "==3.2.4" },
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl" },
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl" },
{ name = "psycopg-c", marker = "(python_full_version != '3.12.*' and platform_machine == 'aarch64' and extra == 'postgres') or (python_full_version != '3.12.*' and platform_machine == 'x86_64' and extra == 'postgres') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'postgres') or (sys_platform != 'linux' and extra == 'postgres')", specifier = "==3.2.4" },
{ name = "psycopg", extras = ["c"], marker = "extra == 'postgres'", specifier = "==3.2.5" },
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl" },
{ name = "psycopg-c", marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'postgres'", url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl" },
{ name = "psycopg-c", marker = "(python_full_version != '3.12.*' and platform_machine == 'aarch64' and extra == 'postgres') or (python_full_version != '3.12.*' and platform_machine == 'x86_64' and extra == 'postgres') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and extra == 'postgres') or (sys_platform != 'linux' and extra == 'postgres')", specifier = "==3.2.5" },
{ name = "python-dateutil", specifier = "~=2.9.0" },
{ name = "python-dotenv", specifier = "~=1.0.1" },
{ name = "python-gnupg", specifier = "~=0.5.4" },
@ -2348,53 +2348,53 @@ wheels = [
[[package]]
name = "psycopg"
version = "3.2.4"
version = "3.2.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e0/f2/954b1467b3e2ca5945b83b5e320268be1f4df486c3e8ffc90f4e4b707979/psycopg-3.2.4.tar.gz", hash = "sha256:f26f1346d6bf1ef5f5ef1714dd405c67fb365cfd1c6cea07de1792747b167b92", size = 156109 }
sdist = { url = "https://files.pythonhosted.org/packages/0e/cf/dc1a4d45e3c6222fe272a245c5cea9a969a7157639da606ac7f2ab5de3a1/psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8", size = 156158 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/49/15114d5f7ee68983f4e1a24d47e75334568960352a07c6f0e796e912685d/psycopg-3.2.4-py3-none-any.whl", hash = "sha256:43665368ccd48180744cab26b74332f46b63b7e06e8ce0775547a3533883d381", size = 198716 },
{ url = "https://files.pythonhosted.org/packages/18/f3/14a1370b1449ca875d5e353ef02cb9db6b70bd46ec361c236176837c0be1/psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879", size = 198749 },
]
[package.optional-dependencies]
c = [
{ name = "psycopg-c", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version != '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version != '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux') or (implementation_name != 'pypy' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (implementation_name != 'pypy' and sys_platform == 'darwin')" },
{ name = "psycopg-c", version = "3.2.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.4", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version != '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version != '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux') or (implementation_name != 'pypy' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (implementation_name != 'pypy' and sys_platform == 'darwin')" },
{ name = "psycopg-c", version = "3.2.5", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux'" },
{ name = "psycopg-c", version = "3.2.5", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
]
[[package]]
name = "psycopg-c"
version = "3.2.4"
version = "3.2.5"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"sys_platform == 'darwin'",
"(python_full_version != '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version != '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux')",
]
sdist = { url = "https://files.pythonhosted.org/packages/17/76/dbdadd9b93b8ad38cff31402c73a6bb9a23c88a4466fa09655d3c6db4d11/psycopg_c-3.2.4.tar.gz", hash = "sha256:22097a04263efb2efd2cc8b00a51fa90e23f9cd4a2e09903fe4d9c6923dac17a", size = 601853 }
sdist = { url = "https://files.pythonhosted.org/packages/cf/cb/468dcca82f08b47af59af4681ef39473cf5c0ef2e09775c701ccdf7284d6/psycopg_c-3.2.5.tar.gz", hash = "sha256:57ad4cfd28de278c424aaceb1f2ad5c7910466e315dfe84e403f3c7a0a2ce81b", size = 609318 }
[[package]]
name = "psycopg-c"
version = "3.2.4"
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl" }
version = "3.2.5"
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl" }
resolution-markers = [
"python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
]
wheels = [
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl", hash = "sha256:7a3b1152b676afe4a9113f784257221cf85147812116de86970272553a846f40" },
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl", hash = "sha256:39012b8df2ef34e172d43ab5976017d054f2c2fc549854927a62e73f5253eacc" },
]
[[package]]
name = "psycopg-c"
version = "3.2.4"
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl" }
version = "3.2.5"
source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl" }
resolution-markers = [
"python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'",
]
wheels = [
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl", hash = "sha256:d0c0c87699846bd7f8c9259db0d17f0c9d062f3281d07b32be00ca28cdcbd5f3" },
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl", hash = "sha256:a0667a62595e355c2d3b6ac05336403c998fbfb31cf6922d73e19018016df1bc" },
]
[[package]]