Compare commits

..

15 Commits

Author SHA1 Message Date
shamoon
c968505f64 Fix: run pre-flight ASN check after barcode detection 2025-12-30 10:02:32 -08:00
shamoon
fbb5864757 Add test to reproduce asn in trash error 2025-12-30 10:02:31 -08:00
GitHub Actions
72fd05501b Auto translate strings 2025-12-29 14:50:09 +00:00
shamoon
a3c19b1e2d Fix: validate cf integer values within PostgreSQL range (#11666) 2025-12-29 06:48:31 -08:00
shamoon
2e6458dbcc Fix environment variable reference in workflow 2025-12-28 20:50:04 -08:00
shamoon
8471507115 Fix ref injection in translate-strings workflow 2025-12-28 20:47:44 -08:00
shamoon
99724a25a2 Fix: support ordering by storage path name (#11661) 2025-12-28 16:05:21 -08:00
shamoon
504c824cfe Fix: propagate metadata override created value (#11659) 2025-12-27 19:42:45 -08:00
shamoon
01c7a345cb Merge branch 'main' into dev 2025-12-26 11:03:55 -08:00
shamoon
985dc9be31 Documentation: default bool value consistency 2025-12-26 08:17:44 -08:00
GitHub Actions
890c2d6757 Auto translate strings 2025-12-24 05:28:28 +00:00
shamoon
00cf026524 Feature: Indonesian translation (#11641) 2025-12-23 21:26:53 -08:00
shamoon
7604a0b583 Fix: prevent ASN collisions for merge operations (#11634) 2025-12-19 20:05:34 -08:00
shamoon
4e789acf2d Chore: mark test_error_skip_rule as flaky 2025-12-18 10:15:04 -08:00
shamoon
d9459d04ea Chore: refactor preview URL variable naming and safeUrl usage 2025-12-18 09:59:14 -08:00
25 changed files with 287 additions and 146 deletions

View File

@@ -12,9 +12,11 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
env:
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
with:
token: ${{ secrets.PNGX_BOT_PAT }}
ref: ${{ github.head_ref }}
ref: ${{ env.GH_REF }}
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6

View File

@@ -1007,7 +1007,7 @@ still perform some basic text pre-processing before matching.
: See also `PAPERLESS_NLTK_DIR`.
Defaults to 1.
Defaults to true, enabling the feature.
#### [`PAPERLESS_DATE_PARSER_LANGUAGES=<lang>`](#PAPERLESS_DATE_PARSER_LANGUAGES) {#PAPERLESS_DATE_PARSER_LANGUAGES}
@@ -1074,7 +1074,7 @@ valid crontab(5) expression describing when to run.
: Enables compression of the responses from the webserver.
: Defaults to 1, enabling compression.
: Defaults to true, enabling compression.
!!! note

View File

@@ -84,7 +84,7 @@ optional-dependencies.postgres = [
"psycopg-pool==3.2.7",
]
optional-dependencies.webserver = [
"granian[uvloop]~=2.6.0",
"granian[uvloop]~=2.5.1",
]
[dependency-groups]

View File

@@ -31,6 +31,7 @@
"fi-FI": "src/locale/messages.fi_FI.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"hu-HU": "src/locale/messages.hu_HU.xlf",
"id-ID": "src/locale/messages.id_ID.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"ja-JP": "src/locale/messages.ja_JP.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",

View File

@@ -10028,179 +10028,186 @@
<context context-type="linenumber">135</context>
</context-group>
</trans-unit>
<trans-unit id="8312065814232621608" datatype="html">
<source>Indonesian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">141</context>
</context-group>
</trans-unit>
<trans-unit id="2935232983274991580" datatype="html">
<source>Italian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">141</context>
<context context-type="linenumber">147</context>
</context-group>
</trans-unit>
<trans-unit id="6924606686202701860" datatype="html">
<source>Japanese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">147</context>
<context context-type="linenumber">153</context>
</context-group>
</trans-unit>
<trans-unit id="6145439649200570157" datatype="html">
<source>Korean</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">153</context>
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="1334425850005897370" datatype="html">
<source>Luxembourgish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">159</context>
<context context-type="linenumber">165</context>
</context-group>
</trans-unit>
<trans-unit id="3071065188816255493" datatype="html">
<source>Dutch</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">165</context>
<context context-type="linenumber">171</context>
</context-group>
</trans-unit>
<trans-unit id="8069284467804715623" datatype="html">
<source>Norwegian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">171</context>
<context context-type="linenumber">177</context>
</context-group>
</trans-unit>
<trans-unit id="4977087909184008115" datatype="html">
<source>Persian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">177</context>
<context context-type="linenumber">183</context>
</context-group>
</trans-unit>
<trans-unit id="792060551707690640" datatype="html">
<source>Polish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">183</context>
<context context-type="linenumber">189</context>
</context-group>
</trans-unit>
<trans-unit id="9184513005098760425" datatype="html">
<source>Portuguese (Brazil)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">189</context>
<context context-type="linenumber">195</context>
</context-group>
</trans-unit>
<trans-unit id="153799456510623899" datatype="html">
<source>Portuguese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">195</context>
<context context-type="linenumber">201</context>
</context-group>
</trans-unit>
<trans-unit id="8118856427047826368" datatype="html">
<source>Romanian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">201</context>
<context context-type="linenumber">207</context>
</context-group>
</trans-unit>
<trans-unit id="7137419789978325708" datatype="html">
<source>Russian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">213</context>
</context-group>
</trans-unit>
<trans-unit id="9102963095355753902" datatype="html">
<source>Slovak</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">213</context>
<context context-type="linenumber">219</context>
</context-group>
</trans-unit>
<trans-unit id="4287008301409320881" datatype="html">
<source>Slovenian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">219</context>
<context context-type="linenumber">225</context>
</context-group>
</trans-unit>
<trans-unit id="8608389829607915090" datatype="html">
<source>Serbian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">225</context>
<context context-type="linenumber">231</context>
</context-group>
</trans-unit>
<trans-unit id="499386805970351976" datatype="html">
<source>Swedish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">231</context>
<context context-type="linenumber">237</context>
</context-group>
</trans-unit>
<trans-unit id="5682359291233237791" datatype="html">
<source>Turkish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">237</context>
<context context-type="linenumber">243</context>
</context-group>
</trans-unit>
<trans-unit id="3578644052206125685" datatype="html">
<source>Ukrainian</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">243</context>
<context context-type="linenumber">249</context>
</context-group>
</trans-unit>
<trans-unit id="3611216939636790848" datatype="html">
<source>Vietnamese</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">249</context>
<context context-type="linenumber">255</context>
</context-group>
</trans-unit>
<trans-unit id="4689443708886954687" datatype="html">
<source>Chinese Simplified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">255</context>
<context context-type="linenumber">261</context>
</context-group>
</trans-unit>
<trans-unit id="8082606363137705994" datatype="html">
<source>Chinese Traditional</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">261</context>
<context context-type="linenumber">267</context>
</context-group>
</trans-unit>
<trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">269</context>
<context context-type="linenumber">275</context>
</context-group>
</trans-unit>
<trans-unit id="313643372755303297" datatype="html">
<source>Successfully completed one-time migratration of settings to the database!</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">603</context>
<context context-type="linenumber">609</context>
</context-group>
</trans-unit>
<trans-unit id="5558341108007064934" datatype="html">
<source>Unable to migrate settings to the database, please try saving manually.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">604</context>
<context context-type="linenumber">610</context>
</context-group>
</trans-unit>
<trans-unit id="1168781785897678748" datatype="html">
<source>You can restart the tour from the settings page.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">677</context>
<context context-type="linenumber">683</context>
</context-group>
</trans-unit>
<trans-unit id="3852289441366561594" datatype="html">

View File

@@ -28,6 +28,7 @@ import localeFa from '@angular/common/locales/fa'
import localeFi from '@angular/common/locales/fi'
import localeFr from '@angular/common/locales/fr'
import localeHu from '@angular/common/locales/hu'
import localeId from '@angular/common/locales/id'
import localeIt from '@angular/common/locales/it'
import localeJa from '@angular/common/locales/ja'
import localeKo from '@angular/common/locales/ko'
@@ -63,6 +64,7 @@ registerLocaleData(localeFa)
registerLocaleData(localeFi)
registerLocaleData(localeFr)
registerLocaleData(localeHu)
registerLocaleData(localeId)
registerLocaleData(localeIt)
registerLocaleData(localeJa)
registerLocaleData(localeKo)

View File

@@ -14,7 +14,7 @@
@if (previewText) {
<div class="bg-light p-3 overflow-auto whitespace-preserve" width="100%">{{previewText}}</div>
} @else {
<object [data]="previewURL | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
<object [data]="previewUrl | safeUrl" width="100%" class="bg-light" [class.p-2]="!isPdf"></object>
}
} @else {
@if (requiresPassword) {
@@ -24,7 +24,7 @@
}
@if (!requiresPassword) {
<pdf-viewer
[src]="previewURL"
[src]="previewUrl"
[original-size]="false"
[show-borders]="false"
[show-all]="true"

View File

@@ -71,7 +71,7 @@ export class PreviewPopupComponent implements OnDestroy {
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
}
get previewURL() {
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
@@ -93,7 +93,7 @@ export class PreviewPopupComponent implements OnDestroy {
init() {
if (this.document.mime_type?.includes('text')) {
this.http
.get(this.previewURL, { responseType: 'text' })
.get(this.previewUrl, { responseType: 'text' })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (res) => {
@@ -126,10 +126,6 @@ export class PreviewPopupComponent implements OnDestroy {
}
}
get previewUrl() {
return this.documentService.getPreviewUrl(this.document.id)
}
mouseEnterPreview() {
this.mouseOnPreview = true
if (!this.popover.isOpen()) {

View File

@@ -379,7 +379,7 @@
<ng-template #previewContent>
<div class="thumb-preview position-absolute pe-none text-center" [class.fade]="previewLoaded">
@if (showThumbnailOverlay) {
<img [src]="thumbUrl | safeUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
<img [src]="thumbUrl" class="mx-auto" [attr.width]="previewZoomScale === 'page-fit' ? 'auto' : '100%'" [attr.height]="previewZoomScale === 'page-fit' ? '100%' : 'auto'" alt="Document loading..." i18n-alt />
}
<div class="position-absolute top-0 start-0 m-2 p-2 d-flex align-items-center justify-content-center">
<div>
@@ -414,7 +414,7 @@
}
@case (ContentRenderType.Image) {
<div class="preview-sticky">
<img [src]="previewUrl | safeUrl" width="100%" height="100%" alt="{{title}}" />
<img [src]="previewUrl" width="100%" height="100%" alt="{{title}}" />
</div>
}
@case (ContentRenderType.TIFF) {

View File

@@ -136,6 +136,12 @@ const LANGUAGE_OPTIONS = [
englishName: 'Hungarian',
dateInputFormat: 'yyyy.mm.dd',
},
{
code: 'id-id',
name: $localize`Indonesian`,
englishName: 'Indonesian',
dateInputFormat: 'dd-mm-yyyy',
},
{
code: 'it-it',
name: $localize`Italian`,

View File

@@ -171,6 +171,7 @@ import localeFa from '@angular/common/locales/fa'
import localeFi from '@angular/common/locales/fi'
import localeFr from '@angular/common/locales/fr'
import localeHu from '@angular/common/locales/hu'
import localeId from '@angular/common/locales/id'
import localeIt from '@angular/common/locales/it'
import localeJa from '@angular/common/locales/ja'
import localeKo from '@angular/common/locales/ko'
@@ -209,6 +210,7 @@ registerLocaleData(localeFa)
registerLocaleData(localeFi)
registerLocaleData(localeFr)
registerLocaleData(localeHu)
registerLocaleData(localeId)
registerLocaleData(localeIt)
registerLocaleData(localeJa)
registerLocaleData(localeKo)

View File

@@ -13,6 +13,7 @@ from pikepdf import Page
from pikepdf import PasswordError
from pikepdf import Pdf
from documents.consumer import ConsumerPreflightPlugin
from documents.converters import convert_from_tiff_to_pdf
from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
@@ -186,9 +187,22 @@ class BarcodePlugin(ConsumeTaskPlugin):
# Update/overwrite an ASN if possible
# After splitting, as otherwise each split document gets the same ASN
if self.settings.barcode_enable_asn and (located_asn := self.asn) is not None:
if (
self.settings.barcode_enable_asn
and not self.metadata.skip_asn
and (located_asn := self.asn) is not None
):
logger.info(f"Found ASN in barcode: {located_asn}")
self.metadata.asn = located_asn
# (Re-)run the preflight ASN check
preflight_plugin = ConsumerPreflightPlugin(
input_doc=self.input_doc,
metadata=self.metadata,
status_mgr=self.status_mgr,
base_tmp_dir=self.base_tmp_dir,
task_id=self.task_id,
)
preflight_plugin.pre_check_asn_value()
def cleanup(self) -> None:
self.temp_dir.cleanup()

View File

@@ -433,6 +433,8 @@ def merge(
if user is not None:
overrides.owner_id = user.id
# Avoid copying or detecting ASN from merged PDFs to prevent collision
overrides.skip_asn = True
logger.info("Adding merged document to the task queue.")

View File

@@ -696,7 +696,7 @@ class ConsumerPlugin(
pk=self.metadata.storage_path_id,
)
if self.metadata.asn is not None:
if self.metadata.asn is not None and not self.metadata.skip_asn:
document.archive_serial_number = self.metadata.asn
if self.metadata.owner_id:
@@ -812,8 +812,8 @@ class ConsumerPreflightPlugin(
"""
Check that if override_asn is given, it is unique and within a valid range
"""
if self.metadata.asn is None:
# check not necessary in case no ASN gets set
if self.metadata.skip_asn or self.metadata.asn is None:
# if skip is set or ASN is None
return
# Validate the range is above zero and less than uint32_t max
# otherwise, Whoosh can't handle it in the index

View File

@@ -22,7 +22,7 @@ class DocumentMetadataOverrides:
document_type_id: int | None = None
tag_ids: list[int] | None = None
storage_path_id: int | None = None
created: datetime.datetime | None = None
created: datetime.date | None = None
asn: int | None = None
owner_id: int | None = None
view_users: list[int] | None = None
@@ -30,6 +30,7 @@ class DocumentMetadataOverrides:
change_users: list[int] | None = None
change_groups: list[int] | None = None
custom_fields: dict | None = None
skip_asn: bool = False
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
"""
@@ -49,6 +50,8 @@ class DocumentMetadataOverrides:
self.storage_path_id = other.storage_path_id
if other.owner_id is not None:
self.owner_id = other.owner_id
if other.skip_asn:
self.skip_asn = True
# merge
if self.tag_ids is None:
@@ -100,6 +103,7 @@ class DocumentMetadataOverrides:
overrides.storage_path_id = doc.storage_path.id if doc.storage_path else None
overrides.owner_id = doc.owner.id if doc.owner else None
overrides.tag_ids = list(doc.tags.values_list("id", flat=True))
overrides.created = doc.created
overrides.view_users = list(
get_users_with_perms(

View File

@@ -18,6 +18,8 @@ from django.core.exceptions import ValidationError
from django.core.validators import DecimalValidator
from django.core.validators import EmailValidator
from django.core.validators import MaxLengthValidator
from django.core.validators import MaxValueValidator
from django.core.validators import MinValueValidator
from django.core.validators import RegexValidator
from django.core.validators import integer_validator
from django.db.models import Count
@@ -875,6 +877,13 @@ class CustomFieldInstanceSerializer(serializers.ModelSerializer):
uri_validator(data["value"])
elif field.data_type == CustomField.FieldDataType.INT:
integer_validator(data["value"])
try:
value_int = int(data["value"])
except (TypeError, ValueError):
raise serializers.ValidationError("Enter a valid integer.")
# Keep values within the PostgreSQL integer range
MinValueValidator(-2147483648)(value_int)
MaxValueValidator(2147483647)(value_int)
elif (
field.data_type == CustomField.FieldDataType.MONETARY
and data["value"] != ""

View File

@@ -1664,6 +1664,44 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.consume_file_mock.assert_not_called()
def test_patch_document_integer_custom_field_out_of_range(self):
"""
GIVEN:
- An integer custom field
- A document
WHEN:
- Patching the document with an integer value exceeding PostgreSQL's range
THEN:
- HTTP 400 is returned (validation catches the overflow)
- No custom field instance is created
"""
cf_int = CustomField.objects.create(
name="intfield",
data_type=CustomField.FieldDataType.INT,
)
doc = Document.objects.create(
title="Doc",
checksum="123",
mime_type="application/pdf",
)
response = self.client.patch(
f"/api/documents/{doc.pk}/",
{
"custom_fields": [
{
"field": cf_int.pk,
"value": 2**31, # overflow for PostgreSQL integer fields
},
],
},
format="json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("custom_fields", response.data)
self.assertEqual(CustomFieldInstance.objects.count(), 0)
def test_upload_with_webui_source(self):
"""
GIVEN: A document with a source file

View File

@@ -11,6 +11,7 @@ from django.test import override_settings
from documents import tasks
from documents.barcodes import BarcodePlugin
from documents.consumer import ConsumerError
from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource
@@ -93,6 +94,41 @@ class TestBarcode(
self.assertDictEqual(separator_page_numbers, {1: False})
@override_settings(CONSUMER_ENABLE_ASN_BARCODE=True)
def test_asn_barcode_duplicate_in_trash_fails(self):
"""
GIVEN:
- A document with ASN barcode 123 is in the trash
WHEN:
- A file with the same barcode ASN is consumed
THEN:
- The ASN check is re-run and consumption fails
"""
test_file = self.BARCODE_SAMPLE_DIR / "barcode-39-asn-123.pdf"
first_doc = Document.objects.create(
title="First ASN 123",
content="",
checksum="asn123first",
mime_type="application/pdf",
archive_serial_number=123,
)
first_doc.delete()
dupe_asn = settings.SCRATCH_DIR / "barcode-39-asn-123-second.pdf"
shutil.copy(test_file, dupe_asn)
with mock.patch("documents.tasks.ProgressManager", DummyProgressManager):
with self.assertRaisesRegex(ConsumerError, r"ASN 123.*trash"):
tasks.consume_file(
ConsumableDocument(
source=DocumentSource.ConsumeFolder,
original_file=dupe_asn,
),
None,
)
@override_settings(
CONSUMER_BARCODE_TIFF_SUPPORT=True,
)

View File

@@ -581,7 +581,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
- Consume file should be called
"""
doc_ids = [self.doc1.id, self.doc2.id, self.doc3.id]
metadata_document_id = self.doc1.id
metadata_document_id = self.doc2.id
user = User.objects.create(username="test_user")
result = bulk_edit.merge(
@@ -602,11 +602,14 @@ class TestPDFActions(DirectoriesMixin, TestCase):
expected_filename,
)
self.assertEqual(consume_file_args[1].title, None)
self.assertTrue(consume_file_args[1].skip_asn)
# With metadata_document_id overrides
result = bulk_edit.merge(doc_ids, metadata_document_id=metadata_document_id)
consume_file_args, _ = mock_consume_file.call_args
self.assertEqual(consume_file_args[1].title, "A (merged)")
self.assertEqual(consume_file_args[1].title, "B (merged)")
self.assertEqual(consume_file_args[1].created, self.doc2.created)
self.assertTrue(consume_file_args[1].skip_asn)
self.assertEqual(result, "OK")
@@ -647,6 +650,7 @@ class TestPDFActions(DirectoriesMixin, TestCase):
expected_filename,
)
self.assertEqual(consume_file_args[1].title, None)
self.assertTrue(consume_file_args[1].skip_asn)
delete_documents_args, _ = mock_delete_documents.call_args
self.assertEqual(

View File

@@ -412,6 +412,14 @@ class TestConsumer(
self.assertEqual(document.archive_serial_number, 123)
self._assert_first_last_send_progress()
def testMetadataOverridesSkipAsnPropagation(self):
overrides = DocumentMetadataOverrides()
incoming = DocumentMetadataOverrides(skip_asn=True)
overrides.update(incoming)
self.assertTrue(overrides.skip_asn)
def testOverrideTitlePlaceholders(self):
c = Correspondent.objects.create(name="Correspondent Name")
dt = DocumentType.objects.create(name="DocType Name")

View File

@@ -708,6 +708,7 @@ class DocumentViewSet(
"title",
"correspondent__name",
"document_type__name",
"storage_path__name",
"created",
"modified",
"added",

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-12 17:41+0000\n"
"POT-Creation-Date: 2025-12-29 14:49+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -1219,35 +1219,35 @@ msgstr ""
msgid "workflow runs"
msgstr ""
#: documents/serialisers.py:640
#: documents/serialisers.py:642
msgid "Invalid color."
msgstr ""
#: documents/serialisers.py:1826
#: documents/serialisers.py:1835
#, python-format
msgid "File type %(type)s not supported"
msgstr ""
#: documents/serialisers.py:1870
#: documents/serialisers.py:1879
#, python-format
msgid "Custom field id must be an integer: %(id)s"
msgstr ""
#: documents/serialisers.py:1877
#: documents/serialisers.py:1886
#, python-format
msgid "Custom field with id %(id)s does not exist"
msgstr ""
#: documents/serialisers.py:1894 documents/serialisers.py:1904
#: documents/serialisers.py:1903 documents/serialisers.py:1913
msgid ""
"Custom fields must be a list of integers or an object mapping ids to values."
msgstr ""
#: documents/serialisers.py:1899
#: documents/serialisers.py:1908
msgid "Some custom fields don't exist or were specified twice."
msgstr ""
#: documents/serialisers.py:2014
#: documents/serialisers.py:2023
msgid "Invalid variable detected."
msgstr ""
@@ -1767,82 +1767,86 @@ msgid "Hungarian"
msgstr ""
#: paperless/settings.py:789
msgid "Italian"
msgid "Indonesian"
msgstr ""
#: paperless/settings.py:790
msgid "Japanese"
msgid "Italian"
msgstr ""
#: paperless/settings.py:791
msgid "Korean"
msgid "Japanese"
msgstr ""
#: paperless/settings.py:792
msgid "Luxembourgish"
msgid "Korean"
msgstr ""
#: paperless/settings.py:793
msgid "Norwegian"
msgid "Luxembourgish"
msgstr ""
#: paperless/settings.py:794
msgid "Dutch"
msgid "Norwegian"
msgstr ""
#: paperless/settings.py:795
msgid "Polish"
msgid "Dutch"
msgstr ""
#: paperless/settings.py:796
msgid "Portuguese (Brazil)"
msgid "Polish"
msgstr ""
#: paperless/settings.py:797
msgid "Portuguese"
msgid "Portuguese (Brazil)"
msgstr ""
#: paperless/settings.py:798
msgid "Romanian"
msgid "Portuguese"
msgstr ""
#: paperless/settings.py:799
msgid "Russian"
msgid "Romanian"
msgstr ""
#: paperless/settings.py:800
msgid "Slovak"
msgid "Russian"
msgstr ""
#: paperless/settings.py:801
msgid "Slovenian"
msgid "Slovak"
msgstr ""
#: paperless/settings.py:802
msgid "Serbian"
msgid "Slovenian"
msgstr ""
#: paperless/settings.py:803
msgid "Swedish"
msgid "Serbian"
msgstr ""
#: paperless/settings.py:804
msgid "Turkish"
msgid "Swedish"
msgstr ""
#: paperless/settings.py:805
msgid "Ukrainian"
msgid "Turkish"
msgstr ""
#: paperless/settings.py:806
msgid "Vietnamese"
msgid "Ukrainian"
msgstr ""
#: paperless/settings.py:807
msgid "Chinese Simplified"
msgid "Vietnamese"
msgstr ""
#: paperless/settings.py:808
msgid "Chinese Simplified"
msgstr ""
#: paperless/settings.py:809
msgid "Chinese Traditional"
msgstr ""

View File

@@ -786,6 +786,7 @@ LANGUAGES = [
("fi-fi", _("Finnish")),
("fr-fr", _("French")),
("hu-hu", _("Hungarian")),
("id-id", _("Indonesian")),
("it-it", _("Italian")),
("ja-jp", _("Japanese")),
("ko-kr", _("Korean")),

View File

@@ -1108,6 +1108,7 @@ class TestMail(
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages), 2)
self.assertEqual(len(self.mailMocker.bogus_mailbox.messages_spam), 1)
@pytest.mark.flaky(reruns=4)
def test_error_skip_rule(self):
account = MailAccount.objects.create(
name="test2",

149
uv.lock generated
View File

@@ -1086,83 +1086,86 @@ wheels = [
[[package]]
name = "granian"
version = "2.6.0"
version = "2.5.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/1e/0a33c4b68b054b9d5f7963371dd06978da5f4f58f58ddcb77854018abfdb/granian-2.6.0.tar.gz", hash = "sha256:d9b773633e411c7bf51590704e608e757dab09cd452fb18971a50a7d7c439677", size = 115955, upload-time = "2025-11-16T16:07:27.082Z" }
sdist = { url = "https://files.pythonhosted.org/packages/78/9b/6ac903de211e5874824e7349387c9e0467459dc1ad0cd960cb4196f38ae6/granian-2.5.4.tar.gz", hash = "sha256:85989a08052f1bbb174fd73759e1ae505e50b4c0690af366ca6ba844203dd463", size = 112016, upload-time = "2025-09-18T11:52:16.004Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/71/e543f91d1a01515ff7211a19e18ee7dcf843dc25655d6cc18039901e2fb1/granian-2.6.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:759e8be4481c5aede0080e3c20a9e1bc7c00258cd4810f88ebcfb6bdac298f03", size = 3078973, upload-time = "2025-11-16T16:05:30.886Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ae/ef87e76e5ade5633c11e892b663b922f8fda5ef804576373516a445d244f/granian-2.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6af5d9a088536798ee3188f1cbcffc5690ed38a53851825e4125c3bf3c9cfef3", size = 2810530, upload-time = "2025-11-16T16:05:32.703Z" },
{ url = "https://files.pythonhosted.org/packages/cf/9c/16a3ee4dad81e0dd446f391dad9ced17e7e685d97cce28188adb2e846004/granian-2.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50c1cad7b2b0fb7c66169a12ab069e2f76f4d2a7390638e5b327504372976518", size = 3331648, upload-time = "2025-11-16T16:05:34.15Z" },
{ url = "https://files.pythonhosted.org/packages/8b/27/c9325343522ed89ac6f885995178c95f90052a5894fc681ec84df24a3ba6/granian-2.6.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a811d0b80099fe1da945e6d137d64dfe8e1dd07d3bf20e2e1eeae6f2c83adbb9", size = 3151584, upload-time = "2025-11-16T16:05:35.584Z" },
{ url = "https://files.pythonhosted.org/packages/4f/73/376f08e3de394e50888bd9f8fa27be5dd60e1fd6cbbec3683f780ddaf5fc/granian-2.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c7b04e5520ec3d749e53da414ba0ccc7773d7b24e8049539d47a4171aa922a", size = 3375838, upload-time = "2025-11-16T16:05:37.425Z" },
{ url = "https://files.pythonhosted.org/packages/5b/9c/f2e32c826fc7fe0c65a6cf0ff0b4c459f71adc78f2721083ff50fa60c29a/granian-2.6.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:d1bbe669228ba475adfdbebbae962f958be3002c742370000b7f5d06f895cacb", size = 3234478, upload-time = "2025-11-16T16:05:38.664Z" },
{ url = "https://files.pythonhosted.org/packages/dc/09/70bb969fcd4b35a357c93490efc7cf97185b521c90fcf21c2483de49cce8/granian-2.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bdef48aab0846fd5c215acd1779328d067708859bbf44c4e9363daa51b8c98bd", size = 3300577, upload-time = "2025-11-16T16:05:39.792Z" },
{ url = "https://files.pythonhosted.org/packages/c7/94/1722f6bf1a64475e390595c0b7a1b0dff40a4279fc215cb3695be7fd5168/granian-2.6.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:8459a8b2335689ecb04b2ccba63cbcdf030c242a64ae77be68fb6e263984a150", size = 3475443, upload-time = "2025-11-16T16:05:41.374Z" },
{ url = "https://files.pythonhosted.org/packages/64/92/8a353cdb800b0c390b3c6d3bc0ab5a815221319bec65419a86a959e64acd/granian-2.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:dcde0783cb546304f0e20a1f99feb1a8795adfb0540c9278e5f9ef583edffb36", size = 3467863, upload-time = "2025-11-16T16:05:42.82Z" },
{ url = "https://files.pythonhosted.org/packages/3c/56/efb12bda35ce3d6ac89ec8a5b02036d17dfaec6bb2cab16f142dc9ee389f/granian-2.6.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:38029b6c25ac5a9f8a6975b65846eee23d9fa7b91089a3ff6d11770d089020f3", size = 3078748, upload-time = "2025-11-16T16:05:45.593Z" },
{ url = "https://files.pythonhosted.org/packages/5e/84/6d640c3439d532792a7668d66089df53d74ffb06455075b9db2a25fbb02d/granian-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:efd9c92bc5d245f10d6924847c25d7f20046c976a4817a87fd8476c22c222b16", size = 2810326, upload-time = "2025-11-16T16:05:47.085Z" },
{ url = "https://files.pythonhosted.org/packages/92/60/909057f8f21e2d6f196f8c9380a755d5453a493cd071afa7f04c9de83725/granian-2.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43e6a25d995206ba0a2fef65fea2789f36dde1006932ea2dcd9a096937c1afdd", size = 3331727, upload-time = "2025-11-16T16:05:48.245Z" },
{ url = "https://files.pythonhosted.org/packages/64/07/27701a5b9aa27873ce92730e80e5c0ad3e7fe80674ba1660996c1463c53a/granian-2.6.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7ac1be5c65fef4e04fb9860ca7c985b9c305f8468d03c8527f006c23100c83", size = 3151437, upload-time = "2025-11-16T16:05:49.413Z" },
{ url = "https://files.pythonhosted.org/packages/b6/1b/dfc6782dad69b02ab6d50a320b54b2e28c573954e0697a3f24a68f7aa3c9/granian-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318a7db03e771e2611a976a8c4ecc7ae39e43e2ebffd20a4c2371a71cdc5659c", size = 3375815, upload-time = "2025-11-16T16:05:50.497Z" },
{ url = "https://files.pythonhosted.org/packages/ad/ab/de57fcf406a9da5b28f83af71bd7b8e2fc944b786f95b01188b4f8c1c049/granian-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:cdb1ab7a0cedfa834c6e8e7c9e2530d80d6fd6f04076c2f6998629688f8ecb00", size = 3234158, upload-time = "2025-11-16T16:05:51.664Z" },
{ url = "https://files.pythonhosted.org/packages/a7/d0/a2d3a14bfce05f62f3ec10cb1c1609fcfe983e6ae929b1656bff8784812c/granian-2.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fd11a9275ad01c2d99a322c1d0c8af0ad162c541515ad1d55ef585fd321cd2b9", size = 3300040, upload-time = "2025-11-16T16:05:53.233Z" },
{ url = "https://files.pythonhosted.org/packages/db/e3/d9b58bacf40da8f937a8a04f2fbc61424f551d0589f3bd6eb0755b57c3be/granian-2.6.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:489b1e24b4360ecdaf08d404e13549d4377e77756d1911454abed9e0b559345a", size = 3475356, upload-time = "2025-11-16T16:05:54.459Z" },
{ url = "https://files.pythonhosted.org/packages/df/50/b45f53dea5ec3d9a94f720f4a0b3a7c2043a298151b52ac389db14025b61/granian-2.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ba9fb67931852cf9d8eee23d1adb78c0e3106bd4ad440cf3b37ce124b4380c", size = 3467883, upload-time = "2025-11-16T16:05:56.017Z" },
{ url = "https://files.pythonhosted.org/packages/ef/db/c7d10c2b61dd40014346af3274500b72654710cdfe400f37358c63481f28/granian-2.6.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b05b4fc5ce5855eb64a02b6e2c70b0d7e24632ee0d1193badfc0dace56688c11", size = 3076177, upload-time = "2025-11-16T16:05:58.824Z" },
{ url = "https://files.pythonhosted.org/packages/9c/54/095eb0cea6976f3aeaab434f9009521b4d50aa37f9efda54a70da5b465ec/granian-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b6aad6e7ded7a0a916119cd3ee28aa989e619074a6ca1ba3dc19cf5ad608832c", size = 2801793, upload-time = "2025-11-16T16:06:00.396Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f5/4177070ec6942b0467c0da59b53cf83ac5b939cfcdf687daeaebaef31299/granian-2.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e77509ad3a5654da1268db9d78d49357bf91ac2d3dcb1c58a00cda162d922a7", size = 3325958, upload-time = "2025-11-16T16:06:01.906Z" },
{ url = "https://files.pythonhosted.org/packages/ad/5a/973e77414882df01ef75801d4c7e51bc2796475c0e7d72356d4a8f7701a5/granian-2.6.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3a7cc82cdc5d0c7371d805f00866f51ece71bb0cb2e1f192b84834cf1a6844b", size = 3146873, upload-time = "2025-11-16T16:06:03.183Z" },
{ url = "https://files.pythonhosted.org/packages/d6/97/410127ee96129c8f0746935b7be6703ad6f31232e0c33edec30496596d26/granian-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbbce087a055eb64896b809a9a1f88161751815b112de4aa02ee4348f49cb73", size = 3387122, upload-time = "2025-11-16T16:06:05.194Z" },
{ url = "https://files.pythonhosted.org/packages/cf/37/36e74876d324fe6326af32a01607afc3f0f0fcb9e674092755da4146c40c/granian-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a7fa2728d32dfaf3b1b2bf5b0b7c6d23bb75eaf62bd08b71b61797d292381970", size = 3234994, upload-time = "2025-11-16T16:06:06.978Z" },
{ url = "https://files.pythonhosted.org/packages/bc/6e/5da9af1fdf7eeff9c7568f35171a0cdd63d73ab87a3deea560393b746d71/granian-2.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:70b3867c33e5c95d6eb722a5c8b847c36c670fc189821bf7aef9934e943c2574", size = 3303337, upload-time = "2025-11-16T16:06:08.263Z" },
{ url = "https://files.pythonhosted.org/packages/e2/ab/d133ed75e9940abc9bed56cb096709b8c4a1dfe6221e61d43bd23939afad/granian-2.6.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:7fb0448a292f2dda9c4130e394ac09ef1164713d873882fd3106ca6949ff0897", size = 3472100, upload-time = "2025-11-16T16:06:09.494Z" },
{ url = "https://files.pythonhosted.org/packages/0d/25/064406ade99fa7153e1a2b129f69af56cc1e50176a2fbec25911d9a121a9/granian-2.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a5bd3c59fe3a7acb22e434749ff2258606a93bc5848fa96332a6ed4c752f4dc8", size = 3480023, upload-time = "2025-11-16T16:06:10.718Z" },
{ url = "https://files.pythonhosted.org/packages/4b/b0/a7be659186bf9de644a5214c31ce337342170de71c5cb1e3ea61e1feeebe/granian-2.6.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:74f579e7295945119394dc05dd1565be1ac700f6f26c8271d8327dfabc95ec34", size = 3075590, upload-time = "2025-11-16T16:06:13.662Z" },
{ url = "https://files.pythonhosted.org/packages/5a/d8/eb55f3657d7c104f96f2d20bd459908842a954f4d95c5769c46bf485d656/granian-2.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4e0e925d016e3dc43ae5950021c9ea0e9ee2ef1334a76ba7fbb80cc9e17c044", size = 2801601, upload-time = "2025-11-16T16:06:14.908Z" },
{ url = "https://files.pythonhosted.org/packages/2a/a3/45c79b3b2388a066e05ae3af171cde13540467efb0ec6404a52c12fcc449/granian-2.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b568459abe4813e4968310312e26add3dab80c3ce5044b537ebfe464601fe9a", size = 3325246, upload-time = "2025-11-16T16:06:16.9Z" },
{ url = "https://files.pythonhosted.org/packages/2f/2c/570df011d8c53a59d945db1b8b6adedf04f43d92bfd72f4149ee60c3aeaf/granian-2.6.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0041ba59e4b89818db1772ea677bb619f5e3030060dcb6c57e8a17d72dc6210b", size = 3146313, upload-time = "2025-11-16T16:06:18.339Z" },
{ url = "https://files.pythonhosted.org/packages/a9/cd/8e9b183db4190fac1401eeab62669ebe35d962ba9b490c6deca421e3daa4/granian-2.6.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c032dca04171e4fbd54e587fe03aeef1825739d02ff3e3c49d578a8b5cc752c", size = 3386170, upload-time = "2025-11-16T16:06:19.946Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/9ccc0d04c1cefdb4bb42f671a0c27df4f68ba872a61edc7fc3bae6077ea9/granian-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d5686da7358fede8e9a1e1344310c6e3cb2c4d02a1aca52c31c990fe6b7d6281", size = 3235277, upload-time = "2025-11-16T16:06:21.754Z" },
{ url = "https://files.pythonhosted.org/packages/96/7d/a082bec08c1d54ce73dd237d6da0f35633cd5f2bfd1aec2f0a2590e6782a/granian-2.6.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:62c69bb23efe26a33ac39e4b6ca0237d84ed6d3bf47a5bb817e00a46c27369f2", size = 3302908, upload-time = "2025-11-16T16:06:22.988Z" },
{ url = "https://files.pythonhosted.org/packages/b1/2e/c8a53c92f0e98c4b36a24c03a4243b53410804f78f1876ca3ea497831381/granian-2.6.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:2ee5087e4b876f29dd1a11e9c2dd8d864ecb207278767a33bba60975260f225d", size = 3470938, upload-time = "2025-11-16T16:06:24.666Z" },
{ url = "https://files.pythonhosted.org/packages/7a/c7/0615d97cc666c6b5c1af24abbb08c6fd536a5f3c055fd09a3cd6b178283e/granian-2.6.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3627b69f391a769acfad4ae26bbfce03b50c31eb5fbea18ec0a44f37c89cf0fd", size = 3479291, upload-time = "2025-11-16T16:06:25.984Z" },
{ url = "https://files.pythonhosted.org/packages/08/2c/8256710307e32cc4aff58d730f3db9e87471121725adc92d700fa0190136/granian-2.6.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:c66877f2b2a1ad6046a228ee228ed4faa43dd4949fbe07f61d5c38ad57506e02", size = 3027712, upload-time = "2025-11-16T16:06:28.819Z" },
{ url = "https://files.pythonhosted.org/packages/63/88/bb3dc2a67f146d03ffd1b3d912c92795ecf52aa2b7ea1375735c522a5e6c/granian-2.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:919ccfe3273c6325c82ecb2e62b5af4d1b57fdc9858ce725b8223af2e1d6e2cd", size = 2753501, upload-time = "2025-11-16T16:06:30.267Z" },
{ url = "https://files.pythonhosted.org/packages/0d/6e/86cea4a4cd0c9adbae74d865468f298083fcefd4d9f8f8f21910078b069a/granian-2.6.0-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7d6368281f9f1bfde53a71f67570b70df773e01329f7a113b248de453e5991c1", size = 2966948, upload-time = "2025-11-16T16:06:31.932Z" },
{ url = "https://files.pythonhosted.org/packages/e0/01/092337f9aae6cb6fb66516894a3a39d723de9ab263d3a144511d07d2ccef/granian-2.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3d0b7dd32a630336120c9a12e7ba7ca4e415bebd22d9735b19df593e01ffa40", size = 3317466, upload-time = "2025-11-16T16:06:33.222Z" },
{ url = "https://files.pythonhosted.org/packages/b7/60/0d3635ef8f1f73789cb1779574493668a76675ef18115826a4a2dcb415d7/granian-2.6.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb18fca86ff560ea5a3bf9dc342245e388409844c257d1125ff9a988c81080b", size = 3273204, upload-time = "2025-11-16T16:06:34.513Z" },
{ url = "https://files.pythonhosted.org/packages/f3/26/09bc5016ae7faac0af40a07934d4d4d41f9e5bd7e97560aac957f7aa9605/granian-2.6.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3606f13ba2fd9afde1d879ef556afcccd17b55c57a9f6be8487626867fe94a20", size = 3107339, upload-time = "2025-11-16T16:06:36.121Z" },
{ url = "https://files.pythonhosted.org/packages/c6/cb/91a13e42965a3e20a4c7398c63843cac9ca1a1c36925bd3ff69e6c17775f/granian-2.6.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:ca8188119daba0a343d2736dd4ed4d8d71ca5c0ca016a3f93599710906aa036f", size = 3298057, upload-time = "2025-11-16T16:06:37.567Z" },
{ url = "https://files.pythonhosted.org/packages/23/8b/19bb0f679b74ddb58e1c6de2e4c85ba986b2040d7446fd7e5a498e5a67cf/granian-2.6.0-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:6ac9d479d4795ab9c7222829d220636250ee034d266ad89a9657b64fb6770b93", size = 3465623, upload-time = "2025-11-16T16:06:39.144Z" },
{ url = "https://files.pythonhosted.org/packages/41/25/4af1f3e0cfea237912d04d57e97193d350b06f93255bde16040780e75589/granian-2.6.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:b8cc3635676639c1c6fc336571e7cdd4d4f0be6e05c33ae06721a570b346ce21", size = 3476874, upload-time = "2025-11-16T16:06:40.868Z" },
{ url = "https://files.pythonhosted.org/packages/bb/53/9ed1a1f710a78eaad2897b9264bb6ae1190dc251af463b87be41f1963dfe/granian-2.6.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cba1d4ac5b101c41fa916fb1ca5d5c359892b63d1470a9587055605c68850df8", size = 3072924, upload-time = "2025-11-16T16:06:43.949Z" },
{ url = "https://files.pythonhosted.org/packages/b1/58/8fa09896c88937a95b92185a1377b09f7cd1b8ac1e0f06a251e02ce96164/granian-2.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2c829ece96a49d431c01553e0c020a675624798659c74e3d800867a415376fef", size = 2800675, upload-time = "2025-11-16T16:06:45.831Z" },
{ url = "https://files.pythonhosted.org/packages/4f/53/779e15fb6372cf00d2c66f392d754e0816bf0597e8346459c22bde9de219/granian-2.6.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:637153b653a85e1bb0cba2e10e321e2cbb1af1e9abab0feafd34eb612fe3fcdd", size = 3323029, upload-time = "2025-11-16T16:06:47.682Z" },
{ url = "https://files.pythonhosted.org/packages/32/ad/3af7388f51b4df3a781ecfc6f1ec18331ec74ea413fb2c62fe24c65e7935/granian-2.6.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6c09792ca3807059ef8e58a7c7bc5620586414f03ebd4bb2e6cd044044f0165", size = 3142617, upload-time = "2025-11-16T16:06:48.964Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4d/6a7766fd9fe09f3f887c2168d5607cc2eb2ee9fe5c9364a877942c05de41/granian-2.6.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79e2f68d99f454d1c51aacc86bed346693c074f27c51fb19b8afe5dc375e1b70", size = 3383669, upload-time = "2025-11-16T16:06:50.363Z" },
{ url = "https://files.pythonhosted.org/packages/b8/1c/b4bbdcd6bbe9c3290a2ac76eac3ae8916fdb38269f9f981e5b933ff02664/granian-2.6.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:995f7b496b16484c97e8da9f44ead66307d6f532135a9890b0d27c88b8232df3", size = 3233040, upload-time = "2025-11-16T16:06:52.787Z" },
{ url = "https://files.pythonhosted.org/packages/75/26/ca7afabab2b31101eabc78df26772bd679e0a2bc879c58e8fcbb9732d57e/granian-2.6.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6d5db194527d52183b2dc17be9d68c59647bc66459c01a960407d446aa686c98", size = 3302090, upload-time = "2025-11-16T16:06:54.112Z" },
{ url = "https://files.pythonhosted.org/packages/e8/3b/3e6992ac60f8d2e7f6eb5ae7845ba8f77d9373379e7d8ec7dbdfac89c00b/granian-2.6.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:c9fd40f3db242eece303ab4e9da4c7c398c865d628d58ff747680c54775ea9e4", size = 3469619, upload-time = "2025-11-16T16:06:55.488Z" },
{ url = "https://files.pythonhosted.org/packages/5d/96/8e78858630d7ca51751502c323f22841a56847db827a73d946a9303108c1/granian-2.6.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:99dfa23b29e6a4f8cc2ec9b55946746f94ce305e474defef5c3c0e496471821e", size = 3479330, upload-time = "2025-11-16T16:06:56.989Z" },
{ url = "https://files.pythonhosted.org/packages/5a/5c/5770f1270c2e59b7d27e25792ed62f3164b8b962ccf19b4a351429fd34fe/granian-2.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a8b356e8d96481c0fa67c2646a516b1f8985171995c0a40c5548352b75cae513", size = 3026090, upload-time = "2025-11-16T16:07:00.105Z" },
{ url = "https://files.pythonhosted.org/packages/28/89/85b40c55ddd270a31e047b368b4d82f32c0f6388511a0affcf6c8459821b/granian-2.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f026d7a2f1395b02cba2b59613edfd463d9ef43aae33b3c5e41f2ac8d0752507", size = 2752890, upload-time = "2025-11-16T16:07:01.507Z" },
{ url = "https://files.pythonhosted.org/packages/bf/4e/369700caefaad0526fc36d43510e9274f430a5bdeea54b97f907e2dd387d/granian-2.6.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:15c888605406c9b29b7da8e3afa0ce31dabad7d446cf42a2033d1f184e280ef3", size = 2965483, upload-time = "2025-11-16T16:07:02.836Z" },
{ url = "https://files.pythonhosted.org/packages/75/47/d6d95615b94a8bac94efca7a634cb3160fb7cd3235039e4d1708e0399453/granian-2.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9ce9ff8d4d9da73eb2e90c72eae986f823ab46b2c8c7ee091ec06e3c835a94e", size = 3313071, upload-time = "2025-11-16T16:07:04.128Z" },
{ url = "https://files.pythonhosted.org/packages/6a/76/f9098765797adfc142d503ee8a18fe137324558a028db6322753d88305d9/granian-2.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:681f44fa950b50721250536477b068315322c447f60b6a7018a9d61385202d67", size = 3271503, upload-time = "2025-11-16T16:07:05.482Z" },
{ url = "https://files.pythonhosted.org/packages/7f/f9/55be32f079af772054284aa917eb7bd77f1f8ba840f0773db9ac47279149/granian-2.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf23f25826e7c87c2cd9d984a358c14106d589febcd71af0f5392bb65fafb07a", size = 3106398, upload-time = "2025-11-16T16:07:07.396Z" },
{ url = "https://files.pythonhosted.org/packages/79/ab/e63f54a8432b2b877d83c5f2921a54791a420685002854dc7005bbd48817/granian-2.6.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:559985641cc1f0497f2c35d75126214f9cf9286ec6cea083fb1d0324712dbc47", size = 3296156, upload-time = "2025-11-16T16:07:08.858Z" },
{ url = "https://files.pythonhosted.org/packages/ef/3c/a37c038be10441a27cfde65a64c4406556ee64ab5deba4a782eaaa5ce7cf/granian-2.6.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:98718c08713d4afdf0e444f6236eeac6d01fdf08d0587f3c15da37fd12ee03f6", size = 3460301, upload-time = "2025-11-16T16:07:10.476Z" },
{ url = "https://files.pythonhosted.org/packages/44/05/bcc03661028df91808440f24ae9923cda4fc53938c6bb85a87e3d47540a5/granian-2.6.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c115726904be2186b1e621a2b4a292f8d0ccc4b0f41ac89dcbe4b50cbaa67414", size = 3474889, upload-time = "2025-11-16T16:07:11.873Z" },
{ url = "https://files.pythonhosted.org/packages/d5/ee/88767d70d21e6c35e44b40176abd25e1adb8f93103b0abc6035c580a52aa/granian-2.6.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:323c096c7ebac19a16306708b4ed6abc9e57be572f0b9ff5dc65532be76f5d59", size = 3089586, upload-time = "2025-11-16T16:07:14.65Z" },
{ url = "https://files.pythonhosted.org/packages/5c/22/2405b36c01b5c32fc4bbc622f7c30b89a4ec9162cc3408a38c41d03e1c27/granian-2.6.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b9736ab48a1b3d70152e495374d4c5b61e90ea2a79f1fc19889f8bba6c68b3b5", size = 2805061, upload-time = "2025-11-16T16:07:16.582Z" },
{ url = "https://files.pythonhosted.org/packages/33/38/79e13366729f0f2561b33abef7deb326860443abbbb1d2247679feaeebdc/granian-2.6.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee4b1f5f7ec7096bdffc98171b559cb703c0be68e1c49ff59c208b90870c6bba", size = 3381989, upload-time = "2025-11-16T16:07:17.906Z" },
{ url = "https://files.pythonhosted.org/packages/90/9f/fcff1978ca3cbf138291a29fe09f2af5d939cab9e5f77acc49510092c0d8/granian-2.6.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e1e27e9527cdcd8e767c52e091e69ade0082c9868107164e32331a9bf9ead621", size = 3237042, upload-time = "2025-11-16T16:07:19.381Z" },
{ url = "https://files.pythonhosted.org/packages/a1/9d/06dc6b5f411cac8d6a6ef4824dc102b1818173027ab4293e4ae57c620cfe/granian-2.6.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f9f9c5384f9370179d849c876c35da82f0ebd7389d04a3923c094a9e4e80afc5", size = 3316073, upload-time = "2025-11-16T16:07:20.95Z" },
{ url = "https://files.pythonhosted.org/packages/1c/e1/45e9861df695c743b57738d9b8c15b3c98ebd34ba16a79884372b2006b32/granian-2.6.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:561a3b86523a0b0e5d636229e3f0dcd80118ace2b1d2d061ecddeba0044ae8ac", size = 3483622, upload-time = "2025-11-16T16:07:22.567Z" },
{ url = "https://files.pythonhosted.org/packages/5f/14/cfe0648b2e1779ed2a2215a97de9acc74f94941bb60c6f2c9fb7061ae4bb/granian-2.6.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:8e12a70bdb3b5845f62dc2013527d5b150b6a4bc484f2dec555e6d27f4852e59", size = 3460175, upload-time = "2025-11-16T16:07:24.327Z" },
{ url = "https://files.pythonhosted.org/packages/e3/a0/b6782563716dfd178f094fe7fe6d28fc6c13857926bb9efac6ddc73dec54/granian-2.5.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:907d17f94a039b1047a82386b4979a6a7db7f4c37598225c6184a2b89f0ae12d", size = 2860831, upload-time = "2025-09-18T11:49:41.521Z" },
{ url = "https://files.pythonhosted.org/packages/a9/a5/6ae10379f21415255dd36b4d26a69a0a8ec80d4ba4fe26ca563e46a1ca62/granian-2.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a009e99d3c4a2a70a15a97391566753045a81641e5a3e651ff346d8bb7fe7450", size = 2550345, upload-time = "2025-09-18T11:49:43.015Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ca/1cdbd669ee4bf85208b96e0bcaf5b51cba67907b71679c18a1da6bea61e6/granian-2.5.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cb602ac3ea567476c339e8683a0fa2ffe7fd8432798bd63c371d5b32502bdb9", size = 3048013, upload-time = "2025-09-18T11:49:44.538Z" },
{ url = "https://files.pythonhosted.org/packages/cf/9e/aba367c3c372d641e78aaaaa4ec8a4452bb8a2259bdb8b7484d537969864/granian-2.5.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52aee85459304f1e74ff4cb5bb60d23db267b671b1199ff589b1a5a65f5638f", size = 2862464, upload-time = "2025-09-18T11:49:45.976Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/542ef36aee53a21ff868a38c4d567eb253b1338501091e36b6ad8090c862/granian-2.5.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8478d777971d6c1601b479c70a5c1aaaba7b656fa5044b1c38b4ba5e172f0fc7", size = 3147423, upload-time = "2025-09-18T11:49:47.266Z" },
{ url = "https://files.pythonhosted.org/packages/e2/a3/1ba8d0d534ab993e1f84eab3320b4e3071e9bec166131e663c60160e5192/granian-2.5.4-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:1da588e951b3e0bce94f2743158750c9733efcbe5c27b31f50e9bda6af8aac1f", size = 2914051, upload-time = "2025-09-18T11:49:48.616Z" },
{ url = "https://files.pythonhosted.org/packages/4b/1e/0e43ee8a4a97c4b2a413964448917cabe154aabc99be46ae0f487ce094e8/granian-2.5.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79db7d0eac7445a383e22b6d3e9323882bc9a9c1d2fd62097c0452822c4de526", size = 2919482, upload-time = "2025-09-18T11:49:49.789Z" },
{ url = "https://files.pythonhosted.org/packages/c7/74/6857f59e1ae9a556b7293c60258963063d74ad154c0411f94dc235aa02ab/granian-2.5.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:cc75f15876415054c094e9ef941cf49c315ee5f0f20701fdfb3ffc698054c727", size = 3157058, upload-time = "2025-09-18T11:49:51.406Z" },
{ url = "https://files.pythonhosted.org/packages/02/e9/05eaa62200693b31a75e3767f5716a55aeb07572f1a41e2d31b6f8bb115d/granian-2.5.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2caeee9d12144c9c285d3074c7979cdf1ad3d84a86204dec9035ca6cec5d713f", size = 3194238, upload-time = "2025-09-18T11:49:52.575Z" },
{ url = "https://files.pythonhosted.org/packages/a9/a3/89471ae2ff6d3111964ef9e0b8ac00c5a68046aca93965048b94ec0ec952/granian-2.5.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a404bff75dc29c01566a4e896237f6cb8eda49a71b90770b8316ebe1b08a3d46", size = 2861005, upload-time = "2025-09-18T11:49:55.442Z" },
{ url = "https://files.pythonhosted.org/packages/85/90/81706bbe0f23737c3c1cf8a4e76a6e2c9ec9c5a950a023aea2aed6ef74c4/granian-2.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d91b4642283ea8169aad64b74b242c364a3ce24d6aeed9b5a4358f99a5ab4d84", size = 2550393, upload-time = "2025-09-18T11:49:57.24Z" },
{ url = "https://files.pythonhosted.org/packages/66/67/7bdd9b1b63c811439ac7b8b4e52112abb5a38b575f033b9c7672d0355a70/granian-2.5.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aa6b4ad4d479fe3e7d42ca4321ae7febad9cdae5c269032234b8b4ac8dbd017", size = 3048134, upload-time = "2025-09-18T11:49:59.263Z" },
{ url = "https://files.pythonhosted.org/packages/39/94/9bd7e8248c438ac7861653ea4fb071a2ed2dcb4b7a1ec5cf034282d4079a/granian-2.5.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2466523c14724d2d68497cd081ffd2aa913381be199e7eb71347847a3651224c", size = 2862817, upload-time = "2025-09-18T11:50:00.932Z" },
{ url = "https://files.pythonhosted.org/packages/c1/f7/c98302718f58a3ee47ab1db83e8c0834b91016fc3c83f3a23f7b256a02a7/granian-2.5.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce9ec6baebb83ba7d1ed507dc7d301f7f29725f9b7a8c9c974f96479dea3a090", size = 3147500, upload-time = "2025-09-18T11:50:02.569Z" },
{ url = "https://files.pythonhosted.org/packages/4d/c0/eb3b94d2eb40d5b94bfad94da018f5daf539e09e8fc33742a8330707913a/granian-2.5.4-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:8b3faa2eec6dbbb072aae575d6a6a5e5577aef13c93d38d454a6a9fffc954ce7", size = 2914076, upload-time = "2025-09-18T11:50:04.074Z" },
{ url = "https://files.pythonhosted.org/packages/0f/77/42e06595c441c14f934858351acacc28fb2552798ab7eefed2e0e3920d15/granian-2.5.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:25a1d03fc93184009d5e76a5bfb5b29222e7debacfc225dd5d3732f6f6f99c10", size = 2919154, upload-time = "2025-09-18T11:50:05.497Z" },
{ url = "https://files.pythonhosted.org/packages/c4/af/1f87d4bfabf09d16dcf3a355cc356daa33185c561a5f3c5904f6fd0f0e5f/granian-2.5.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:1e580f5fa30ed04e724c71de099dcacc4722613f8a53e41454bac86242887da7", size = 3157119, upload-time = "2025-09-18T11:50:07.088Z" },
{ url = "https://files.pythonhosted.org/packages/ab/34/18b038e4b67a97eb776bf84307a0d5c8bff79290024973def614d8052596/granian-2.5.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5a4e74bf6d91dd7df6ffc7edb74e74147057fc947c04684d2d9af03e5e71ad71", size = 3193906, upload-time = "2025-09-18T11:50:08.305Z" },
{ url = "https://files.pythonhosted.org/packages/ea/9f/2a419461f2696bd95ba6b4d2a1c09b7372b79e66ac3b7dd4a985bf35f7d6/granian-2.5.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c4387cca4e38ec7579cac71a2da27fd177ced682b1de8bf917b9610f2ac0ba5e", size = 2846208, upload-time = "2025-09-18T11:50:10.934Z" },
{ url = "https://files.pythonhosted.org/packages/0a/bd/f9b9f57e14f778665e5b56a5b98d20187136517188a39ac404b13812bb34/granian-2.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a126b75927583292a9d2cfae627cd4b1da3e68d04dd87ba5a53b4860768d9e04", size = 2537995, upload-time = "2025-09-18T11:50:12.461Z" },
{ url = "https://files.pythonhosted.org/packages/55/eb/4df4fd10fb0ca0aa7ccbbe6b805e8019dc83d3a7861a8e0ec73a4f671bcf/granian-2.5.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b44dc391bf9bc1303bcb2cb344bbb5c35de92f43a3e8584f2a984dfda2fea8e3", size = 3033917, upload-time = "2025-09-18T11:50:16.535Z" },
{ url = "https://files.pythonhosted.org/packages/1a/ad/a3b8a773ee347f1a9b52d37caeb373eff590363c478cd6d9d20422a842de/granian-2.5.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07c47847163a1bcce0b7c323093b20be8a8ec9d4f4eba596b4d27f85ddbe669f", size = 2860524, upload-time = "2025-09-18T11:50:17.811Z" },
{ url = "https://files.pythonhosted.org/packages/f3/ae/188342234ed4f842ad63fd6a0328a05a8e2b991293496527b30654b6710c/granian-2.5.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c50539f654ce5f8fadd9b360fac0361d812c39c7a5f1e525889c51899a10f0", size = 3139768, upload-time = "2025-09-18T11:50:19.006Z" },
{ url = "https://files.pythonhosted.org/packages/28/40/babaaf6b95bf690cf3af1ff0c3a1d9c33b23f2c18dabb373c805653834bd/granian-2.5.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e52f65acd4da0a3af7a5d2d6a6445d530e84fe52057ee39f52ce92a6598fe37b", size = 2915038, upload-time = "2025-09-18T11:50:20.194Z" },
{ url = "https://files.pythonhosted.org/packages/a2/01/3d2eda00cbaa09f5a734d57fb4f52f68a1a48137a262e46d59ffecb54bd6/granian-2.5.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b78ab23495e384521085c33fecb3987779e1b1e43f34acd5b25e864b699933f9", size = 2915368, upload-time = "2025-09-18T11:50:21.335Z" },
{ url = "https://files.pythonhosted.org/packages/8a/3c/51670c8d83334ea1ce1b54aae93f6066ff101ca81ccd6ab01832309b2156/granian-2.5.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:6a477b204fca30218b3cc16721df38f1e159c5ee27252b305c78982af1995974", size = 3142893, upload-time = "2025-09-18T11:50:22.843Z" },
{ url = "https://files.pythonhosted.org/packages/68/42/81f848d9cf6cd77f24d0625d5c49caf6471477c97a760827d41cbb90a214/granian-2.5.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7f58116ab1300ca744a861482ce3194e9be5f1adad9ac4adda89d47b1ba3fa50", size = 3206519, upload-time = "2025-09-18T11:50:24.124Z" },
{ url = "https://files.pythonhosted.org/packages/50/aa/ed7cee53c0663fbb4d64b9a143d176f76000100f8a5ccef8a166df2bb9a9/granian-2.5.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:533bf842b56c8531705048211e3152fb1234d7611f83257a71cbf7e734c0f4a1", size = 2845936, upload-time = "2025-09-18T11:50:27.394Z" },
{ url = "https://files.pythonhosted.org/packages/04/a3/a63b04b67fb44578ce5d2d6c2b669932d6adb9a4844d86dc0f0cd0adb409/granian-2.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1efb111f84236a72d5864d64a0198e04e699014119c33d957fac34a0efb2474", size = 2537660, upload-time = "2025-09-18T11:50:28.953Z" },
{ url = "https://files.pythonhosted.org/packages/a6/99/4c630712f95ce6105f070631242fbafe4c045a00cd5e00f437a03085a9cc/granian-2.5.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0341a553fe913b4a741c10f532f5315d57deaa34877494d4c4b09c666f5266c", size = 3033766, upload-time = "2025-09-18T11:50:30.515Z" },
{ url = "https://files.pythonhosted.org/packages/44/73/d05f0cd49764feedfc91b418e060c212a35077154209e22654891c08d2dc/granian-2.5.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b3b24b7620df45752bbf34f93250f733a6996a7409879efbea6ab38f57eff69", size = 2860740, upload-time = "2025-09-18T11:50:31.799Z" },
{ url = "https://files.pythonhosted.org/packages/6e/85/061748715e5c213c8f5e58c9a7f95741fc5787a0c45c7b066b1df0f59453/granian-2.5.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bb902636271f9e61f6653625670982f7a0e19cbc7ae56fc65cd26bf330c612f", size = 3139932, upload-time = "2025-09-18T11:50:33.07Z" },
{ url = "https://files.pythonhosted.org/packages/7e/5b/f361708fd275763f1436bc1584bf3171ea5b6b12255e7cf3d5a2d7280546/granian-2.5.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:23b2e86ea97320bbe80866a94e6855752f0c73c0ec07772e0241e8409384cde5", size = 2914531, upload-time = "2025-09-18T11:50:34.364Z" },
{ url = "https://files.pythonhosted.org/packages/0d/84/9f1dbcb6a2228fd9bbf548d81f17225ae26dbbcef33f61d7e14971d78d2f/granian-2.5.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:328ed82315ccbd5dedc8f5318a1f80d49e07eb34ebc0753bc2930d2f30070a34", size = 2915108, upload-time = "2025-09-18T11:50:35.667Z" },
{ url = "https://files.pythonhosted.org/packages/77/10/13e04a2ef44f711474ca68a095e65f72fb6ced28f5c0930980a012386f8b/granian-2.5.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9bd438bb41cbac25f5f3155924947f0e2594b69f8a5f78e43c453c35fa28a1f0", size = 3142589, upload-time = "2025-09-18T11:50:37.349Z" },
{ url = "https://files.pythonhosted.org/packages/43/a5/826dab4703261d0f500b5d1d0cc8a965157856b0266ca3679185d316d5f7/granian-2.5.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d6d1b462ccb57af3051def8eae13f1df81dc902e9deff3cc6dfbb692c40a5a1f", size = 3206072, upload-time = "2025-09-18T11:50:38.602Z" },
{ url = "https://files.pythonhosted.org/packages/d5/40/a9d9e976bfbd6274d1fe052676fb02c055c1476a9936961218a211785cef/granian-2.5.4-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d04a1432ed98b7e4b4e5cff188819f34bd680785352237477d7886eb9305f692", size = 2763126, upload-time = "2025-09-18T11:50:41.404Z" },
{ url = "https://files.pythonhosted.org/packages/ad/84/0a302005e3c1c254c592d29aaa11a281ef4a84dfab45ccfe3072223f9c5b/granian-2.5.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c6309d729f1b022b09fd051a277096a732bd8ed39986ac4b9849f6e79b660880", size = 2479158, upload-time = "2025-09-18T11:50:42.767Z" },
{ url = "https://files.pythonhosted.org/packages/74/9f/a889cc30f2ee67acdcfccd10c3da239c828c584cb9e7f04e54717ff0c42b/granian-2.5.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a067c27e733b0851e6f66175c1aac8badda60b698457181881c08a4e17baecf", size = 3016245, upload-time = "2025-09-18T11:50:44.3Z" },
{ url = "https://files.pythonhosted.org/packages/b7/37/22a86b369b140b3684f8aecfd9c80ed2765421c09cb3d3ef06245a6aaaf8/granian-2.5.4-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:54bd0604db172a964b1bc4b8709329b7f4e9cff6b8f468104ca7603a5e61d529", size = 2795566, upload-time = "2025-09-18T11:50:45.626Z" },
{ url = "https://files.pythonhosted.org/packages/c3/cc/65073d6f08d9251ceaa5eb9a485e1a8fda9cfb4bac3c03c9f28e01abf416/granian-2.5.4-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:487bdc40b234ef84510eac1d63f0720ca92daca08feb7d2d98a1f0a84cc54b0e", size = 2909489, upload-time = "2025-09-18T11:50:48.98Z" },
{ url = "https://files.pythonhosted.org/packages/d9/76/c52fdfcd536584ed01a878e13139e338083fac4fde32d6fbfb0f3b29f380/granian-2.5.4-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:76cd55ab521cc501a977f50ace9d72a6a4f9a6849e6490b14af2e9acc614ce55", size = 3124827, upload-time = "2025-09-18T11:50:50.45Z" },
{ url = "https://files.pythonhosted.org/packages/f3/fc/4d93870feebc547bbc68dcbfa3a6c491107a6ac634636c7e332441ef9077/granian-2.5.4-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:27b3e7906916ad6e84a8b16d89517d8652bece62888cb9421597eb14767a9d92", size = 3193471, upload-time = "2025-09-18T11:50:52.493Z" },
{ url = "https://files.pythonhosted.org/packages/aa/32/d1776652352df81c420e61a1d79711c9992ba6dcd1a419b1c9df83c925ce/granian-2.5.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c237db56e5ff3fdad6539a3fbfcb9b57ce71463db55a016ba08296828043112f", size = 2827836, upload-time = "2025-09-18T11:50:55.444Z" },
{ url = "https://files.pythonhosted.org/packages/73/60/0f7b994feaca68c2585fa96338369fc8f928281cb1f7da2a377ec698f6a2/granian-2.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a76d7033a8d68c8353293fae8365e3b649bb155ab39af14387f3e9e870d503fb", size = 2525297, upload-time = "2025-09-18T11:50:56.765Z" },
{ url = "https://files.pythonhosted.org/packages/ff/06/73580eef85ac4ac4da7205d320e449a46db0e613b6df706a5feb46809e96/granian-2.5.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6778b9f7ecef8a423dd203aa5b0644a18d53eb749e830b2fe33abecad5d7e84", size = 3028201, upload-time = "2025-09-18T11:50:58.047Z" },
{ url = "https://files.pythonhosted.org/packages/19/78/5c0b43af33b18bd528b10ff770aaeb158a4fbeb0ac5cc0371921759b6b59/granian-2.5.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3accc02c981436e783772b12ea8cead35e8e644437881d7da842ff474f8e30f9", size = 2852319, upload-time = "2025-09-18T11:50:59.573Z" },
{ url = "https://files.pythonhosted.org/packages/ff/ae/ad9cc1729d12b9699318c66015999efef14c7fd17836393a09a1bbbf5185/granian-2.5.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3eaaf38851308af616ad5fdc35f333857f128493072ea208c1bb2fb557dcf2e", size = 3135211, upload-time = "2025-09-18T11:51:01.022Z" },
{ url = "https://files.pythonhosted.org/packages/7d/41/cbda436041e46116647d804d2eada3fab144883b0e6ce75f3d967a116c6c/granian-2.5.4-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:3cad64e8e41d6f3daf3e7a3eea88023aa7e64ee81450443ac9f4e6cae005079d", size = 2914810, upload-time = "2025-09-18T11:51:02.36Z" },
{ url = "https://files.pythonhosted.org/packages/9f/28/ab53dcab1d55636eca417a4263114d24872958609ceb853b84887b12cc38/granian-2.5.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4bb60b122971326d0d1baf10336c67bdecdd7adc708cf0b09bf1cde5563e8f5", size = 2914603, upload-time = "2025-09-18T11:51:03.701Z" },
{ url = "https://files.pythonhosted.org/packages/a0/33/e8810b11004e9139b6dd71dfa4a1a56ae60331e8d72dfa8bc7121158abff/granian-2.5.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:14129339f0ed9bbd2d129f82ed16e0c330edca7300482bd53cef466cc7b3ec6d", size = 3137585, upload-time = "2025-09-18T11:51:04.921Z" },
{ url = "https://files.pythonhosted.org/packages/17/4c/78650e1d54a9a8460add62643a57a0042880592e5a6f5574370954d7e91d/granian-2.5.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:294c574fcd8005587671476b751e5853b262ecb1b1011e634ac160d6a0259abd", size = 3195738, upload-time = "2025-09-18T11:51:06.328Z" },
{ url = "https://files.pythonhosted.org/packages/d8/d0/cdab820b2f5c692dc4c67879f937fb223eae1599784755362a53872f512c/granian-2.5.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3f0940963f6c6e50de19195014902d9a565a612aa0583082c9082bd83f259446", size = 2747894, upload-time = "2025-09-18T11:51:09.927Z" },
{ url = "https://files.pythonhosted.org/packages/71/15/1d27cd429a4fb0cb066848ca7ba432e5887b2506873832136444a6aa24d2/granian-2.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bc27bff6ea5a80fd5bf28297ac53fa31771cbdfa35650a6eb4f2c4efc751097d", size = 2463984, upload-time = "2025-09-18T11:51:11.294Z" },
{ url = "https://files.pythonhosted.org/packages/18/c0/2f3594892055c465c0035d5fc174da95c01a4902f9c63e7ea05d49fad892/granian-2.5.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73c136bac23cd1d2c969a52374972ec7da6e0920768bf0bcce65e00cabb4ebb9", size = 3009713, upload-time = "2025-09-18T11:51:12.713Z" },
{ url = "https://files.pythonhosted.org/packages/31/e7/09e44ece7ebcd5a70cd313ca3a0fa5b21632697c20412ddf169c51f16894/granian-2.5.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2dc03e2f375354b95d477c9455fb2fb427a922740f45e036cdf60da660adbf13", size = 2794570, upload-time = "2025-09-18T11:51:14.015Z" },
{ url = "https://files.pythonhosted.org/packages/e4/91/e76301bf0411ede4739b273c972f959c4675702418b00228ed7c278aac05/granian-2.5.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:539ee12b02281929358349e01a0c42c0594ebcf4f44033c8a4d7a446f837e034", size = 2909702, upload-time = "2025-09-18T11:51:16.252Z" },
{ url = "https://files.pythonhosted.org/packages/4a/6e/928b37a9a8a863339f5e37d4154de6faf1b8c58c8684799e117caf66b3a4/granian-2.5.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:97735bdbc2877583ea1c8dbfca31bcaf118a6e818afe6000eb8a9d09fd9d07e0", size = 3118746, upload-time = "2025-09-18T11:51:17.928Z" },
{ url = "https://files.pythonhosted.org/packages/94/7c/5c24c3d17ae24eee4af34bc46ecf6d9fc5d3a23b7b974c7ef3ff0f51cea9/granian-2.5.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:5f642a4fa1d41943d288db714bd1e0d218537bfa8bc6355d7063e8959b84c32b", size = 3187333, upload-time = "2025-09-18T11:51:19.427Z" },
{ url = "https://files.pythonhosted.org/packages/7b/7f/37896fc4180fd0f59146e3bd88e21fb71ce28c705d482540cfc8fc532cc9/granian-2.5.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b970a50230ae437615d754e1bc4aaa740fbe3f1418cc0c8933b260a691bb8f5", size = 2853053, upload-time = "2025-09-18T11:51:37.959Z" },
{ url = "https://files.pythonhosted.org/packages/a2/f4/fb23d1958b52a1546f25da03c459e11ea634d166dc7ef6024c7662c949fc/granian-2.5.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a09d2bef7805f10093aa356d976fdb3607d274252ef9429c6c1a24d239032c29", size = 2553878, upload-time = "2025-09-18T11:51:39.251Z" },
{ url = "https://files.pythonhosted.org/packages/54/43/e105894f0ae2711080c827f71d04d63ec7b4b881e0c4ae8a22a41500c800/granian-2.5.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6d5bd05c6e833d54b77c2ee19130cfa5d54ae4eb301ffca56744f712c4a9d03", size = 3139712, upload-time = "2025-09-18T11:51:40.999Z" },
{ url = "https://files.pythonhosted.org/packages/b1/ae/d1df545edeafbfd787c73863e47b269ff5646ec68b025db692a1c6aafbd5/granian-2.5.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7782a25ab78a55b61cb9b06f8aac471e9fafa3e1c20d6cdf970e93c834f6ddf", size = 2919335, upload-time = "2025-09-18T11:51:42.616Z" },
{ url = "https://files.pythonhosted.org/packages/6f/95/c55889554ef4471b786853707c3fa68f8ef4fafa3257054bd6990042db1f/granian-2.5.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4403600ac0273d4169c4c73773f57c5a3b44cc8aa8384a2f468c98c4294a3f27", size = 2927449, upload-time = "2025-09-18T11:51:44.273Z" },
{ url = "https://files.pythonhosted.org/packages/0b/52/34d3e17753c20ea56b88e48778e8c2b6fa01593e6febc0123bb6edfae7d7/granian-2.5.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c711335619a6936728b7773052fb0ec9d612b19abb2c786972ce3efee836df9d", size = 3172721, upload-time = "2025-09-18T11:51:45.76Z" },
{ url = "https://files.pythonhosted.org/packages/d2/b0/0323368449a05b2693ebbbbd4f61bcaef858a3df0255b6ee3dd460112549/granian-2.5.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ff9996929a16a72a61fb1f03a9e79e830bf7a6a4e9eb0470c6ef03f67d5ea5c0", size = 3183851, upload-time = "2025-09-18T11:51:47.651Z" },
{ url = "https://files.pythonhosted.org/packages/6a/69/e4c77826239bf3d612dced6a3a0007ef378bb454d602d5751a16c8b74d2f/granian-2.5.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f3fcf2e6c8a150f48e4b77271b33ebfc7c2d8381e692b5177d4bd7fcefbb691d", size = 2853059, upload-time = "2025-09-18T11:51:52.136Z" },
{ url = "https://files.pythonhosted.org/packages/6f/74/4ac398a54f718db5fd13e92ed1198a76c7c332dee4ab7af9c06ded51c884/granian-2.5.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:c5550d533a61053fc4d95044bdc80ba00118ca312ed85867ed63163fa5878f85", size = 2553706, upload-time = "2025-09-18T11:51:53.714Z" },
{ url = "https://files.pythonhosted.org/packages/32/10/871e8d09eae976613b3b0812eb927d39e7960fecf1ac885e21962bbc8ff3/granian-2.5.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48356114113ac3d48f70ea05cf42e560384c93318f5ef8f5638cb666f8243f2b", size = 3139539, upload-time = "2025-09-18T11:51:55.133Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f6/dd0ef6a7f3f4fea28b57014bc69b925995c7c6006451653b115440bf432b/granian-2.5.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cbadc8e49f90716b8f8aa2c2cee7a2e82c5a37dab5f6fbd449e76185ce205715", size = 2919295, upload-time = "2025-09-18T11:51:56.496Z" },
{ url = "https://files.pythonhosted.org/packages/27/dd/3a8f99363b05d33049ea8cd94e0d40792aebddfa39f6f14120b71613fdca/granian-2.5.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d1712a8ede152c2648557de6a23dbeb05ed499bfd83c42dad0689d9f2ba0621d", size = 2927578, upload-time = "2025-09-18T11:51:58.081Z" },
{ url = "https://files.pythonhosted.org/packages/49/42/ecb762dbf7a1447a89b4884907d07fa505fd89f904c1e2d3bd4f30aeb9d1/granian-2.5.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b78b8a6f30d0534b2db3f9cb99702d06be110b6e91f5639837a6f52f4891fc1d", size = 3172980, upload-time = "2025-09-18T11:51:59.428Z" },
{ url = "https://files.pythonhosted.org/packages/4c/2f/2abc969b206033d789c87d0ed7ee17a8831b0740e30590348abb544dba13/granian-2.5.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:10ae5ce26b1048888ac5aa3747415d8be8bbb014106b27ef0e77d23d2e00c51d", size = 3183479, upload-time = "2025-09-18T11:52:01.133Z" },
]
[package.optional-dependencies]
@@ -2280,7 +2283,7 @@ requires-dist = [
{ name = "filelock", specifier = "~=3.20.0" },
{ name = "flower", specifier = "~=2.0.1" },
{ name = "gotenberg-client", specifier = "~=0.12.0" },
{ name = "granian", extras = ["uvloop"], marker = "extra == 'webserver'", specifier = "~=2.6.0" },
{ name = "granian", extras = ["uvloop"], marker = "extra == 'webserver'", specifier = "~=2.5.1" },
{ name = "httpx-oauth", specifier = "~=0.16" },
{ name = "imap-tools", specifier = "~=1.11.0" },
{ name = "inotifyrecursive", specifier = "~=0.3" },