Compare commits

..

1 Commits

Author SHA1 Message Date
shamoon
1b6a9d3816 Add info buttons for core metadata items 2025-08-04 23:45:50 -04:00
15 changed files with 359 additions and 383 deletions

View File

@@ -63,7 +63,7 @@ dependencies = [
"redis[hiredis]~=5.2.1", "redis[hiredis]~=5.2.1",
"scikit-learn~=1.7.0", "scikit-learn~=1.7.0",
"setproctitle~=1.3.4", "setproctitle~=1.3.4",
"tika-client~=0.10.0", "tika-client~=0.9.0",
"tqdm~=4.67.1", "tqdm~=4.67.1",
"watchdog~=6.0", "watchdog~=6.0",
"whitenoise~=6.9", "whitenoise~=6.9",
@@ -81,7 +81,7 @@ optional-dependencies.postgres = [
"psycopg-pool==3.2.6", "psycopg-pool==3.2.6",
] ]
optional-dependencies.webserver = [ optional-dependencies.webserver = [
"granian[uvloop]~=2.5.0", "granian[uvloop]~=2.4.1",
] ]
[dependency-groups] [dependency-groups]
@@ -204,9 +204,15 @@ lint.per-file-ignores."docker/wait-for-redis.py" = [
"INP001", "INP001",
"T201", "T201",
] ]
lint.per-file-ignores."src/documents/file_handling.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/management/commands/document_consumer.py" = [ lint.per-file-ignores."src/documents/management/commands/document_consumer.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/management/commands/document_exporter.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [ lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
@@ -216,6 +222,9 @@ lint.per-file-ignores."src/documents/models.py" = [
lint.per-file-ignores."src/documents/parsers.py" = [ lint.per-file-ignores."src/documents/parsers.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/signals/handlers.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [ lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [
"RUF001", "RUF001",
] ]

View File

@@ -332,19 +332,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">102</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">102</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">102</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">103</context> <context context-type="linenumber">102</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4930506384627295710" datatype="html"> <trans-unit id="4930506384627295710" datatype="html">
@@ -545,7 +545,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context> <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">362</context> <context context-type="linenumber">361</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context> <context context-type="sourcefile">src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html</context>
@@ -605,7 +605,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
<context context-type="linenumber">74</context> <context context-type="linenumber">73</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5079885666748292382" datatype="html"> <trans-unit id="5079885666748292382" datatype="html">
@@ -763,19 +763,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">52</context> <context context-type="linenumber">51</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">52</context> <context context-type="linenumber">51</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">52</context> <context context-type="linenumber">51</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">52</context> <context context-type="linenumber">51</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
@@ -1225,19 +1225,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">6</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">6</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">6</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">6</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="309314153079578337" datatype="html"> <trans-unit id="309314153079578337" datatype="html">
@@ -1432,7 +1432,7 @@
<source>Cancel</source> <source>Cancel</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context> <context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">361</context> <context context-type="linenumber">362</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/confirm-dialog/confirm-dialog.component.ts</context> <context context-type="sourcefile">src/app/components/common/confirm-dialog/confirm-dialog.component.ts</context>
@@ -1500,7 +1500,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">74</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6839066544204061364" datatype="html"> <trans-unit id="6839066544204061364" datatype="html">
@@ -1598,19 +1598,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">3</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">3</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">3</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">3</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4880728824338713664" datatype="html"> <trans-unit id="4880728824338713664" datatype="html">
@@ -1696,35 +1696,35 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">20</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context> <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
@@ -1816,19 +1816,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">43</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">43</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">43</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">43</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context>
@@ -2121,51 +2121,51 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">9</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">9</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">9</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">9</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">85</context> <context context-type="linenumber">84</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">85</context> <context context-type="linenumber">84</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">85</context> <context context-type="linenumber">84</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">85</context> <context context-type="linenumber">84</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">96</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">96</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">96</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">96</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
@@ -2440,35 +2440,35 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">83</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">83</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">83</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">84</context> <context context-type="linenumber">83</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">94</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">94</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">94</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">94</context> <context context-type="linenumber">93</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context> <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.html</context>
@@ -5227,19 +5227,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">12</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">12</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">12</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">13</context> <context context-type="linenumber">12</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4391289919356861627" datatype="html"> <trans-unit id="4391289919356861627" datatype="html">
@@ -8333,19 +8333,19 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">87</context> <context context-type="linenumber">86</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">87</context> <context context-type="linenumber">86</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">87</context> <context context-type="linenumber">86</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">87</context> <context context-type="linenumber">86</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="651372623796033489" datatype="html"> <trans-unit id="651372623796033489" datatype="html">
@@ -8672,76 +8672,76 @@
<source>Filter by:</source> <source>Filter by:</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">19</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">19</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">19</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">19</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1383365546483928780" datatype="html"> <trans-unit id="1383365546483928780" datatype="html">
<source>Matching</source> <source>Matching</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">39</context> <context context-type="linenumber">38</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1488347670280290838" datatype="html"> <trans-unit id="1488347670280290838" datatype="html">
<source>Document count</source> <source>Document count</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">40</context> <context context-type="linenumber">39</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">40</context> <context context-type="linenumber">39</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">40</context> <context context-type="linenumber">39</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">40</context> <context context-type="linenumber">39</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8095412801504464756" datatype="html"> <trans-unit id="8095412801504464756" datatype="html">
<source>{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION_2"/>}}</source> <source>{VAR_PLURAL, plural, =1 {One <x id="INTERPOLATION"/>} other {<x id="INTERPOLATION_1"/> total <x id="INTERPOLATION_2"/>}}</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="810888510148304696" datatype="html"> <trans-unit id="810888510148304696" datatype="html">

View File

@@ -50,7 +50,7 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div> <div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group me-2"> <div class="btn-group me-2">
<button type="button" (click)="discardChanges()" class="btn btn-outline-secondary" [disabled]="loading || (isDirty$ | async) === false" i18n>Discard</button> <button type="button" (click)="discardChanges()" class="btn btn-secondary" [disabled]="loading || (isDirty$ | async) === false" i18n>Discard</button>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button type="submit" class="btn btn-primary" [disabled]="loading || !configForm.valid || (isDirty$ | async) === false" i18n>Save</button> <button type="submit" class="btn btn-primary" [disabled]="loading || !configForm.valid || (isDirty$ | async) === false" i18n>Save</button>

View File

@@ -358,6 +358,6 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div> <div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button> <button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button> <button type="button" (click)="reset()" class="btn btn-secondary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
</form> </form>

View File

@@ -164,7 +164,7 @@ describe('ManagementListComponent', () => {
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
const createButton = fixture.debugElement.queryAll(By.css('button'))[4] const createButton = fixture.debugElement.queryAll(By.css('button'))[3]
createButton.triggerEventHandler('click') createButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined() expect(modal).not.toBeUndefined()
@@ -188,7 +188,7 @@ describe('ManagementListComponent', () => {
const toastInfoSpy = jest.spyOn(toastService, 'showInfo') const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
const editButton = fixture.debugElement.queryAll(By.css('button'))[7] const editButton = fixture.debugElement.queryAll(By.css('button'))[6]
editButton.triggerEventHandler('click') editButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined() expect(modal).not.toBeUndefined()
@@ -213,7 +213,7 @@ describe('ManagementListComponent', () => {
const deleteSpy = jest.spyOn(tagService, 'delete') const deleteSpy = jest.spyOn(tagService, 'delete')
const reloadSpy = jest.spyOn(component, 'reloadData') const reloadSpy = jest.spyOn(component, 'reloadData')
const deleteButton = fixture.debugElement.queryAll(By.css('button'))[8] const deleteButton = fixture.debugElement.queryAll(By.css('button'))[7]
deleteButton.triggerEventHandler('click') deleteButton.triggerEventHandler('click')
expect(modal).not.toBeUndefined() expect(modal).not.toBeUndefined()
@@ -233,7 +233,7 @@ describe('ManagementListComponent', () => {
it('should support quick filter for objects', () => { it('should support quick filter for objects', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
const filterButton = fixture.debugElement.queryAll(By.css('button'))[9] const filterButton = fixture.debugElement.queryAll(By.css('button'))[8]
filterButton.triggerEventHandler('click') filterButton.triggerEventHandler('click')
expect(qfSpy).toHaveBeenCalledWith([ expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_TAGS_ALL, value: tags[0].id.toString() }, { rule_type: FILTER_HAS_TAGS_ALL, value: tags[0].id.toString() },

View File

@@ -70,6 +70,6 @@
} }
</ul> </ul>
<button type="button" (click)="reset()" class="btn btn-outline-secondary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button> <button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
<button type="submit" class="btn btn-primary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button> <button type="button" (click)="reset()" class="btn btn-secondary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
</form> </form>

View File

@@ -1,5 +1,4 @@
import os import os
from pathlib import Path
from django.conf import settings from django.conf import settings
@@ -8,15 +7,19 @@ from documents.templating.filepath import validate_filepath_template_and_render
from documents.templating.utils import convert_format_str_to_template_format from documents.templating.utils import convert_format_str_to_template_format
def create_source_path_directory(source_path: Path) -> None: def create_source_path_directory(source_path):
source_path.parent.mkdir(parents=True, exist_ok=True) os.makedirs(os.path.dirname(source_path), exist_ok=True)
def delete_empty_directories(directory: Path, root: Path) -> None: def delete_empty_directories(directory, root):
if not directory.is_dir(): if not os.path.isdir(directory):
return return
if not directory.is_relative_to(root): # Go up in the directory hierarchy and try to delete all directories
directory = os.path.normpath(directory)
root = os.path.normpath(root)
if not directory.startswith(root + os.path.sep):
# don't do anything outside our originals folder. # don't do anything outside our originals folder.
# append os.path.set so that we avoid these cases: # append os.path.set so that we avoid these cases:
@@ -24,12 +27,11 @@ def delete_empty_directories(directory: Path, root: Path) -> None:
# root = /home/originals ("/" gets appended and startswith fails) # root = /home/originals ("/" gets appended and startswith fails)
return return
# Go up in the directory hierarchy and try to delete all directories
while directory != root: while directory != root:
if not list(directory.iterdir()): if not os.listdir(directory):
# it's empty # it's empty
try: try:
directory.rmdir() os.rmdir(directory)
except OSError: except OSError:
# whatever. empty directories aren't that bad anyway. # whatever. empty directories aren't that bad anyway.
return return
@@ -38,10 +40,10 @@ def delete_empty_directories(directory: Path, root: Path) -> None:
return return
# go one level up # go one level up
directory = directory.parent directory = os.path.normpath(os.path.dirname(directory))
def generate_unique_filename(doc, *, archive_filename=False) -> Path: def generate_unique_filename(doc, *, archive_filename=False):
""" """
Generates a unique filename for doc in settings.ORIGINALS_DIR. Generates a unique filename for doc in settings.ORIGINALS_DIR.
@@ -54,32 +56,21 @@ def generate_unique_filename(doc, *, archive_filename=False) -> Path:
""" """
if archive_filename: if archive_filename:
old_filename: Path | None = ( old_filename = doc.archive_filename
Path(doc.archive_filename) if doc.archive_filename else None
)
root = settings.ARCHIVE_DIR root = settings.ARCHIVE_DIR
else: else:
old_filename = Path(doc.filename) if doc.filename else None old_filename = doc.filename
root = settings.ORIGINALS_DIR root = settings.ORIGINALS_DIR
# If generating archive filenames, try to make a name that is similar to # If generating archive filenames, try to make a name that is similar to
# the original filename first. # the original filename first.
if archive_filename and doc.filename: if archive_filename and doc.filename:
# Generate the full path using the same logic as generate_filename new_filename = os.path.splitext(doc.filename)[0] + ".pdf"
base_generated = generate_filename(doc, archive_filename=archive_filename) if new_filename == old_filename or not os.path.exists(
os.path.join(root, new_filename),
# Try to create a simple PDF version based on the original filename ):
# but preserve any directory structure from the template return new_filename
if str(base_generated.parent) != ".":
# Has directory structure, preserve it
simple_pdf_name = base_generated.parent / (Path(doc.filename).stem + ".pdf")
else:
# No directory structure
simple_pdf_name = Path(Path(doc.filename).stem + ".pdf")
if simple_pdf_name == old_filename or not (root / simple_pdf_name).exists():
return simple_pdf_name
counter = 0 counter = 0
@@ -93,7 +84,7 @@ def generate_unique_filename(doc, *, archive_filename=False) -> Path:
# still the same as before. # still the same as before.
return new_filename return new_filename
if (root / new_filename).exists(): if os.path.exists(os.path.join(root, new_filename)):
counter += 1 counter += 1
else: else:
return new_filename return new_filename
@@ -105,8 +96,8 @@ def generate_filename(
counter=0, counter=0,
append_gpg=True, append_gpg=True,
archive_filename=False, archive_filename=False,
) -> Path: ):
base_path: Path | None = None path = ""
def format_filename(document: Document, template_str: str) -> str | None: def format_filename(document: Document, template_str: str) -> str | None:
rendered_filename = validate_filepath_template_and_render( rendered_filename = validate_filepath_template_and_render(
@@ -143,34 +134,17 @@ def generate_filename(
# If we have one, render it # If we have one, render it
if filename_format is not None: if filename_format is not None:
rendered_path: str | None = format_filename(doc, filename_format) path = format_filename(doc, filename_format)
if rendered_path:
base_path = Path(rendered_path)
counter_str = f"_{counter:02}" if counter else "" counter_str = f"_{counter:02}" if counter else ""
filetype_str = ".pdf" if archive_filename else doc.file_type filetype_str = ".pdf" if archive_filename else doc.file_type
if base_path: if path:
# Split the path into directory and filename parts filename = f"{path}{counter_str}{filetype_str}"
directory = base_path.parent
# Use the full name (not just stem) as the base filename
base_filename = base_path.name
# Build the final filename with counter and filetype
final_filename = f"{base_filename}{counter_str}{filetype_str}"
# If we have a directory component, include it
if str(directory) != ".":
full_path = directory / final_filename
else: else:
full_path = Path(final_filename) filename = f"{doc.pk:07}{counter_str}{filetype_str}"
else:
# No template, use document ID
final_filename = f"{doc.pk:07}{counter_str}{filetype_str}"
full_path = Path(final_filename)
# Add GPG extension if needed
if append_gpg and doc.storage_type == doc.STORAGE_TYPE_GPG: if append_gpg and doc.storage_type == doc.STORAGE_TYPE_GPG:
full_path = full_path.with_suffix(full_path.suffix + ".gpg") filename += ".gpg"
return full_path return filename

View File

@@ -236,7 +236,10 @@ class Command(CryptMixin, BaseCommand):
# now make an archive in the original target, with all files stored # now make an archive in the original target, with all files stored
if self.zip_export and temp_dir is not None: if self.zip_export and temp_dir is not None:
shutil.make_archive( shutil.make_archive(
self.original_target / options["zip_name"], os.path.join(
self.original_target,
options["zip_name"],
),
format="zip", format="zip",
root_dir=temp_dir.name, root_dir=temp_dir.name,
) )
@@ -339,7 +342,7 @@ class Command(CryptMixin, BaseCommand):
) )
if self.split_manifest: if self.split_manifest:
manifest_name = base_name.with_name(f"{base_name.stem}-manifest.json") manifest_name = Path(base_name + "-manifest.json")
if self.use_folder_prefix: if self.use_folder_prefix:
manifest_name = Path("json") / manifest_name manifest_name = Path("json") / manifest_name
manifest_name = (self.target / manifest_name).resolve() manifest_name = (self.target / manifest_name).resolve()
@@ -413,7 +416,7 @@ class Command(CryptMixin, BaseCommand):
else: else:
item.unlink() item.unlink()
def generate_base_name(self, document: Document) -> Path: def generate_base_name(self, document: Document) -> str:
""" """
Generates a unique name for the document, one which hasn't already been exported (or will be) Generates a unique name for the document, one which hasn't already been exported (or will be)
""" """
@@ -433,12 +436,12 @@ class Command(CryptMixin, BaseCommand):
break break
else: else:
filename_counter += 1 filename_counter += 1
return Path(base_name) return base_name
def generate_document_targets( def generate_document_targets(
self, self,
document: Document, document: Document,
base_name: Path, base_name: str,
document_dict: dict, document_dict: dict,
) -> tuple[Path, Path | None, Path | None]: ) -> tuple[Path, Path | None, Path | None]:
""" """
@@ -446,25 +449,25 @@ class Command(CryptMixin, BaseCommand):
""" """
original_name = base_name original_name = base_name
if self.use_folder_prefix: if self.use_folder_prefix:
original_name = Path("originals") / original_name original_name = os.path.join("originals", original_name)
original_target = (self.target / original_name).resolve() original_target = (self.target / Path(original_name)).resolve()
document_dict[EXPORTER_FILE_NAME] = str(original_name) document_dict[EXPORTER_FILE_NAME] = original_name
if not self.no_thumbnail: if not self.no_thumbnail:
thumbnail_name = base_name.parent / (base_name.stem + "-thumbnail.webp") thumbnail_name = base_name + "-thumbnail.webp"
if self.use_folder_prefix: if self.use_folder_prefix:
thumbnail_name = Path("thumbnails") / thumbnail_name thumbnail_name = os.path.join("thumbnails", thumbnail_name)
thumbnail_target = (self.target / thumbnail_name).resolve() thumbnail_target = (self.target / Path(thumbnail_name)).resolve()
document_dict[EXPORTER_THUMBNAIL_NAME] = str(thumbnail_name) document_dict[EXPORTER_THUMBNAIL_NAME] = thumbnail_name
else: else:
thumbnail_target = None thumbnail_target = None
if not self.no_archive and document.has_archive_version: if not self.no_archive and document.has_archive_version:
archive_name = base_name.parent / (base_name.stem + "-archive.pdf") archive_name = base_name + "-archive.pdf"
if self.use_folder_prefix: if self.use_folder_prefix:
archive_name = Path("archive") / archive_name archive_name = os.path.join("archive", archive_name)
archive_target = (self.target / archive_name).resolve() archive_target = (self.target / Path(archive_name)).resolve()
document_dict[EXPORTER_ARCHIVE_NAME] = str(archive_name) document_dict[EXPORTER_ARCHIVE_NAME] = archive_name
else: else:
archive_target = None archive_target = None
@@ -569,7 +572,7 @@ class Command(CryptMixin, BaseCommand):
perform_copy = False perform_copy = False
if target.exists(): if target.exists():
source_stat = source.stat() source_stat = os.stat(source)
target_stat = target.stat() target_stat = target.stat()
if self.compare_checksums and source_checksum: if self.compare_checksums and source_checksum:
target_checksum = hashlib.md5(target.read_bytes()).hexdigest() target_checksum = hashlib.md5(target.read_bytes()).hexdigest()

View File

@@ -63,11 +63,11 @@ class Document:
/ "documents" / "documents"
/ "originals" / "originals"
/ f"{self.pk:07}.{self.file_type}.gpg" / f"{self.pk:07}.{self.file_type}.gpg"
) ).as_posix()
@property @property
def source_file(self): def source_file(self):
return self.source_path.open("rb") return Path(self.source_path).open("rb")
@property @property
def file_name(self): def file_name(self):

View File

@@ -1,8 +1,8 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import os
import shutil import shutil
from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import httpx import httpx
@@ -51,6 +51,8 @@ from documents.permissions import set_permissions_for_object
from documents.templating.workflows import parse_w_workflow_placeholders from documents.templating.workflows import parse_w_workflow_placeholders
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path
from documents.classifier import DocumentClassifier from documents.classifier import DocumentClassifier
from documents.data_models import ConsumableDocument from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides from documents.data_models import DocumentMetadataOverrides
@@ -327,16 +329,15 @@ def cleanup_document_deletion(sender, instance, **kwargs):
# Find a non-conflicting filename in case a document with the same # Find a non-conflicting filename in case a document with the same
# name was moved to trash earlier # name was moved to trash earlier
counter = 0 counter = 0
old_filename = Path(instance.source_path).name old_filename = os.path.split(instance.source_path)[1]
old_filebase = Path(old_filename).stem (old_filebase, old_fileext) = os.path.splitext(old_filename)
old_fileext = Path(old_filename).suffix
while True: while True:
new_file_path = settings.EMPTY_TRASH_DIR / ( new_file_path = settings.EMPTY_TRASH_DIR / (
old_filebase + (f"_{counter:02}" if counter else "") + old_fileext old_filebase + (f"_{counter:02}" if counter else "") + old_fileext
) )
if new_file_path.exists(): if os.path.exists(new_file_path):
counter += 1 counter += 1
else: else:
break break
@@ -360,26 +361,26 @@ def cleanup_document_deletion(sender, instance, **kwargs):
files += (instance.source_path,) files += (instance.source_path,)
for filename in files: for filename in files:
if filename and filename.is_file(): if filename and os.path.isfile(filename):
try: try:
filename.unlink() os.unlink(filename)
logger.debug(f"Deleted file {filename}.") logger.debug(f"Deleted file {filename}.")
except OSError as e: except OSError as e:
logger.warning( logger.warning(
f"While deleting document {instance!s}, the file " f"While deleting document {instance!s}, the file "
f"{filename} could not be deleted: {e}", f"{filename} could not be deleted: {e}",
) )
elif filename and not filename.is_file(): elif filename and not os.path.isfile(filename):
logger.warning(f"Expected {filename} to exist, but it did not") logger.warning(f"Expected {filename} to exist, but it did not")
delete_empty_directories( delete_empty_directories(
Path(instance.source_path).parent, os.path.dirname(instance.source_path),
root=settings.ORIGINALS_DIR, root=settings.ORIGINALS_DIR,
) )
if instance.has_archive_version: if instance.has_archive_version:
delete_empty_directories( delete_empty_directories(
Path(instance.archive_path).parent, os.path.dirname(instance.archive_path),
root=settings.ARCHIVE_DIR, root=settings.ARCHIVE_DIR,
) )
@@ -400,14 +401,14 @@ def update_filename_and_move_files(
if isinstance(instance, CustomFieldInstance): if isinstance(instance, CustomFieldInstance):
instance = instance.document instance = instance.document
def validate_move(instance, old_path: Path, new_path: Path): def validate_move(instance, old_path, new_path):
if not old_path.is_file(): if not os.path.isfile(old_path):
# Can't do anything if the old file does not exist anymore. # Can't do anything if the old file does not exist anymore.
msg = f"Document {instance!s}: File {old_path} doesn't exist." msg = f"Document {instance!s}: File {old_path} doesn't exist."
logger.fatal(msg) logger.fatal(msg)
raise CannotMoveFilesException(msg) raise CannotMoveFilesException(msg)
if new_path.is_file(): if os.path.isfile(new_path):
# Can't do anything if the new file already exists. Skip updating file. # Can't do anything if the new file already exists. Skip updating file.
msg = f"Document {instance!s}: Cannot rename file since target path {new_path} already exists." msg = f"Document {instance!s}: Cannot rename file since target path {new_path} already exists."
logger.warning(msg) logger.warning(msg)
@@ -435,20 +436,16 @@ def update_filename_and_move_files(
old_filename = instance.filename old_filename = instance.filename
old_source_path = instance.source_path old_source_path = instance.source_path
# Need to convert to string to be able to save it to the db instance.filename = generate_unique_filename(instance)
instance.filename = str(generate_unique_filename(instance))
move_original = old_filename != instance.filename move_original = old_filename != instance.filename
old_archive_filename = instance.archive_filename old_archive_filename = instance.archive_filename
old_archive_path = instance.archive_path old_archive_path = instance.archive_path
if instance.has_archive_version: if instance.has_archive_version:
# Need to convert to string to be able to save it to the db instance.archive_filename = generate_unique_filename(
instance.archive_filename = str(
generate_unique_filename(
instance, instance,
archive_filename=True, archive_filename=True,
),
) )
move_archive = old_archive_filename != instance.archive_filename move_archive = old_archive_filename != instance.archive_filename
@@ -490,11 +487,11 @@ def update_filename_and_move_files(
# Try to move files to their original location. # Try to move files to their original location.
try: try:
if move_original and instance.source_path.is_file(): if move_original and os.path.isfile(instance.source_path):
logger.info("Restoring previous original path") logger.info("Restoring previous original path")
shutil.move(instance.source_path, old_source_path) shutil.move(instance.source_path, old_source_path)
if move_archive and instance.archive_path.is_file(): if move_archive and os.path.isfile(instance.archive_path):
logger.info("Restoring previous archive path") logger.info("Restoring previous archive path")
shutil.move(instance.archive_path, old_archive_path) shutil.move(instance.archive_path, old_archive_path)
@@ -515,15 +512,17 @@ def update_filename_and_move_files(
# finally, remove any empty sub folders. This will do nothing if # finally, remove any empty sub folders. This will do nothing if
# something has failed above. # something has failed above.
if not old_source_path.is_file(): if not os.path.isfile(old_source_path):
delete_empty_directories( delete_empty_directories(
Path(old_source_path).parent, os.path.dirname(old_source_path),
root=settings.ORIGINALS_DIR, root=settings.ORIGINALS_DIR,
) )
if instance.has_archive_version and not old_archive_path.is_file(): if instance.has_archive_version and not os.path.isfile(
old_archive_path,
):
delete_empty_directories( delete_empty_directories(
Path(old_archive_path).parent, os.path.dirname(old_archive_path),
root=settings.ARCHIVE_DIR, root=settings.ARCHIVE_DIR,
) )
@@ -1220,7 +1219,10 @@ def run_workflows(
) )
files = None files = None
if action.webhook.include_document: if action.webhook.include_document:
with original_file.open("rb") as f: with open(
original_file,
"rb",
) as f:
files = { files = {
"file": ( "file": (
filename, filename,

View File

@@ -41,9 +41,11 @@ class TestDocument(TestCase):
Path(file_path).touch() Path(file_path).touch()
Path(thumb_path).touch() Path(thumb_path).touch()
with mock.patch("documents.signals.handlers.Path.unlink") as mock_unlink: with mock.patch("documents.signals.handlers.os.unlink") as mock_unlink:
document.delete() document.delete()
empty_trash([document.pk]) empty_trash([document.pk])
mock_unlink.assert_any_call(file_path)
mock_unlink.assert_any_call(thumb_path)
self.assertEqual(mock_unlink.call_count, 2) self.assertEqual(mock_unlink.call_count, 2)
def test_document_soft_delete(self): def test_document_soft_delete(self):
@@ -61,7 +63,7 @@ class TestDocument(TestCase):
Path(file_path).touch() Path(file_path).touch()
Path(thumb_path).touch() Path(thumb_path).touch()
with mock.patch("documents.signals.handlers.Path.unlink") as mock_unlink: with mock.patch("documents.signals.handlers.os.unlink") as mock_unlink:
document.delete() document.delete()
self.assertEqual(mock_unlink.call_count, 0) self.assertEqual(mock_unlink.call_count, 0)

View File

@@ -34,12 +34,12 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
document.save() document.save()
self.assertEqual(generate_filename(document), Path(f"{document.pk:07d}.pdf")) self.assertEqual(generate_filename(document), f"{document.pk:07d}.pdf")
document.storage_type = Document.STORAGE_TYPE_GPG document.storage_type = Document.STORAGE_TYPE_GPG
self.assertEqual( self.assertEqual(
generate_filename(document), generate_filename(document),
Path(f"{document.pk:07d}.pdf.gpg"), f"{document.pk:07d}.pdf.gpg",
) )
@override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}") @override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
@@ -58,12 +58,12 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.filename = generate_filename(document) document.filename = generate_filename(document)
# Ensure that filename is properly generated # Ensure that filename is properly generated
self.assertEqual(document.filename, Path("none/none.pdf")) self.assertEqual(document.filename, "none/none.pdf")
# Enable encryption and check again # Enable encryption and check again
document.storage_type = Document.STORAGE_TYPE_GPG document.storage_type = Document.STORAGE_TYPE_GPG
document.filename = generate_filename(document) document.filename = generate_filename(document)
self.assertEqual(document.filename, Path("none/none.pdf.gpg")) self.assertEqual(document.filename, "none/none.pdf.gpg")
document.save() document.save()
@@ -96,7 +96,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated # Ensure that filename is properly generated
document.filename = generate_filename(document) document.filename = generate_filename(document)
self.assertEqual(document.filename, Path("none/none.pdf")) self.assertEqual(document.filename, "none/none.pdf")
create_source_path_directory(document.source_path) create_source_path_directory(document.source_path)
document.source_path.touch() document.source_path.touch()
@@ -137,7 +137,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated # Ensure that filename is properly generated
document.filename = generate_filename(document) document.filename = generate_filename(document)
self.assertEqual(document.filename, Path("none/none.pdf")) self.assertEqual(document.filename, "none/none.pdf")
create_source_path_directory(document.source_path) create_source_path_directory(document.source_path)
Path(document.source_path).touch() Path(document.source_path).touch()
@@ -247,7 +247,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
# Ensure that filename is properly generated # Ensure that filename is properly generated
document.filename = generate_filename(document) document.filename = generate_filename(document)
self.assertEqual(document.filename, Path("none/none.pdf")) self.assertEqual(document.filename, "none/none.pdf")
create_source_path_directory(document.source_path) create_source_path_directory(document.source_path)
@@ -269,11 +269,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
dt = DocumentType.objects.create(name="my_doc_type") dt = DocumentType.objects.create(name="my_doc_type")
d = Document.objects.create(title="the_doc", mime_type="application/pdf") d = Document.objects.create(title="the_doc", mime_type="application/pdf")
self.assertEqual(generate_filename(d), Path("none - the_doc.pdf")) self.assertEqual(generate_filename(d), "none - the_doc.pdf")
d.document_type = dt d.document_type = dt
self.assertEqual(generate_filename(d), Path("my_doc_type - the_doc.pdf")) self.assertEqual(generate_filename(d), "my_doc_type - the_doc.pdf")
@override_settings(FILENAME_FORMAT="{asn} - {title}") @override_settings(FILENAME_FORMAT="{asn} - {title}")
def test_asn(self): def test_asn(self):
@@ -289,8 +289,8 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
archive_serial_number=None, archive_serial_number=None,
checksum="B", checksum="B",
) )
self.assertEqual(generate_filename(d1), Path("652 - the_doc.pdf")) self.assertEqual(generate_filename(d1), "652 - the_doc.pdf")
self.assertEqual(generate_filename(d2), Path("none - the_doc.pdf")) self.assertEqual(generate_filename(d2), "none - the_doc.pdf")
@override_settings(FILENAME_FORMAT="{title} {tag_list}") @override_settings(FILENAME_FORMAT="{title} {tag_list}")
def test_tag_list(self): def test_tag_list(self):
@@ -298,7 +298,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
doc.tags.create(name="tag2") doc.tags.create(name="tag2")
doc.tags.create(name="tag1") doc.tags.create(name="tag1")
self.assertEqual(generate_filename(doc), Path("doc1 tag1,tag2.pdf")) self.assertEqual(generate_filename(doc), "doc1 tag1,tag2.pdf")
doc = Document.objects.create( doc = Document.objects.create(
title="doc2", title="doc2",
@@ -306,7 +306,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
mime_type="application/pdf", mime_type="application/pdf",
) )
self.assertEqual(generate_filename(doc), Path("doc2.pdf")) self.assertEqual(generate_filename(doc), "doc2.pdf")
@override_settings(FILENAME_FORMAT="//etc/something/{title}") @override_settings(FILENAME_FORMAT="//etc/something/{title}")
def test_filename_relative(self): def test_filename_relative(self):
@@ -330,11 +330,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
created=d1, created=d1,
) )
self.assertEqual(generate_filename(doc1), Path("2020-03-06.pdf")) self.assertEqual(generate_filename(doc1), "2020-03-06.pdf")
doc1.created = datetime.date(2020, 11, 16) doc1.created = datetime.date(2020, 11, 16)
self.assertEqual(generate_filename(doc1), Path("2020-11-16.pdf")) self.assertEqual(generate_filename(doc1), "2020-11-16.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT="{added_year}-{added_month}-{added_day}", FILENAME_FORMAT="{added_year}-{added_month}-{added_day}",
@@ -347,11 +347,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
added=d1, added=d1,
) )
self.assertEqual(generate_filename(doc1), Path("232-01-09.pdf")) self.assertEqual(generate_filename(doc1), "232-01-09.pdf")
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1)) doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
self.assertEqual(generate_filename(doc1), Path("2020-11-16.pdf")) self.assertEqual(generate_filename(doc1), "2020-11-16.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT="{correspondent}/{correspondent}/{correspondent}", FILENAME_FORMAT="{correspondent}/{correspondent}/{correspondent}",
@@ -389,11 +389,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.mime_type = "application/pdf" document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
self.assertEqual(generate_filename(document), Path("0000001.pdf")) self.assertEqual(generate_filename(document), "0000001.pdf")
document.pk = 13579 document.pk = 13579
self.assertEqual(generate_filename(document), Path("0013579.pdf")) self.assertEqual(generate_filename(document), "0013579.pdf")
@override_settings(FILENAME_FORMAT=None) @override_settings(FILENAME_FORMAT=None)
def test_format_none(self): def test_format_none(self):
@@ -402,7 +402,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.mime_type = "application/pdf" document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
self.assertEqual(generate_filename(document), Path("0000001.pdf")) self.assertEqual(generate_filename(document), "0000001.pdf")
def test_try_delete_empty_directories(self): def test_try_delete_empty_directories(self):
# Create our working directory # Create our working directory
@@ -428,7 +428,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.mime_type = "application/pdf" document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
self.assertEqual(generate_filename(document), Path("0000001.pdf")) self.assertEqual(generate_filename(document), "0000001.pdf")
@override_settings(FILENAME_FORMAT="{created__year}") @override_settings(FILENAME_FORMAT="{created__year}")
def test_invalid_format_key(self): def test_invalid_format_key(self):
@@ -437,7 +437,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
document.mime_type = "application/pdf" document.mime_type = "application/pdf"
document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
self.assertEqual(generate_filename(document), Path("0000001.pdf")) self.assertEqual(generate_filename(document), "0000001.pdf")
@override_settings(FILENAME_FORMAT="{title}") @override_settings(FILENAME_FORMAT="{title}")
def test_duplicates(self): def test_duplicates(self):
@@ -564,7 +564,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
value_select="abc123", value_select="abc123",
) )
self.assertEqual(generate_filename(doc), Path("document_apple.pdf")) self.assertEqual(generate_filename(doc), "document_apple.pdf")
# handler should not have been called # handler should not have been called
self.assertEqual(m.call_count, 0) self.assertEqual(m.call_count, 0)
@@ -576,7 +576,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
], ],
} }
cf.save() cf.save()
self.assertEqual(generate_filename(doc), Path("document_aubergine.pdf")) self.assertEqual(generate_filename(doc), "document_aubergine.pdf")
# handler should have been called # handler should have been called
self.assertEqual(m.call_count, 1) self.assertEqual(m.call_count, 1)
@@ -897,7 +897,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
pk=1, pk=1,
checksum="1", checksum="1",
) )
self.assertEqual(generate_filename(doc), Path("This. is the title.pdf")) self.assertEqual(generate_filename(doc), "This. is the title.pdf")
doc = Document.objects.create( doc = Document.objects.create(
title="my\\invalid/../title:yay", title="my\\invalid/../title:yay",
@@ -905,7 +905,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
pk=2, pk=2,
checksum="2", checksum="2",
) )
self.assertEqual(generate_filename(doc), Path("my-invalid-..-title-yay.pdf")) self.assertEqual(generate_filename(doc), "my-invalid-..-title-yay.pdf")
@override_settings(FILENAME_FORMAT="{created}") @override_settings(FILENAME_FORMAT="{created}")
def test_date(self): def test_date(self):
@@ -916,7 +916,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
pk=2, pk=2,
checksum="2", checksum="2",
) )
self.assertEqual(generate_filename(doc), Path("2020-05-21.pdf")) self.assertEqual(generate_filename(doc), "2020-05-21.pdf")
def test_dynamic_path(self): def test_dynamic_path(self):
""" """
@@ -935,7 +935,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
checksum="2", checksum="2",
storage_path=StoragePath.objects.create(path="TestFolder/{{created}}"), storage_path=StoragePath.objects.create(path="TestFolder/{{created}}"),
) )
self.assertEqual(generate_filename(doc), Path("TestFolder/2020-06-25.pdf")) self.assertEqual(generate_filename(doc), "TestFolder/2020-06-25.pdf")
def test_dynamic_path_with_none(self): def test_dynamic_path_with_none(self):
""" """
@@ -956,7 +956,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
checksum="2", checksum="2",
storage_path=StoragePath.objects.create(path="{{asn}} - {{created}}"), storage_path=StoragePath.objects.create(path="{{asn}} - {{created}}"),
) )
self.assertEqual(generate_filename(doc), Path("none - 2020-06-25.pdf")) self.assertEqual(generate_filename(doc), "none - 2020-06-25.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT_REMOVE_NONE=True, FILENAME_FORMAT_REMOVE_NONE=True,
@@ -984,7 +984,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
checksum="2", checksum="2",
storage_path=sp, storage_path=sp,
) )
self.assertEqual(generate_filename(doc), Path("TestFolder/2020-06-25.pdf")) self.assertEqual(generate_filename(doc), "TestFolder/2020-06-25.pdf")
# Special case, undefined variable, then defined at the start of the template # Special case, undefined variable, then defined at the start of the template
# This could lead to an absolute path after we remove the leading -none-, but leave the leading / # This could lead to an absolute path after we remove the leading -none-, but leave the leading /
@@ -993,7 +993,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
"{{ owner_username }}/{{ created_year }}/{{ correspondent }}/{{ title }}" "{{ owner_username }}/{{ created_year }}/{{ correspondent }}/{{ title }}"
) )
sp.save() sp.save()
self.assertEqual(generate_filename(doc), Path("2020/does not matter.pdf")) self.assertEqual(generate_filename(doc), "2020/does not matter.pdf")
def test_multiple_doc_paths(self): def test_multiple_doc_paths(self):
""" """
@@ -1028,14 +1028,8 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
), ),
) )
self.assertEqual( self.assertEqual(generate_filename(doc_a), "ThisIsAFolder/4/2020-06-25.pdf")
generate_filename(doc_a), self.assertEqual(generate_filename(doc_b), "SomeImportantNone/2020-07-25.pdf")
Path("ThisIsAFolder/4/2020-06-25.pdf"),
)
self.assertEqual(
generate_filename(doc_b),
Path("SomeImportantNone/2020-07-25.pdf"),
)
@override_settings( @override_settings(
FILENAME_FORMAT=None, FILENAME_FORMAT=None,
@@ -1070,11 +1064,8 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
), ),
) )
self.assertEqual(generate_filename(doc_a), Path("0000002.pdf")) self.assertEqual(generate_filename(doc_a), "0000002.pdf")
self.assertEqual( self.assertEqual(generate_filename(doc_b), "SomeImportantNone/2020-07-25.pdf")
generate_filename(doc_b),
Path("SomeImportantNone/2020-07-25.pdf"),
)
@override_settings( @override_settings(
FILENAME_FORMAT="{created_year_short}/{created_month_name_short}/{created_month_name}/{title}", FILENAME_FORMAT="{created_year_short}/{created_month_name_short}/{created_month_name}/{title}",
@@ -1087,7 +1078,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
pk=2, pk=2,
checksum="2", checksum="2",
) )
self.assertEqual(generate_filename(doc), Path("89/Dec/December/The Title.pdf")) self.assertEqual(generate_filename(doc), "89/Dec/December/The Title.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT="{added_year_short}/{added_month_name}/{added_month_name_short}/{title}", FILENAME_FORMAT="{added_year_short}/{added_month_name}/{added_month_name_short}/{title}",
@@ -1100,7 +1091,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
pk=2, pk=2,
checksum="2", checksum="2",
) )
self.assertEqual(generate_filename(doc), Path("84/August/Aug/The Title.pdf")) self.assertEqual(generate_filename(doc), "84/August/Aug/The Title.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT="{owner_username}/{title}", FILENAME_FORMAT="{owner_username}/{title}",
@@ -1133,8 +1124,8 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
checksum="3", checksum="3",
) )
self.assertEqual(generate_filename(owned_doc), Path("user1/The Title.pdf")) self.assertEqual(generate_filename(owned_doc), "user1/The Title.pdf")
self.assertEqual(generate_filename(no_owner_doc), Path("none/does matter.pdf")) self.assertEqual(generate_filename(no_owner_doc), "none/does matter.pdf")
@override_settings( @override_settings(
FILENAME_FORMAT="{original_name}", FILENAME_FORMAT="{original_name}",
@@ -1180,20 +1171,17 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
original_filename="logs.txt", original_filename="logs.txt",
) )
self.assertEqual(generate_filename(doc_with_original), Path("someepdf.pdf")) self.assertEqual(generate_filename(doc_with_original), "someepdf.pdf")
self.assertEqual( self.assertEqual(
generate_filename(tricky_with_original), generate_filename(tricky_with_original),
Path("some pdf with spaces and stuff.pdf"), "some pdf with spaces and stuff.pdf",
) )
self.assertEqual(generate_filename(no_original), Path("none.pdf")) self.assertEqual(generate_filename(no_original), "none.pdf")
self.assertEqual(generate_filename(text_doc), Path("logs.txt")) self.assertEqual(generate_filename(text_doc), "logs.txt")
self.assertEqual( self.assertEqual(generate_filename(text_doc, archive_filename=True), "logs.pdf")
generate_filename(text_doc, archive_filename=True),
Path("logs.pdf"),
)
@override_settings( @override_settings(
FILENAME_FORMAT="XX{correspondent}/{title}", FILENAME_FORMAT="XX{correspondent}/{title}",
@@ -1218,7 +1206,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
# Ensure that filename is properly generated # Ensure that filename is properly generated
document.filename = generate_filename(document) document.filename = generate_filename(document)
self.assertEqual(document.filename, Path("XX/doc1.pdf")) self.assertEqual(document.filename, "XX/doc1.pdf")
def test_complex_template_strings(self): def test_complex_template_strings(self):
""" """
@@ -1256,19 +1244,19 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("somepath/some where/2020-06-25/Does Matter.pdf"), "somepath/some where/2020-06-25/Does Matter.pdf",
) )
doc_a.checksum = "5" doc_a.checksum = "5"
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("somepath/2024-10-01/Does Matter.pdf"), "somepath/2024-10-01/Does Matter.pdf",
) )
sp.path = "{{ document.title|lower }}{{ document.archive_serial_number - 2 }}" sp.path = "{{ document.title|lower }}{{ document.archive_serial_number - 2 }}"
sp.save() sp.save()
self.assertEqual(generate_filename(doc_a), Path("does matter23.pdf")) self.assertEqual(generate_filename(doc_a), "does matter23.pdf")
sp.path = """ sp.path = """
somepath/ somepath/
@@ -1287,13 +1275,13 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
sp.save() sp.save()
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("somepath/asn-000-200/Does Matter/Does Matter.pdf"), "somepath/asn-000-200/Does Matter/Does Matter.pdf",
) )
doc_a.archive_serial_number = 301 doc_a.archive_serial_number = 301
doc_a.save() doc_a.save()
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("somepath/asn-201-400/asn-3xx/Does Matter.pdf"), "somepath/asn-201-400/asn-3xx/Does Matter.pdf",
) )
@override_settings( @override_settings(
@@ -1322,7 +1310,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
with self.assertLogs(level=logging.WARNING) as capture: with self.assertLogs(level=logging.WARNING) as capture:
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("0000002.pdf"), "0000002.pdf",
) )
self.assertEqual(len(capture.output), 1) self.assertEqual(len(capture.output), 1)
@@ -1357,7 +1345,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
with self.assertLogs(level=logging.WARNING) as capture: with self.assertLogs(level=logging.WARNING) as capture:
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("0000002.pdf"), "0000002.pdf",
) )
self.assertEqual(len(capture.output), 1) self.assertEqual(len(capture.output), 1)
@@ -1425,7 +1413,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("invoices/1234.pdf"), "invoices/1234.pdf",
) )
with override_settings( with override_settings(
@@ -1439,7 +1427,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("Some Title_ChoiceOne.pdf"), "Some Title_ChoiceOne.pdf",
) )
# Check for handling Nones well # Check for handling Nones well
@@ -1448,7 +1436,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("Some Title_Default Value.pdf"), "Some Title_Default Value.pdf",
) )
cf.name = "Invoice Number" cf.name = "Invoice Number"
@@ -1461,7 +1449,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("invoices/4567.pdf"), "invoices/4567.pdf",
) )
with override_settings( with override_settings(
@@ -1469,7 +1457,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("invoices/0.pdf"), "invoices/0.pdf",
) )
def test_datetime_filter(self): def test_datetime_filter(self):
@@ -1508,7 +1496,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("2020/Some Title.pdf"), "2020/Some Title.pdf",
) )
with override_settings( with override_settings(
@@ -1516,7 +1504,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("2020-06-25/Some Title.pdf"), "2020-06-25/Some Title.pdf",
) )
with override_settings( with override_settings(
@@ -1524,7 +1512,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc_a), generate_filename(doc_a),
Path("2024-10-01/Some Title.pdf"), "2024-10-01/Some Title.pdf",
) )
def test_slugify_filter(self): def test_slugify_filter(self):
@@ -1551,7 +1539,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc), generate_filename(doc),
Path("some-title-with-special-characters.pdf"), "some-title-with-special-characters.pdf",
) )
# Test with correspondent name containing spaces and special chars # Test with correspondent name containing spaces and special chars
@@ -1565,7 +1553,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc), generate_filename(doc),
Path("johns-office-workplace/some-title-with-special-characters.pdf"), "johns-office-workplace/some-title-with-special-characters.pdf",
) )
# Test with custom fields # Test with custom fields
@@ -1584,5 +1572,5 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
): ):
self.assertEqual( self.assertEqual(
generate_filename(doc), generate_filename(doc),
Path("brussels-belgium/some-title-with-special-characters.pdf"), "brussels-belgium/some-title-with-special-characters.pdf",
) )

View File

@@ -209,7 +209,7 @@ class TestExportImport(
4, 4,
) )
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
self.assertEqual( self.assertEqual(
self._get_document_from_manifest(manifest, self.d1.id)["fields"]["title"], self._get_document_from_manifest(manifest, self.d1.id)["fields"]["title"],
@@ -235,7 +235,9 @@ class TestExportImport(
).as_posix() ).as_posix()
self.assertIsFile(fname) self.assertIsFile(fname)
self.assertIsFile( self.assertIsFile(
self.target / element[document_exporter.EXPORTER_THUMBNAIL_NAME], (
self.target / element[document_exporter.EXPORTER_THUMBNAIL_NAME]
).as_posix(),
) )
with Path(fname).open("rb") as f: with Path(fname).open("rb") as f:
@@ -250,7 +252,7 @@ class TestExportImport(
if document_exporter.EXPORTER_ARCHIVE_NAME in element: if document_exporter.EXPORTER_ARCHIVE_NAME in element:
fname = ( fname = (
self.target / element[document_exporter.EXPORTER_ARCHIVE_NAME] self.target / element[document_exporter.EXPORTER_ARCHIVE_NAME]
) ).as_posix()
self.assertIsFile(fname) self.assertIsFile(fname)
with Path(fname).open("rb") as f: with Path(fname).open("rb") as f:
@@ -310,7 +312,7 @@ class TestExportImport(
) )
self._do_export() self._do_export()
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
st_mtime_1 = (self.target / "manifest.json").stat().st_mtime st_mtime_1 = (self.target / "manifest.json").stat().st_mtime
@@ -320,7 +322,7 @@ class TestExportImport(
self._do_export() self._do_export()
m.assert_not_called() m.assert_not_called()
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
st_mtime_2 = (self.target / "manifest.json").stat().st_mtime st_mtime_2 = (self.target / "manifest.json").stat().st_mtime
Path(self.d1.source_path).touch() Path(self.d1.source_path).touch()
@@ -332,7 +334,7 @@ class TestExportImport(
self.assertEqual(m.call_count, 1) self.assertEqual(m.call_count, 1)
st_mtime_3 = (self.target / "manifest.json").stat().st_mtime st_mtime_3 = (self.target / "manifest.json").stat().st_mtime
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
self.assertNotEqual(st_mtime_1, st_mtime_2) self.assertNotEqual(st_mtime_1, st_mtime_2)
self.assertNotEqual(st_mtime_2, st_mtime_3) self.assertNotEqual(st_mtime_2, st_mtime_3)
@@ -350,7 +352,7 @@ class TestExportImport(
self._do_export() self._do_export()
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
with mock.patch( with mock.patch(
"documents.management.commands.document_exporter.copy_file_with_basic_stats", "documents.management.commands.document_exporter.copy_file_with_basic_stats",
@@ -358,7 +360,7 @@ class TestExportImport(
self._do_export() self._do_export()
m.assert_not_called() m.assert_not_called()
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
self.d2.checksum = "asdfasdgf3" self.d2.checksum = "asdfasdgf3"
self.d2.save() self.d2.save()
@@ -369,7 +371,7 @@ class TestExportImport(
self._do_export(compare_checksums=True) self._do_export(compare_checksums=True)
self.assertEqual(m.call_count, 1) self.assertEqual(m.call_count, 1)
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
def test_update_export_deleted_document(self): def test_update_export_deleted_document(self):
shutil.rmtree(Path(self.dirs.media_dir) / "documents") shutil.rmtree(Path(self.dirs.media_dir) / "documents")
@@ -383,7 +385,7 @@ class TestExportImport(
self.assertTrue(len(manifest), 7) self.assertTrue(len(manifest), 7)
doc_from_manifest = self._get_document_from_manifest(manifest, self.d3.id) doc_from_manifest = self._get_document_from_manifest(manifest, self.d3.id)
self.assertIsFile( self.assertIsFile(
str(self.target / doc_from_manifest[EXPORTER_FILE_NAME]), (self.target / doc_from_manifest[EXPORTER_FILE_NAME]).as_posix(),
) )
self.d3.delete() self.d3.delete()
@@ -395,12 +397,12 @@ class TestExportImport(
self.d3.id, self.d3.id,
) )
self.assertIsFile( self.assertIsFile(
self.target / doc_from_manifest[EXPORTER_FILE_NAME], (self.target / doc_from_manifest[EXPORTER_FILE_NAME]).as_posix(),
) )
manifest = self._do_export(delete=True) manifest = self._do_export(delete=True)
self.assertIsNotFile( self.assertIsNotFile(
self.target / doc_from_manifest[EXPORTER_FILE_NAME], (self.target / doc_from_manifest[EXPORTER_FILE_NAME]).as_posix(),
) )
self.assertTrue(len(manifest), 6) self.assertTrue(len(manifest), 6)
@@ -414,20 +416,20 @@ class TestExportImport(
) )
self._do_export(use_filename_format=True) self._do_export(use_filename_format=True)
self.assertIsFile(self.target / "wow1" / "c.pdf") self.assertIsFile((self.target / "wow1" / "c.pdf").as_posix())
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
self.d1.title = "new_title" self.d1.title = "new_title"
self.d1.save() self.d1.save()
self._do_export(use_filename_format=True, delete=True) self._do_export(use_filename_format=True, delete=True)
self.assertIsNotFile(self.target / "wow1" / "c.pdf") self.assertIsNotFile((self.target / "wow1" / "c.pdf").as_posix())
self.assertIsNotDir(self.target / "wow1") self.assertIsNotDir((self.target / "wow1").as_posix())
self.assertIsFile(self.target / "new_title" / "c.pdf") self.assertIsFile((self.target / "new_title" / "c.pdf").as_posix())
self.assertIsFile(self.target / "manifest.json") self.assertIsFile((self.target / "manifest.json").as_posix())
self.assertIsFile(self.target / "wow2" / "none.pdf") self.assertIsFile((self.target / "wow2" / "none.pdf").as_posix())
self.assertIsFile( self.assertIsFile(
self.target / "wow2" / "none_01.pdf", (self.target / "wow2" / "none_01.pdf").as_posix(),
) )
def test_export_missing_files(self): def test_export_missing_files(self):

View File

@@ -20,7 +20,7 @@ def source_path_before(self):
if self.storage_type == STORAGE_TYPE_GPG: if self.storage_type == STORAGE_TYPE_GPG:
fname += ".gpg" fname += ".gpg"
return Path(settings.ORIGINALS_DIR) / fname return (Path(settings.ORIGINALS_DIR) / fname).as_posix()
def file_type_after(self): def file_type_after(self):
@@ -35,7 +35,7 @@ def source_path_after(doc):
if doc.storage_type == STORAGE_TYPE_GPG: if doc.storage_type == STORAGE_TYPE_GPG:
fname += ".gpg" # pragma: no cover fname += ".gpg" # pragma: no cover
return Path(settings.ORIGINALS_DIR) / fname return (Path(settings.ORIGINALS_DIR) / fname).as_posix()
@override_settings(PASSPHRASE="test") @override_settings(PASSPHRASE="test")

186
uv.lock generated
View File

@@ -1,5 +1,5 @@
version = 1 version = 1
revision = 3 revision = 2
requires-python = ">=3.10" requires-python = ">=3.10"
resolution-markers = [ resolution-markers = [
"sys_platform == 'darwin'", "sys_platform == 'darwin'",
@@ -312,15 +312,15 @@ wheels = [
[[package]] [[package]]
name = "channels" name = "channels"
version = "4.3.1" version = "4.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "asgiref", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "asgiref", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "django", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/12/a0/46450fcf9e56af18a6b0440ba49db6635419bb7bc84142c35f4143b1a66c/channels-4.3.1.tar.gz", hash = "sha256:97413ffd674542db08e16a9ef09cd86ec0113e5f8125fbd33cf0854adcf27cdb", size = 26896, upload-time = "2025-08-01T13:25:19.952Z" } sdist = { url = "https://files.pythonhosted.org/packages/72/04/6768c7a887f9c593c4d49f99130c8aec4ea06e750bc17c306b689f6caf3b/channels-4.3.0.tar.gz", hash = "sha256:7db32c61dcd88eada1647e6c6f6ad2eb724b75d4852eeff26ad1c51ccd1a37f7", size = 26816, upload-time = "2025-07-28T13:52:50.334Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/89/1c/eae1c2a8c195760376e7f65d0bdcc3e966695d29cfbe5c54841ce5c71408/channels-4.3.1-py3-none-any.whl", hash = "sha256:b091d4b26f91d807de3e84aead7ba785314f27eaf5bac31dd51b1c956b883859", size = 31286, upload-time = "2025-08-01T13:25:18.845Z" }, { url = "https://files.pythonhosted.org/packages/7c/59/0866202ee593e1b0dab0b472ebb8169e1b2b7886ad3008d193da2bbe10cb/channels-4.3.0-py3-none-any.whl", hash = "sha256:0497f3affb95e621b37d6bae1b6a5d9e8e1e1221007a2566f280091cf30ffcce", size = 31238, upload-time = "2025-07-28T13:52:49.117Z" },
] ]
[[package]] [[package]]
@@ -1024,86 +1024,86 @@ wheels = [
[[package]] [[package]]
name = "granian" name = "granian"
version = "2.5.0" version = "2.4.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/e7/91/6b51c5749a58e5d86063b193c15914700464f0d64eda84178bf432dbbcf9/granian-2.5.0.tar.gz", hash = "sha256:bed0d047c9c0c6c6a5a85ee5b3c7e2683fc63e03ac032eaf3d7654fa96bde102", size = 110336, upload-time = "2025-07-30T18:55:15.161Z" } sdist = { url = "https://files.pythonhosted.org/packages/b6/95/33666bbf579b36562cdfb66293d0b349e9d28a41a5e473ab61ea565e0859/granian-2.4.1.tar.gz", hash = "sha256:31dd5b28373e330506ae3dd4742880317263a54460046e5303585305ed06a793", size = 105802, upload-time = "2025-07-01T21:49:56.81Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/95/31/2d837432708230fc281fce10d5db2eb1755005c9d8f4fdac0707729d442d/granian-2.5.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:49c1c5059b39db0863e3e967ba18f1ab55fa0f86388537d937aa18baf2934f71", size = 3044791, upload-time = "2025-07-30T18:52:31.448Z" }, { url = "https://files.pythonhosted.org/packages/6b/5f/a1a68e68e145979a1387fb27918f057758ed98af7ab71dce865bd8de6200/granian-2.4.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7a5279a4d6664f1aa60826af6e3588d890732067c8f6266946d9810452e616ea", size = 3051532, upload-time = "2025-07-01T21:47:21.13Z" },
{ url = "https://files.pythonhosted.org/packages/ba/c8/0b48fd67dfe88ce7a21de0fe7030789f71c26223f5523bd5e951dd98afde/granian-2.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aba86bae39b4e8f770d7e6664490e0abdcdcd742300d16cf1cb370dc159a09c1", size = 2602731, upload-time = "2025-07-30T18:52:34.641Z" }, { url = "https://files.pythonhosted.org/packages/3c/9f/1672e33247cfb1128147e38f27e7e226e0e36185a070570480cdd710212b/granian-2.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42c93f33914d9de8f79ce4bfe50f8b640733865831c4ec020199c9c57bf52cfd", size = 2709147, upload-time = "2025-07-01T21:47:23.553Z" },
{ url = "https://files.pythonhosted.org/packages/86/21/f40d46af2ce98b7b883d5d34730103afbd5a7bb8fd1af8989cb617275be5/granian-2.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0ff75ceeae4c9015c1c5fc5d699bb75b7b10d05f5d69aa091ced11be1e145e43", size = 3347314, upload-time = "2025-07-30T18:52:36.389Z" }, { url = "https://files.pythonhosted.org/packages/70/02/52031944a6c7170ca71c007879ffd6c1ad5e78bd4c9d0ed76b1d3c43916c/granian-2.4.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5468d62131dcc003c944bd4f82cd05e1c3d3c7773e367ef0fd78d197cc7d4d30", size = 3307063, upload-time = "2025-07-01T21:47:25.065Z" },
{ url = "https://files.pythonhosted.org/packages/8b/0d/2d0ab45ba4bd68e70129ebe6d14ab8a41263e66a4b3b25b80c8d60ccf704/granian-2.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5f8b1a0b82fa3af2a94572664a3382e3d786981a9cda3bf8b118a0f10338f89", size = 2949913, upload-time = "2025-07-30T18:52:38.036Z" }, { url = "https://files.pythonhosted.org/packages/29/1b/590108fd38356e29b509e32fea25036e1b12ea87e102e08615b01b342e47/granian-2.4.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab74a8ecb4d94d5dda7b7596fa5e00e10f4d8a22783f7e3b75e73a096bd584f5", size = 3004408, upload-time = "2025-07-01T21:47:26.541Z" },
{ url = "https://files.pythonhosted.org/packages/8a/a4/c11b542bae8d7d02e1f5f07a0925b4bb1ad084fa1438e9ec2f89369154fd/granian-2.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d01fb578ab52b95c8cc39fe09a01ef5682011f4eea67d0e5e33ab941c9ef38", size = 3233880, upload-time = "2025-07-30T18:52:39.447Z" }, { url = "https://files.pythonhosted.org/packages/ed/4f/fbf480554a80217af3428e1a6c6dd613e2c4ab4568839ee2473a9c25e297/granian-2.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6a6582b10d7a9d4a4ef03e89469fbfe779309035e956a197ce40f09de68273a", size = 3219653, upload-time = "2025-07-01T21:47:28.1Z" },
{ url = "https://files.pythonhosted.org/packages/21/ca/c67684afa272dbde2898b6eed6b51679c16b40e0f2562ef36030fc63fb45/granian-2.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:92271019ee283cd74d137e6d8e4fda836a80846a7a006d986246d3e300d8229a", size = 3108699, upload-time = "2025-07-30T18:52:40.744Z" }, { url = "https://files.pythonhosted.org/packages/99/21/dc0743099e615c87475d10f4e0713de067279243a432aa407c13d14af40e/granian-2.4.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5f471deb897631e9c9b104ea7d20bffc3a7d31b5d57d4198aa8e41e6c9e38ac6", size = 3102815, upload-time = "2025-07-01T21:47:29.298Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e2/13caaaf3a5f8eea1c265567486927035244a8ae8b5f357d697d8c09a7474/granian-2.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e08ce1768433327720a754a78a3134100b989663c6e924626ba8ce8cdb3ee0a4", size = 3114257, upload-time = "2025-07-30T18:52:42.743Z" }, { url = "https://files.pythonhosted.org/packages/e0/90/7df59160facda055050bfcf1987cc43f2d67d6d5ce39e23e3bd927978ba0/granian-2.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:522f7649604cd0c661800992357f4f9af9822279f66931bbe8664968ffd49a2a", size = 3094521, upload-time = "2025-07-01T21:47:30.459Z" },
{ url = "https://files.pythonhosted.org/packages/8f/71/aa85e2ab2d183efcdbc199107d7683629ff5aeabad409cccecc8197c73d7/granian-2.5.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d09f33aa20fa0f7f9e48f12e87573b3819b357235cac602ec6cef1b5b3ca33a9", size = 3495152, upload-time = "2025-07-30T18:52:44.451Z" }, { url = "https://files.pythonhosted.org/packages/a4/8e/72fa602cc07df284beac01ff2eb9ccbeee23914e9790d7b91ca401edf428/granian-2.4.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:2a12f6a6a86376e3dc964eaa5a7321cd984c09b0c408d5af379aa2e4cb1ba661", size = 3444340, upload-time = "2025-07-01T21:47:31.972Z" },
{ url = "https://files.pythonhosted.org/packages/a3/dd/5e3bab1c332ca2194ef3c58075b1533d944e2cf66b6a242c60c81957e962/granian-2.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5aec8050fc01706d27d74fffc32c29eb483530ea3ad383e885e8b7315ae1c699", size = 3273112, upload-time = "2025-07-30T18:52:46.387Z" }, { url = "https://files.pythonhosted.org/packages/a1/90/73438d52c1cb68f7e80bbdb90aff066167c6ef97053afc26d74f56635775/granian-2.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c5c1494b0235cf69dc5cac737dc6b1d3a82833efd5c9ef5a756971b49355988", size = 3246331, upload-time = "2025-07-01T21:47:33.089Z" },
{ url = "https://files.pythonhosted.org/packages/e3/f3/7d9cee103d91f4d1b934ebaa0cea944638a7b4940c5af72163e486cd4989/granian-2.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0eda4c389c222aa5455b7205640df0207201a86c46e5be98dd0040b6cc45146a", size = 3044837, upload-time = "2025-07-30T18:52:49.893Z" }, { url = "https://files.pythonhosted.org/packages/12/36/3189cf0aa085732859355e9f0464e83644920fab71429c79e32807f7be32/granian-2.4.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dc90c780fc3bb45e653ebab41336d053bc05a85eeb2439540b5d1188b55a44a5", size = 3051270, upload-time = "2025-07-01T21:47:35.791Z" },
{ url = "https://files.pythonhosted.org/packages/ef/b8/fcb93a7bddcedc0af11a446094b33dc99af93a338abd8e95747aae3d1112/granian-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:58aea28ebb2cdf7545ee3cb1c8593c8b2f857a9fa6219589ecd3f5a4b365262f", size = 2602770, upload-time = "2025-07-30T18:52:51.588Z" }, { url = "https://files.pythonhosted.org/packages/c0/f2/57311b3c493b3dac84f7bb2d2d2e36bb204efa5963bf64acda2c902165cf/granian-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8303307f26df720b6c9421857478b90b8c404012965f017574bf4ad0baca637b", size = 2709284, upload-time = "2025-07-01T21:47:36.958Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d9/5f94af3cbdbe023774d24616648e428cfd307f18232a64d82caa2ad8113a/granian-2.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf9dc480d481ae834a085f1f46213ebf80512b1ace0559f6c0335edb24be0e92", size = 3347657, upload-time = "2025-07-30T18:52:53.694Z" }, { url = "https://files.pythonhosted.org/packages/41/c5/a9b9ff4ad4411405a79b18425489b731762a97641b99caddc07577922d12/granian-2.4.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6e6e501eac6acf8ac5bc6247fa67b3eb2cd59b91e683d96028abbf7cb28b0ed", size = 3306997, upload-time = "2025-07-01T21:47:38.128Z" },
{ url = "https://files.pythonhosted.org/packages/82/12/ba9be1b7a9ad28b66735a648a996578b64873c580a3a0681575e60cfa0f8/granian-2.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:604c544273b36091b54fdf66d4e9a0f98dc0369b380ba5dd328478ba65cda320", size = 2949970, upload-time = "2025-07-30T18:52:55.359Z" }, { url = "https://files.pythonhosted.org/packages/81/3a/35f3fc7134bb1b7ea677adf6506b78723f8356ba4230ca1790d7251e421c/granian-2.4.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66b995a12229de0aa30cbe2a338279ac7e720b35db20592fe7fed7a9249649ac", size = 3004758, upload-time = "2025-07-01T21:47:39.69Z" },
{ url = "https://files.pythonhosted.org/packages/07/54/f29100152e7dd6f5dfdff2626b040711735aff2ec9f61cba8e7d04614a5e/granian-2.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6d70f6e7edd183afb62468a7fc175348145aec303297a41b1714a9b6d8150d", size = 3233777, upload-time = "2025-07-30T18:52:57.117Z" }, { url = "https://files.pythonhosted.org/packages/f2/99/ffb3bba665f81ab7e339afbce2c9da14178e4e85ce20ec599791117557af/granian-2.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdf7529847f9aa3f25d89c132fb238853233bfb8e422f39946ebb651cb9f1e6a", size = 3219788, upload-time = "2025-07-01T21:47:41.268Z" },
{ url = "https://files.pythonhosted.org/packages/04/e0/988993586ba3d5e80cc87fc464601df55634ec440eb10889c8fdd3b613ac/granian-2.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:651cbad3a137b762885b7e57dea77b5d11262b0a2c16d4b61b4812bc8bc5ffa4", size = 3108386, upload-time = "2025-07-30T18:52:58.644Z" }, { url = "https://files.pythonhosted.org/packages/0d/91/2684c1c29574a39e5436149cc977e092004d0357bca0e03f55264a39299e/granian-2.4.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6eb47dd316e5e2354e81c514cb58455c37ea84f103756b6f6562181293eee287", size = 3102656, upload-time = "2025-07-01T21:47:42.514Z" },
{ url = "https://files.pythonhosted.org/packages/6a/06/815fde5195f40a2a1be4e78ad0c3cdd05727dcca59a5a231d0df95fb6f68/granian-2.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:22bac6aace54e4183831a46a4033c076100150527676e666eeb84df9829e0e1c", size = 3114733, upload-time = "2025-07-30T18:53:00.059Z" }, { url = "https://files.pythonhosted.org/packages/b7/cc/64dc5d96c5557f1bda25e52eb74284f295a46b4c1660b95bdd212665d5ae/granian-2.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9218b0b4e2c0743444d1a84ba222236efd5d67702b024f8ce9fd2c309f6b147b", size = 3094233, upload-time = "2025-07-01T21:47:43.645Z" },
{ url = "https://files.pythonhosted.org/packages/fb/7f/a8d2fce7f810aa3b7b16550cfbde4756eeb3efcd3646b0adb6c6b7d4bf95/granian-2.5.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:0429dd0c4c21c123b06b7f9057ecb4c1fc3d6228f442276e27545a5ad6fc780c", size = 3495857, upload-time = "2025-07-30T18:53:01.43Z" }, { url = "https://files.pythonhosted.org/packages/db/53/f4d30b60b628698bce653196c75d369bdc543e2d31a6811fd3a963b396ef/granian-2.4.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:dd07733183eb291769d0929ec58c6f16293f82d09fbc434bc3474f1c5e185c3c", size = 3444746, upload-time = "2025-07-01T21:47:44.984Z" },
{ url = "https://files.pythonhosted.org/packages/29/58/984b53efc3b245f5f9b8d76822061b3fb4c5c1024d526ffe59da55b5405c/granian-2.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6141582757eb2bb8c8390637a3ac29dbba0c10db93192adb360c7060f149782", size = 3273079, upload-time = "2025-07-30T18:53:02.792Z" }, { url = "https://files.pythonhosted.org/packages/c5/0d/737a6185a2db9f662de5b5a06373e1244f354ebc132e6bde5987d34ad169/granian-2.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf1301875c28bb54d87280473d3b2378fb86339d117913a13df1ab2764a5effe", size = 3246068, upload-time = "2025-07-01T21:47:46.611Z" },
{ url = "https://files.pythonhosted.org/packages/d1/31/590b932524f43289aa9f735d0b92ccdd97b2d9e388a5acad171fc01382e4/granian-2.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e7627c3b7e3f9c024c4edd80636e8326fbce0420889e0951da349d13742e503", size = 3026668, upload-time = "2025-07-30T18:53:05.505Z" }, { url = "https://files.pythonhosted.org/packages/f4/d5/c0e6258b8aa18dbb335cd3a886d07ae64bb661ce3fc655d8efa24043cda5/granian-2.4.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:5e05c62d82f14dec1b36b358d766422423f5d610c414a3c83259424174a3658e", size = 3044572, upload-time = "2025-07-01T21:47:49.627Z" },
{ url = "https://files.pythonhosted.org/packages/50/94/6bcd3d0cec40994112dfd2b3102f4ff3bd2e62928f6524fb95f38fae6647/granian-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0a4c317f30c227baf16f541f0c93cb08ee45fbf8a2ef5317ba07b6bd6b7c877", size = 2584723, upload-time = "2025-07-30T18:53:06.865Z" }, { url = "https://files.pythonhosted.org/packages/a0/d7/f6b6b5a9d59fc13bcf65554e5cee0ff4e8581fd8af0a69a760e495ab9190/granian-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6166ea4b96cfa2409b70579b1c2609f52fa6035999f7f57975b3b9fc0486f2b1", size = 2698583, upload-time = "2025-07-01T21:47:51.241Z" },
{ url = "https://files.pythonhosted.org/packages/2c/5b/44089c2384ba2e3e5a3cdf08e8a000bff07cf7382fa2d9a0e4e1a9ba6451/granian-2.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:35eb58b0f80fc9f55d5210d339ba8f5f8d9c126a2e29f051e8b62353e3f84b1d", size = 3326781, upload-time = "2025-07-30T18:53:08.323Z" }, { url = "https://files.pythonhosted.org/packages/3b/b8/714141af2190f49b8aac8f72a55621e1730e104a7afac5f8cb3b6c92ddd2/granian-2.4.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0fc250818064d47c48eb02af7e703bb692ee1d478575fce9659e96cf576f03f3", size = 3303145, upload-time = "2025-07-01T21:47:52.437Z" },
{ url = "https://files.pythonhosted.org/packages/bf/7d/7ca304dde1ce475b83e4add007e36a87284e6838f158db87c11a2c93f379/granian-2.5.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def250f04c0374069278152bf3e08ecb1f67e0c99d3eb14d902df1e1558b93fa", size = 2937454, upload-time = "2025-07-30T18:53:10.099Z" }, { url = "https://files.pythonhosted.org/packages/39/6e/1b4b25ab3a734c13e7edb3f219df9d27760ce6b2077c3a29e7db1fd9ff66/granian-2.4.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:019464b5f28a9c475cb4b0aa29d3d1e76f115812b63a03b30fb60b40208e5bf2", size = 2994252, upload-time = "2025-07-01T21:47:53.854Z" },
{ url = "https://files.pythonhosted.org/packages/17/6c/858a7cce6ce07adc2f0e7b1809af61d1af2affbc07edaecd127f16207b37/granian-2.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eda01a9027f2921f42b4f8bb16e46f8ab67a5345e52b3ffeedd2f921a09c87b6", size = 3229723, upload-time = "2025-07-30T18:53:11.464Z" }, { url = "https://files.pythonhosted.org/packages/95/fc/1be24a6e8c64c47516222e1198e407c134ed1596919debc276fd8ebf35c6/granian-2.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82da2bf26c97fd9bc6663bbeda60b469105f5fb4609a5bdc6d9af5e590b703fe", size = 3216855, upload-time = "2025-07-01T21:47:55.923Z" },
{ url = "https://files.pythonhosted.org/packages/8a/7b/ded645443ec95921d407e3d277418987a4aed955830f67d6b151c8c095f9/granian-2.5.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c8a51bcb7e533ab75d2d9e13432e9b63c90eeb7fdde700875253efbfb2dcdcbc", size = 3109804, upload-time = "2025-07-30T18:53:13.179Z" }, { url = "https://files.pythonhosted.org/packages/95/86/fe782ee6093c92208d1d5caaf4c0af689c67f1d0ade1b4525c199bf2477c/granian-2.4.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0bd37c7f43a784344291b5288680c57ab8a651c67b188d9f735be59f87531dbd", size = 3096595, upload-time = "2025-07-01T21:47:57.602Z" },
{ url = "https://files.pythonhosted.org/packages/0a/64/837131cd49e17c219e2a04ed711459e0f93bd5c1db7fc243666e1b9d412a/granian-2.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a9c5670171a97c35aeea79fd7100058e461b0271a7e81fdecd586c770cdd2b41", size = 3099711, upload-time = "2025-07-30T18:53:14.764Z" }, { url = "https://files.pythonhosted.org/packages/24/e0/c0f21edede864276129471c8fef7ec8b28ef41498ae61a5e204eb5fe09da/granian-2.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ddd27ed8b98da83c6833b80f41b05b09351872b4eedfe591eb5b21e46506477", size = 3080317, upload-time = "2025-07-01T21:47:58.797Z" },
{ url = "https://files.pythonhosted.org/packages/8a/4e/80578d06426a40a2718b3183c5e32ba570001153cbbb3fe523d3f9e89880/granian-2.5.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:760275d286142775fb21c85d96fc4996e00de9d3b054c424b2c8519679aa14b5", size = 3468897, upload-time = "2025-07-30T18:53:16.477Z" }, { url = "https://files.pythonhosted.org/packages/9d/0b/18aeb06d9126405716608b1707d174e00b2fd50ea27c7e36a6a0c97eede4/granian-2.4.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e42d4e1712de2412449771aae1bbedf302b3fedb256bf9a9798a548a2ceddacf", size = 3420134, upload-time = "2025-07-01T21:47:59.993Z" },
{ url = "https://files.pythonhosted.org/packages/49/8c/7e5d0187a4e53830cc49231a55f01d3d251eec0cbb09209a0ffde8b33741/granian-2.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1573755288d70f37b55acb14f01cb3a7f7cdca7bf143f908c649ada254e9cb6", size = 3275035, upload-time = "2025-07-30T18:53:17.947Z" }, { url = "https://files.pythonhosted.org/packages/21/7a/c63c8c35215d59306eb42639cfedbe656443247ef0f9212717ad40deee8f/granian-2.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ba5c9f5a5e21c50856480b0d3fa007c846acee44e5b9692f5803ae5ba1f5d7f3", size = 3242402, upload-time = "2025-07-01T21:48:01.319Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d8/c3c8a452f1b590400bb2cdef1ca61da8e9913762884cf3e6ba801fd3fdad/granian-2.5.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:50d4dc74ab763c1bf396cf85d93a8202bf1bfb74150b03f9fd62b600cd0c777c", size = 3026123, upload-time = "2025-07-30T18:53:20.94Z" }, { url = "https://files.pythonhosted.org/packages/d2/8a/3417812f0cc6e518dcd06b0c6965d69f5e740d7989a976e6531a420fd884/granian-2.4.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:86b3a50ff2b83eb2ad856ef32b544daa4162b5da88926edc3e18d5111c635713", size = 3044274, upload-time = "2025-07-01T21:48:03.809Z" },
{ url = "https://files.pythonhosted.org/packages/89/f0/e7038189d4e3b5f1e10bc23547b687bcdecdefbddef87013db64efea6800/granian-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:31705782cd616b9b70536c1b61b7f15815ebc4dcccdb72f58aa806ba7ac5dfa1", size = 2584469, upload-time = "2025-07-30T18:53:22.579Z" }, { url = "https://files.pythonhosted.org/packages/f0/df/75f57f08224504260290518501cb25d325a51172adad673843db5f006093/granian-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8796c39fa0618dd39765fee63776b0ff841986a0caa8aae2d26dce0dae4898c", size = 2698572, upload-time = "2025-07-01T21:48:05.387Z" },
{ url = "https://files.pythonhosted.org/packages/6a/2d/718620f393b6030d4a9ac5d1bc66cc5d159fb7f7c60c5d4483fd43902f7d/granian-2.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bbc4ebc727202ad4b3073ca8148c2af49904710d6fce84872191b2dd5cd36916", size = 3326593, upload-time = "2025-07-30T18:53:24.288Z" }, { url = "https://files.pythonhosted.org/packages/9c/27/c2ffaa57710b39d0fb5f03294033411672d700e78cd641eae5e18139a466/granian-2.4.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95d48c4aff262c5b31438a70f802fa9592c59d3f04fbf07e0f46efefd1e03bb4", size = 3302180, upload-time = "2025-07-01T21:48:07.061Z" },
{ url = "https://files.pythonhosted.org/packages/07/57/c8b5f014673717e850db6d551057e74e330aadad5250d3f312cee432b1ec/granian-2.5.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af272218076663280fdc293b7da3adb716f23d54211cefad92fcf7e01b3eed19", size = 2937464, upload-time = "2025-07-30T18:53:25.72Z" }, { url = "https://files.pythonhosted.org/packages/aa/c7/a6121c187c762e127367544214041f98963e4e7dfd2c1dfdbfbe1bc46fe3/granian-2.4.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe7a9e95335227a741bbfd815594f10d637fc4d6824335bdd09fe8cb7ce9cf5", size = 2994091, upload-time = "2025-07-01T21:48:08.791Z" },
{ url = "https://files.pythonhosted.org/packages/fe/91/8ae8aa9f0c3bbdf42fada15d623a5ab9fffd38acc243ec5619b6cbd60b9a/granian-2.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36493c4f2b672d027eb11b05ca6660f9fd4944452841d213cb0cb64da869539b", size = 3229316, upload-time = "2025-07-30T18:53:27.262Z" }, { url = "https://files.pythonhosted.org/packages/ed/9d/74690dd9cb3541c09b98e1fd75deddcc3885af7ecac3eb813e9f2b4df5e4/granian-2.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e95d58dfd6a4bbf89f826863506a789b7fc12e575b4128b3c095450cffa334d4", size = 3216004, upload-time = "2025-07-01T21:48:10.187Z" },
{ url = "https://files.pythonhosted.org/packages/a5/8c/6c294cf3d77dc9524530d30057f9b6e334cc12c5414feb604fb277d030a3/granian-2.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:afafac4908d5931e4b2c2a09612e063d7ccd05e531f16b7f11e3bccc4ca8972c", size = 3109818, upload-time = "2025-07-30T18:53:29.004Z" }, { url = "https://files.pythonhosted.org/packages/72/83/e09820a814a3071edb0abccf9ddfe7c7d9be337cfb49987a75c759b281a2/granian-2.4.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:266a036f1de45c01b6518a62e4878b6368bc09bff4ff14e4481eb5c556951a8c", size = 3096136, upload-time = "2025-07-01T21:48:11.488Z" },
{ url = "https://files.pythonhosted.org/packages/4a/49/acc3f1e02e35009d9486e4e00d2c951798a8098935d2374f52c7d2728438/granian-2.5.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:fb157c3d66301ffad4113da4c51aed4d56006b9ebe9d0892c682a634b5fff773", size = 3099384, upload-time = "2025-07-30T18:53:30.448Z" }, { url = "https://files.pythonhosted.org/packages/a4/0b/a6adefd57834903af73cafafe02a77a324b9422758cc52923a97eba5085a/granian-2.4.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5aeb00bce5e025fe4b640799c15061aaebc7edf1bd7b8aff6caeed325674fcda", size = 3080194, upload-time = "2025-07-01T21:48:12.765Z" },
{ url = "https://files.pythonhosted.org/packages/c1/87/7cdd96fbeabbceea3820736e65bd6d8c0021983605cee26ef1bf2e11e24b/granian-2.5.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:879fdeb71fe279175a25d709d95dd2db01eb67cd12d300e51e3dc704ca5e52fd", size = 3468575, upload-time = "2025-07-30T18:53:31.857Z" }, { url = "https://files.pythonhosted.org/packages/dc/1b/b4c62359303ade1e6d5a96b019f0db52da0b545a990cc580a6caacfedacb/granian-2.4.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:8982f76b753f5b3b374aff7e6e3b7061e7e42b934a071ae51e8f616ad38089fe", size = 3419814, upload-time = "2025-07-01T21:48:14.439Z" },
{ url = "https://files.pythonhosted.org/packages/fd/64/bd41efc6bfbca0ff871ce28a13b9e687055dd70913dfce92c4a21a264bf7/granian-2.5.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:74601bda3aedb249a3d5059d48108acfa61d6f71686162bda0bedc013a443efb", size = 3274703, upload-time = "2025-07-30T18:53:33.378Z" }, { url = "https://files.pythonhosted.org/packages/cc/dd/e240acc4390bbe056592d37dfd89384d706572af196551a5d9f7ddd6ff22/granian-2.4.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3059d4577863bcfc06e1036d6542ec5e6d98af6bbd1703c40806756971fee90a", size = 3241894, upload-time = "2025-07-01T21:48:19.284Z" },
{ url = "https://files.pythonhosted.org/packages/e9/43/af71556ea889c28b8c1c74e9f50a64c040a92bae5e4412b8617638a8aa0e/granian-2.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f371dd9eedae26158901fee3eb934e8fa61491cc78d234470ce364b989c78a1f", size = 2955162, upload-time = "2025-07-30T18:53:36.263Z" }, { url = "https://files.pythonhosted.org/packages/29/8c/af2139e6fae75a587ae616acb4abaaf6b87fc0939c1ed18598e1ab9e3fb5/granian-2.4.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:87b5ca8686dae65cb11c12ef06f8eebae31be8f4385ff1b892ffb8ed604b3ce4", size = 2975244, upload-time = "2025-07-01T21:48:22.079Z" },
{ url = "https://files.pythonhosted.org/packages/c2/35/14c2c050f3df95eb054f2a44b41a02c30c8a04dc8cca888330f55c43a436/granian-2.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f7bf7ed30bcda9bbc9962f187081c5dfa6aa07e06c3a59486bc573b5def35914", size = 2548356, upload-time = "2025-07-30T18:53:38.116Z" }, { url = "https://files.pythonhosted.org/packages/6b/83/54b31cc7bf578a9fba2112d0fa67b5c87a17198a44fb4ca9588773630bc2/granian-2.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b0caf3363657913530418e4af115e89f428075bd46c0bf972b1557e417ad9a7", size = 2639421, upload-time = "2025-07-01T21:48:23.395Z" },
{ url = "https://files.pythonhosted.org/packages/79/b3/8944acd78ff37a2effcdaf1d6163179e38c46c67c98bb1a68e93a75eb2c2/granian-2.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3152037d799ea97e5736de054a48bf75368fb79b7cfee7e6aa46de1076a43882", size = 3091535, upload-time = "2025-07-30T18:53:39.514Z" }, { url = "https://files.pythonhosted.org/packages/3a/1f/007dae5d387a19d52eaee04c58e21c0bd261dfb9bc3d5ba60f956b8818f0/granian-2.4.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e324d5ffe8c8c964d2d909ba68b46395b1179cd4aa0e9950f10df0741f689d4d", size = 3067951, upload-time = "2025-07-01T21:48:24.697Z" },
{ url = "https://files.pythonhosted.org/packages/ce/49/cf0c89eaa41ac81271e3ae33834f71db945cc09dba609f6dc0e75247fd35/granian-2.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:9a53151c2d31dbcf1acbe6af89ce0282387614b6401650d511ca4260ba0e03c1", size = 2977716, upload-time = "2025-07-30T18:53:41.03Z" }, { url = "https://files.pythonhosted.org/packages/6c/f2/c9fd583e1f528361c78077e31e377aad96f38e193e1e175525abc1ff5a2f/granian-2.4.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:33fabdd106df6f4de61b018847bc9aaa39fa8e56ced78f516778b33f7ad26a8f", size = 2964829, upload-time = "2025-07-01T21:48:26.286Z" },
{ url = "https://files.pythonhosted.org/packages/47/58/e828bd5a02c412484b4056cef9aa505b014cc0bb1882f5dbdaf26782f147/granian-2.5.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:8f9918bee3c21eb1410f4323440d76eaa0c2d2e6ca4fa3e3a20d07cc54b788f6", size = 3094250, upload-time = "2025-07-30T18:53:42.495Z" }, { url = "https://files.pythonhosted.org/packages/d3/95/5e297f7c02f4db5f6681fea8a577921366379d814a3bd2bfd4d184390bac/granian-2.4.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:452ed0de24bcdfc8bc39803650592d38bc728e94819e53c679272a410a1868f8", size = 3070446, upload-time = "2025-07-01T21:48:27.648Z" },
{ url = "https://files.pythonhosted.org/packages/ab/ba/e70f0de5cd6e5bca15ea5e5bc6c5598d34f9d4e9e6707e79e6edb63f1fac/granian-2.5.0-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:c28a34951c1ed8eea97948882bdbc374ce111be5a59293693613d25043ba1313", size = 3458806, upload-time = "2025-07-30T18:53:44.308Z" }, { url = "https://files.pythonhosted.org/packages/5c/24/933e3d7cfd4e2dc97ae7f1e5be1c5a93b3d664118323d58047a320119667/granian-2.4.1-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:b69ff98e5ba85095b88f819525c11118c0f714ff7927ad4157d92a77de873c18", size = 3410970, upload-time = "2025-07-01T21:48:29.558Z" },
{ url = "https://files.pythonhosted.org/packages/a1/c5/4d45042c86a924703f0c9617859742e929cdfe31b644bb16f9845c75342c/granian-2.5.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:944ea3bd400a7ccc8129835eda65bd6a37f8fb77828f4e6ded2f06827d6ec25f", size = 3254382, upload-time = "2025-07-30T18:53:45.769Z" }, { url = "https://files.pythonhosted.org/packages/02/ff/2bfcb0e8c98ac2abe0c65d6950e35ef2aececb21c1378201591e621c8f96/granian-2.4.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17517f379b4def0d4ead09cb5febbf07a6f3380065995eb3646f77a67bd0a8d4", size = 3232429, upload-time = "2025-07-01T21:48:31.118Z" },
{ url = "https://files.pythonhosted.org/packages/6d/7f/b4bbe3b818ec058afc0b5c19d46d4955b1b94297c503d2d92470f60e7e20/granian-2.5.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:2959482f9907206c11a059ababe2705ab10d2ddd68004aaf6f6977e193ffc3e1", size = 3010491, upload-time = "2025-07-30T18:53:48.523Z" }, { url = "https://files.pythonhosted.org/packages/c9/f3/f275a6d59dc373dba73af73c416b9e4140c5aca2988ba76348f256c389b6/granian-2.4.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:36beed559c729ca24d512de4fd7397a5f04fbd01caafa71bd8d2ca7a96d9aeed", size = 3032351, upload-time = "2025-07-01T21:48:34.144Z" },
{ url = "https://files.pythonhosted.org/packages/57/c1/f83ed5c17e66867f0ff91cee530a8c0bf72a31db5dbfcaa41373f5f4bf3b/granian-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64327e137b36e648598c595aad4eda92f54e97262cdd3a0ef28dfdaf7998cfed", size = 2569139, upload-time = "2025-07-30T18:53:49.901Z" }, { url = "https://files.pythonhosted.org/packages/27/14/892b86220893c5fe303dbe0f09c99643c44bcfc469f2e1ce827abc353a49/granian-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2891d9e214c7369e1c0eb8004d798a1b9a0b5d4f36de5fc73e8bb30b15786f59", size = 2681597, upload-time = "2025-07-01T21:48:35.497Z" },
{ url = "https://files.pythonhosted.org/packages/6a/f1/841256d8a21af9a7980a8888e9aa15ce2bcf08d314ca3a0878228fa4bca6/granian-2.5.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0755ee98a021c223df203f6d5bc7a57131422ba579addfc5f3b5f3d2e362c81", size = 3321682, upload-time = "2025-07-30T18:53:51.342Z" }, { url = "https://files.pythonhosted.org/packages/4d/89/02a17e1839e339590e81b13024e4ca31232a7038346c3aaaf7f60a59f936/granian-2.4.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bddd37bf65c007befb0d86dc7968e3fc06ebd114df1e3b270627004bdba049d2", size = 3298967, upload-time = "2025-07-01T21:48:37.085Z" },
{ url = "https://files.pythonhosted.org/packages/f2/66/476ecee6b8fa9c5f61a71f0b26b15c8230fed9ceb4a0e1f2206e7ffe0a40/granian-2.5.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee2bb9dc2c93aa441175d7cc1e75aa670a757100945844646de1b7cb3777f50b", size = 2929348, upload-time = "2025-07-30T18:53:53.078Z" }, { url = "https://files.pythonhosted.org/packages/07/ca/8f8904ef23d19b436bd64eeaae4fc4c35a78b8f44d905e0ded571ff89b1e/granian-2.4.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acc82f3e8d85f02e495a01e169dc76ab319875c3a6c512ee09769b27871e8268", size = 2988213, upload-time = "2025-07-01T21:48:38.75Z" },
{ url = "https://files.pythonhosted.org/packages/48/60/9bd62de29df75911f26672bfed13e05273560c51c76cddda25516f53583c/granian-2.5.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30fe55ba2f9f6ed85b91d6804d042a426480c504b322d42403bec42c1e303c", size = 3222269, upload-time = "2025-07-30T18:53:55.345Z" }, { url = "https://files.pythonhosted.org/packages/96/45/6f31a58d12e2d938071a245db19bb2ba09c14b4881d531bd9f86c12313aa/granian-2.4.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d4ea691ac19e808c4deb23cc142708a940a1d03af46f8e0abf9169517343613", size = 3211546, upload-time = "2025-07-01T21:48:40.048Z" },
{ url = "https://files.pythonhosted.org/packages/e8/c5/26871d3f8658fdc832768dfe26a005d711c1af358d3b651bab3658220be4/granian-2.5.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:99edb7c9875f91341d40143d14d45919385a29470056f5a2da94e14746cbcf74", size = 3103622, upload-time = "2025-07-30T18:53:57.227Z" }, { url = "https://files.pythonhosted.org/packages/df/8b/111a1735c055f57e8844e20ab6b05db9305c5e7df87b47b95ba4a4f67924/granian-2.4.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:f446eabd25995d6688459e1ed959b323aa3d7bf4d501d43c249bf8552f642349", size = 3090038, upload-time = "2025-07-01T21:48:42.291Z" },
{ url = "https://files.pythonhosted.org/packages/84/46/ea6c67008ac403fb40d01b8c792827ce8a1625504d1811cd4533219c1e9e/granian-2.5.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:e5142c8dcbe66c9a5c611ba46de47d1ae2cda5f6efdc11d223b586d980158caf", size = 3093618, upload-time = "2025-07-30T18:53:58.695Z" }, { url = "https://files.pythonhosted.org/packages/0e/e1/959e7fcfbc6752f30ca491ec786e3051a09dc2f50886e7513d6c54ef8c5e/granian-2.4.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:e40f89956c92f6006bc117001a72c799d8739de5ec08a13e550aa7a116ac6ef0", size = 3074937, upload-time = "2025-07-01T21:48:43.978Z" },
{ url = "https://files.pythonhosted.org/packages/1a/de/b344e0237e64d7bd1733b8ff418f616e91028f19e6199169cbffa3b203f6/granian-2.5.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9a11f339ba11940218c0f8ac2c6da872ca1832789e4e2ce10958e8da931bc0b7", size = 3462674, upload-time = "2025-07-30T18:54:00.167Z" }, { url = "https://files.pythonhosted.org/packages/b3/5f/9681d9e605f4659b94c13bd12be0324332cbc76a1d9ee369b2fb4f8bb6fb/granian-2.4.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:74554a79d59fcec5dbc44485039eedc7364e56437bec9c4704172a2a8cbdc784", size = 3416187, upload-time = "2025-07-01T21:48:45.325Z" },
{ url = "https://files.pythonhosted.org/packages/3a/5b/ec4c8673fba9b240680bf9fbe8c8be049a127a3dbedab926546f3b22e214/granian-2.5.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:846550b371057e3b1e151ef30fe25026e9882a525d0fa66929650eb9ecf75c21", size = 3265050, upload-time = "2025-07-30T18:54:01.79Z" }, { url = "https://files.pythonhosted.org/packages/57/c3/18f49e4c251d624e31ca0bfcb3056c0a162296b904954e8771f122ac42e2/granian-2.4.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:97f79411fe6c9bc82efa2c8875a08adf1dcdf9c0336a1f3858a3835572c40eed", size = 3235677, upload-time = "2025-07-01T21:48:46.752Z" },
{ url = "https://files.pythonhosted.org/packages/2f/7e/b6742dab22285aecbdf59169b3098fb2ec7ad5b98a49b40dde45701bfc97/granian-2.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f69e54a136725e7d2911968571fe9a5685282d0097497799b6453d9d4dffee31", size = 2936812, upload-time = "2025-07-30T18:54:04.878Z" }, { url = "https://files.pythonhosted.org/packages/b7/61/2640db211a9eaf14d95fc94818c9cdddf8e026ec9ee7bad1b39b2d90a6b4/granian-2.4.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e53be3efa80bdd8c8ef4e6bd5e22ddc7bfd17fe8a3e37c43c9b4228c05afd075", size = 2968799, upload-time = "2025-07-01T21:48:49.527Z" },
{ url = "https://files.pythonhosted.org/packages/db/a6/27e2ea416d87b23df767454b7898bc85f184056f4cbfc208a10b56193e79/granian-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d6f209d06723fe20808be7206fc2b86f5fabc015eac1a5d458b1a6e98f005b7a", size = 2531122, upload-time = "2025-07-30T18:54:06.388Z" }, { url = "https://files.pythonhosted.org/packages/df/b1/cd8138c0f783caef5d2da1bde3f4bc6b71ad8e102acaae173d12e80306d8/granian-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:955e6a861c3de1e510f724d2d07ca5798bfb8fef1de30e166f23caf52d9a4582", size = 2624589, upload-time = "2025-07-01T21:48:50.975Z" },
{ url = "https://files.pythonhosted.org/packages/2e/a0/46733fbde3f19bcd07b16619113a898ebe0c2762e6a63eb8eed234c48bfc/granian-2.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e5fd23b02bd2c7cc9ca86d780f262b63a53bc5d7846e96a15edf4ac7aa10a79", size = 3085608, upload-time = "2025-07-30T18:54:08.426Z" }, { url = "https://files.pythonhosted.org/packages/9e/b3/368282d1f830b8008cdad3a413f81d849b5000213d39ecbfab25f32c405a/granian-2.4.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0dddf558fe722d8b1b7dc18b4bff05afa90b25f498da8d7c3403fe4e1e9e0", size = 3063109, upload-time = "2025-07-01T21:48:52.587Z" },
{ url = "https://files.pythonhosted.org/packages/68/6f/28e7ddf79de6244a69a0c5e283a2ab7a4089185f9f3007fb9fed86ec11d0/granian-2.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:48968902d351f1bfc70ac5fa69157ee86e5f2947f49df5a84c0965213dbb4ee0", size = 2971321, upload-time = "2025-07-30T18:54:09.886Z" }, { url = "https://files.pythonhosted.org/packages/1f/69/578cecd39ff50e9e29f1e74f243ed30fd743301dd88537462f0fb13b803c/granian-2.4.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a5a6bfd310d7a86b12673b1a1969c44d60a6b9059e8fc86d238aa1d52e5d2268", size = 2959657, upload-time = "2025-07-01T21:48:53.973Z" },
{ url = "https://files.pythonhosted.org/packages/1d/1a/3ba12fd7466741cf3fe7e67d36153261099acd94ff29f6096b8ee62db4f2/granian-2.5.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:000952722eb63bb1811e391142872bf1ebe7f4434b1d39bbf4e91090d4af9429", size = 3088487, upload-time = "2025-07-30T18:54:11.4Z" }, { url = "https://files.pythonhosted.org/packages/9a/0e/1811d70c0701ef7a969d8d9c5cab3415139aa66660925f48676fc48dad22/granian-2.4.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e7ad9d0c1a5f07b5e0085a92f94db1e5a617826801b4dce8bfeae2441a13b55f", size = 3065173, upload-time = "2025-07-01T21:48:55.278Z" },
{ url = "https://files.pythonhosted.org/packages/15/1c/ed2d4e3029ffd0d3f3522e38c022b5bbe23d4b27ea919c4737fc307b43dc/granian-2.5.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:141bb4aff1c83b51b2d34381b515d9db38d0481603119e4519fdc9ae932e7a2e", size = 3452014, upload-time = "2025-07-30T18:54:12.894Z" }, { url = "https://files.pythonhosted.org/packages/9a/ba/29a554dba7194479b20756075596e387885c91bbfea276375c6fd34797da/granian-2.4.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:e7c099a9a431fc6ee05bb89d106045c43413854a1ed646f960bc06385eaefd7e", size = 3405136, upload-time = "2025-07-01T21:48:56.638Z" },
{ url = "https://files.pythonhosted.org/packages/e6/24/0575774a57278db4bf49da156e02ad11aa65e564a2070d66c1b0849f9ae7/granian-2.5.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:fe1f870d24c0f9e4a2dfb6f44ef2173fac90425dcc7e35538028fa5ada75876b", size = 3248972, upload-time = "2025-07-30T18:54:14.279Z" }, { url = "https://files.pythonhosted.org/packages/73/37/d6002091509c4f2a14132be702d0ff910b69fda9d88098e6379347420873/granian-2.4.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:1273bebaf9431aa938708e0c87d0b4eb2ff5a445c17d9a7eb320af96f33fa366", size = 3227816, upload-time = "2025-07-01T21:48:58.035Z" },
{ url = "https://files.pythonhosted.org/packages/39/2c/4e51fe7d7539d5629df74735529220bb7f21155151ad50ff346c117b9199/granian-2.5.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8e0ba96062b56d76311b2ce990065ae5580539e97e1609ab1949ce9f29114b3e", size = 3034058, upload-time = "2025-07-30T18:54:32.87Z" }, { url = "https://files.pythonhosted.org/packages/8d/43/fed39e0611e967934da940435e4ce3bd23835dac8e9811c57eb551e0be05/granian-2.4.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:72f826123561895469b3431db0d96484f52863743181b3f1f41c73b4adbc7807", size = 3049482, upload-time = "2025-07-01T21:49:15.984Z" },
{ url = "https://files.pythonhosted.org/packages/9b/0f/cb32b490e1aff8dde99b730013e4cc429e9b15b884dfbb95fcf423973cab/granian-2.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:88de6d16de826897a83eb971af4ff6b82275771f6504e802e36cb2749836502d", size = 2601562, upload-time = "2025-07-30T18:54:34.341Z" }, { url = "https://files.pythonhosted.org/packages/99/13/e7ab0944e82e441d903eafc884b246c25fd2e66e9de01b8c0dde5806ce76/granian-2.4.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0efdbe606d0b98e2724d90c18e33200870f3eb1b75c33ca384defb7e95bca889", size = 2699245, upload-time = "2025-07-01T21:49:17.397Z" },
{ url = "https://files.pythonhosted.org/packages/3f/2f/807528573ddcad450a282133389d83ee4582e3ae0eff1de20e8d70768e6c/granian-2.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4df376a99fed695aeadbd239cda670431184a5e69615ee5aab1624b35dbddbdf", size = 3216043, upload-time = "2025-07-30T18:54:35.806Z" }, { url = "https://files.pythonhosted.org/packages/46/64/2fb7949494d3d39c1afc26bac9539e129571d5aff54e6ddfad3ebbcaf822/granian-2.4.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64f38d0e0425016b764ef333ed2ddac469eca09d50395ad15059c422d7faa3c0", size = 3212448, upload-time = "2025-07-01T21:49:18.781Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d8/226cc1864814e738fd53971159662cb9d55c5191681a080a4cb73555b760/granian-2.5.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0c41cc1c58c75f6bedde20e958a54cd09c40c924cb8e07e516c76a308e9e64d", size = 3106601, upload-time = "2025-07-30T18:54:37.457Z" }, { url = "https://files.pythonhosted.org/packages/73/09/72d6dbb880f14a5d461a681a9068fce8bd214d4f190cc27d17dff669e5c0/granian-2.4.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:519a9d62fd0a5106b3d316902c315ea65fc8acc5d4c3ba84427dd51367dc251c", size = 3112247, upload-time = "2025-07-01T21:49:20.196Z" },
{ url = "https://files.pythonhosted.org/packages/9f/11/25b0b7ac30af79d4390149cc96aa8e8bf4d5dbe605feeaec7b832c05c156/granian-2.5.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ac08d7f6c44643228220c33c3e697601bd785e970e587ab8c7bac11a661e91b6", size = 3106064, upload-time = "2025-07-30T18:54:39.556Z" }, { url = "https://files.pythonhosted.org/packages/80/ba/6bd2838e0082fa3b385c94fa4559c847d573d377c3e283c3eadae40a5110/granian-2.4.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d5f336179f010be9bbd2a5999851150e98d31ba3b9baae609eb73c99106dca1e", size = 3092795, upload-time = "2025-07-01T21:49:21.743Z" },
{ url = "https://files.pythonhosted.org/packages/6c/e8/f4f9239f1d932c73a96efb930a8278c36e44d5ec051a08a4d98bd3bc4ce5/granian-2.5.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d2258cdc480327a6d0552acdb1005cd2cd65caffc79ebb796c900ece43b66c1d", size = 3533494, upload-time = "2025-07-30T18:54:41.528Z" }, { url = "https://files.pythonhosted.org/packages/15/55/de4700fbb6d406bd86860f855387e7f3f37e7231429d9e9afb93d04eb2f0/granian-2.4.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e82a41444f2cdf70114fdc7b70b2b20e50276c0003f5535f9031f8f605649cb4", size = 3455186, upload-time = "2025-07-01T21:49:23.126Z" },
{ url = "https://files.pythonhosted.org/packages/f7/f9/b57edf4f5f90c5bc01880a51ae257810bb479048c333532173677fc2212b/granian-2.5.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1db48cf24f3230c748e59d0f6d40616289e549cfe51fa908e1a2a85575b9ca54", size = 3272111, upload-time = "2025-07-30T18:54:43.07Z" }, { url = "https://files.pythonhosted.org/packages/c0/45/20d430f2d59e2de3b78577d918a219547930339be6693466d7841b12a7ec/granian-2.4.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:cb728baa8292150c222719d8f1a17eaf4d44d7c1a3e141bc1b9a378373fada5b", size = 3246602, upload-time = "2025-07-01T21:49:24.679Z" },
{ url = "https://files.pythonhosted.org/packages/17/ee/97010d532c4ac48fb2c6dd03e296c8d6ad5829e09f399bfd0a33b6098953/granian-2.5.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a80f23e904f23ca9a90d613444a477a8df8bb2ef1df7bf279ffa6ab7cbbf042a", size = 3034092, upload-time = "2025-07-30T18:54:46.212Z" }, { url = "https://files.pythonhosted.org/packages/0f/33/b5c6d733a9f64049eecc84000eda100e76d699d75299bd61d6f134852eca/granian-2.4.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2e902d611e8b2ff72f9c516284e0c4621c7f93b577ae19aea9eb821c6462adcc", size = 3049355, upload-time = "2025-07-01T21:49:27.809Z" },
{ url = "https://files.pythonhosted.org/packages/08/f9/263fd8d1d2f0904ead415a19a1b05980180ee647edcb6b0727e2a942e4ad/granian-2.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:53b10f7996c9a732cb3e4cf30890badbac5f9ec4baa2898851a68100767cc754", size = 2601634, upload-time = "2025-07-30T18:54:47.765Z" }, { url = "https://files.pythonhosted.org/packages/4e/3e/fb70016f426dc7c6423583d5625391b80e8d479283f7bc0c6452dfb8dfd5/granian-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e02ac71af55a9514557b61541baea1b657cf2a11aa33335f292a64e73baef160", size = 2699157, upload-time = "2025-07-01T21:49:29.337Z" },
{ url = "https://files.pythonhosted.org/packages/e7/a0/2a3348f6a1291a50cc0b6535b6cdf7fbb73998a5ced6536c0026834a781d/granian-2.5.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1a05836852ff70745ec094940d9edc62bb3f1a1f7ffb8ee692d6727ebc8b95", size = 3216203, upload-time = "2025-07-30T18:54:49.404Z" }, { url = "https://files.pythonhosted.org/packages/43/9b/d6ea53cbf3f527d38ad30ffa4304ed566de3e481186bfe9396dc19f76c8c/granian-2.4.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf7daddd6c978726af19db1b5a0c49d0f3abf8ef1f93804fc3912fd1e546c71a", size = 3212442, upload-time = "2025-07-01T21:49:30.872Z" },
{ url = "https://files.pythonhosted.org/packages/d9/5f/f18826ae61861c6e20a1887a359c71074f423e4cd7237faf186618d0f7ee/granian-2.5.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3ce4c44ebb949980cc02e0c8a823fb4352c94f2443004fe4c39eb0262fcb2e6e", size = 3106525, upload-time = "2025-07-30T18:54:50.927Z" }, { url = "https://files.pythonhosted.org/packages/fc/ef/5fff01d6cde612469e0e16198afc9027d1e331304adb025db3461afd4baf/granian-2.4.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:54928278eb4b1a225295c06bbfae5dbc1559d6b8c870052f8a5e245583ed4e28", size = 3112239, upload-time = "2025-07-01T21:49:32.322Z" },
{ url = "https://files.pythonhosted.org/packages/c7/51/6776580a11e0966af4919735072b7d6fd95feea2907753a77046f1f8fbe3/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5a8eea72a37c582fe2653587f5b4bb5323bd8882fcbccff3054311b0735d3814", size = 3106320, upload-time = "2025-07-30T18:54:53.034Z" }, { url = "https://files.pythonhosted.org/packages/1f/64/541b640354e3a12b0125af545fdb138d9c3688b341db2d2cb98540373707/granian-2.4.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:afb0a69869b294db49bbbb5c03bc3d8568b9fc224126b6b5a0a45e37bb980c2c", size = 3092835, upload-time = "2025-07-01T21:49:33.882Z" },
{ url = "https://files.pythonhosted.org/packages/37/8f/9910ac8585fe0f8f0d55bb197a08cccea27e7cd778d059302e5d4c78dfdf/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:168762227d94b74dddff066b2f3519f22426e09f8394ed1a2f48072f80be9275", size = 3533584, upload-time = "2025-07-30T18:54:54.837Z" }, { url = "https://files.pythonhosted.org/packages/c8/b2/c4f6ab5eb28d4cdc611bc10a50c64e959e36a0574ba91ad6eced6fcb8754/granian-2.4.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:5f3c94c342fa0239ded5a5d1e855ab3adb9c6ff489458d2648457db047f9a1d8", size = 3455269, upload-time = "2025-07-01T21:49:35.757Z" },
{ url = "https://files.pythonhosted.org/packages/3c/49/24c811233d11756182ad13dd30e5323dd88faeb76879493dccb484f8d8fe/granian-2.5.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:534a0922c460a8bf9c85937edbc7aac00497d05b64bf9ccc5f5b93006882ecd7", size = 3272305, upload-time = "2025-07-30T18:54:56.948Z" }, { url = "https://files.pythonhosted.org/packages/d1/24/86e07e45695bde6dc8a9d878c2be08d5d0dcc41ec8514ecf77ebc9bb3b59/granian-2.4.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:51613148b46d90374c7050cc9b8cff3e33119b6f8d2db454362371f79fac62f3", size = 3246476, upload-time = "2025-07-01T21:49:37.33Z" },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -1946,7 +1946,6 @@ dependencies = [
{ name = "ocrmypdf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "ocrmypdf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pathvalidate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "pathvalidate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pdf2image", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "pdf2image", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "psycopg-pool", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "python-gnupg", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "python-gnupg", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -1976,7 +1975,7 @@ postgres = [
{ name = "psycopg-c", version = "3.2.9", 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.9", 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.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-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.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-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.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-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.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-cp312-cp312-linux_x86_64.whl" }, marker = "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "psycopg-pool", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "psycopg-pool", version = "3.2.6", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'linux' or sys_platform == 'darwin'" },
] ]
webserver = [ webserver = [
{ name = "granian", extra = ["uvloop"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "granian", extra = ["uvloop"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -2070,7 +2069,7 @@ requires-dist = [
{ name = "filelock", specifier = "~=3.18.0" }, { name = "filelock", specifier = "~=3.18.0" },
{ name = "flower", specifier = "~=2.0.1" }, { name = "flower", specifier = "~=2.0.1" },
{ name = "gotenberg-client", specifier = "~=0.10.0" }, { name = "gotenberg-client", specifier = "~=0.10.0" },
{ name = "granian", extras = ["uvloop"], marker = "extra == 'webserver'", specifier = "~=2.5.0" }, { name = "granian", extras = ["uvloop"], marker = "extra == 'webserver'", specifier = "~=2.4.1" },
{ name = "httpx-oauth", specifier = "~=0.16" }, { name = "httpx-oauth", specifier = "~=0.16" },
{ name = "imap-tools", specifier = "~=1.11.0" }, { name = "imap-tools", specifier = "~=1.11.0" },
{ name = "inotifyrecursive", specifier = "~=0.3" }, { name = "inotifyrecursive", specifier = "~=0.3" },
@@ -2085,8 +2084,7 @@ requires-dist = [
{ 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.9/psycopg_c-3.2.9-cp312-cp312-linux_aarch64.whl" }, { 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.9/psycopg_c-3.2.9-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.9/psycopg_c-3.2.9-cp312-cp312-linux_x86_64.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.9/psycopg_c-3.2.9-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.9" }, { 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.9" },
{ name = "psycopg-pool" }, { name = "psycopg-pool", marker = "extra == 'postgres'" },
{ name = "psycopg-pool", marker = "extra == 'postgres'", specifier = "==3.2.6" },
{ name = "python-dateutil", specifier = "~=2.9.0" }, { name = "python-dateutil", specifier = "~=2.9.0" },
{ name = "python-dotenv", specifier = "~=1.1.0" }, { name = "python-dotenv", specifier = "~=1.1.0" },
{ name = "python-gnupg", specifier = "~=0.5.4" }, { name = "python-gnupg", specifier = "~=0.5.4" },
@@ -2097,7 +2095,7 @@ requires-dist = [
{ name = "redis", extras = ["hiredis"], specifier = "~=5.2.1" }, { name = "redis", extras = ["hiredis"], specifier = "~=5.2.1" },
{ name = "scikit-learn", specifier = "~=1.7.0" }, { name = "scikit-learn", specifier = "~=1.7.0" },
{ name = "setproctitle", specifier = "~=1.3.4" }, { name = "setproctitle", specifier = "~=1.3.4" },
{ name = "tika-client", specifier = "~=0.10.0" }, { name = "tika-client", specifier = "~=0.9.0" },
{ name = "tqdm", specifier = "~=4.67.1" }, { name = "tqdm", specifier = "~=4.67.1" },
{ name = "watchdog", specifier = "~=6.0" }, { name = "watchdog", specifier = "~=6.0" },
{ name = "whitenoise", specifier = "~=6.9" }, { name = "whitenoise", specifier = "~=6.9" },
@@ -2438,7 +2436,7 @@ c = [
{ name = "psycopg-c", version = "3.2.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-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.9", source = { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.9/psycopg_c-3.2.9-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'" },
] ]
pool = [ pool = [
{ name = "psycopg-pool", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "psycopg-pool", version = "3.2.6", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'linux' or sys_platform == 'darwin'" },
] ]
[[package]] [[package]]
@@ -2477,14 +2475,12 @@ wheels = [
name = "psycopg-pool" name = "psycopg-pool"
version = "3.2.6" version = "3.2.6"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" } sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" }, { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" },
] ]
[[package]] [[package]]
name = "pyasn1" name = "pyasn1"
version = "0.6.1" version = "0.6.1"
@@ -2708,11 +2704,11 @@ wheels = [
[[package]] [[package]]
name = "python-gnupg" name = "python-gnupg"
version = "0.5.5" version = "0.5.4"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/42/d0/72a14a79f26c6119b281f6ccc475a787432ef155560278e60df97ce68a86/python-gnupg-0.5.5.tar.gz", hash = "sha256:3fdcaf76f60a1b948ff8e37dc398d03cf9ce7427065d583082b92da7a4ff5a63", size = 66467, upload-time = "2025-08-04T19:26:55.778Z" } sdist = { url = "https://files.pythonhosted.org/packages/f1/3e/ba0dc69c9f4e0aeb24d93175230ef057c151790a7516012f61014918992d/python-gnupg-0.5.4.tar.gz", hash = "sha256:f2fdb5fb29615c77c2743e1cb3d9314353a6e87b10c37d238d91ae1c6feae086", size = 65705, upload-time = "2025-01-07T11:58:34.073Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/19/c147f78cc18c8788f54d4a16a22f6c05deba85ead5672d3ddf6dcba5a5fe/python_gnupg-0.5.5-py2.py3-none-any.whl", hash = "sha256:51fa7b8831ff0914bc73d74c59b99c613de7247b91294323c39733bb85ac3fc1", size = 21916, upload-time = "2025-08-04T19:26:54.307Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/6666ed5a0d3ce4d5444af62e373d5ba8ab253a03487c86f2f9f1078e7c31/python_gnupg-0.5.4-py2.py3-none-any.whl", hash = "sha256:40ce25cde9df29af91fe931ce9df3ce544e14a37f62b13ca878c897217b2de6c", size = 21730, upload-time = "2025-01-07T11:58:32.249Z" },
] ]
[[package]] [[package]]
@@ -3358,16 +3354,16 @@ wheels = [
[[package]] [[package]]
name = "tika-client" name = "tika-client"
version = "0.10.0" version = "0.9.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" }, { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/21/be/65bfc47e4689ecd5ead20cf47dc0084fd767b7e71e8cfabf5fddc42aae3c/tika_client-0.10.0.tar.gz", hash = "sha256:3101e8b2482ae4cb7f87be13ada970ff691bdc3404d94cd52f5e57a09c99370c", size = 2178257, upload-time = "2025-08-04T17:47:30.414Z" } sdist = { url = "https://files.pythonhosted.org/packages/94/ad/3508e42b470a037b3f5c19ca9993893d0faa30ba7ec7e6ac33db9bc3bf51/tika_client-0.9.0.tar.gz", hash = "sha256:c10bba8e40ede23c039f84ccd821fb2d290d339cc26cbd267ab9b561a1e83659", size = 2175246, upload-time = "2025-01-15T18:46:23.901Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b1/31/002e0fa5bca67d6a19da8c294273486f6c46cbcc83d6879719a38a181461/tika_client-0.10.0-py3-none-any.whl", hash = "sha256:f5486cc884e4522575662aa295bda761bf9f101ac8d92840155b58ab8b96f6e2", size = 18237, upload-time = "2025-08-04T17:47:28.966Z" }, { url = "https://files.pythonhosted.org/packages/36/8c/90ba51e014fb548ee34dd5ed14e85ec4a205ff97b89ca393e4de321304ac/tika_client-0.9.0-py3-none-any.whl", hash = "sha256:2464e8335b5e92c276641c729e7707f1e894a2bfb51cc59abdd3bdfb532da8a0", size = 17963, upload-time = "2025-01-15T18:46:21.143Z" },
] ]
[[package]] [[package]]