From da38efebdf46c2a06b86a7615406522f93e66b0c Mon Sep 17 00:00:00 2001
From: Michael Shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sun, 1 Jan 2023 08:59:43 -0800
Subject: [PATCH 01/32] Use correct direction for RTL content
---
.../document-detail/document-detail.component.html | 2 +-
.../document-detail/document-detail.component.scss | 4 ++++
.../document-detail/document-detail.component.ts | 7 +++++++
src-ui/src/app/data/paperless-document-metadata.ts | 2 ++
src/documents/views.py | 8 ++++++++
5 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index 0384de371..54ac665e0 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -91,7 +91,7 @@
Content
-
+
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.scss b/src-ui/src/app/components/document-detail/document-detail.component.scss
index 3ae922564..71d50ca61 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.scss
+++ b/src-ui/src/app/components/document-detail/document-detail.component.scss
@@ -28,3 +28,7 @@
left: 30%;
right: 30%;
}
+
+textarea.rtl {
+ direction: rtl;
+}
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index 08d0b0e82..f99f547e6 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -135,6 +135,13 @@ export class DocumentDetailComponent
: this.metadata?.original_mime_type
}
+ get isRTL() {
+ if (!this.metadata || !this.metadata.lang) return false
+ else {
+ return ['ar', 'he', 'fe'].includes(this.metadata.lang)
+ }
+ }
+
ngOnInit(): void {
this.documentForm.valueChanges
.pipe(takeUntil(this.unsubscribeNotifier))
diff --git a/src-ui/src/app/data/paperless-document-metadata.ts b/src-ui/src/app/data/paperless-document-metadata.ts
index 152f69046..b8c030ee8 100644
--- a/src-ui/src/app/data/paperless-document-metadata.ts
+++ b/src-ui/src/app/data/paperless-document-metadata.ts
@@ -10,4 +10,6 @@ export interface PaperlessDocumentMetadata {
original_filename?: string
has_archive_version?: boolean
+
+ lang?: string
}
diff --git a/src/documents/views.py b/src/documents/views.py
index e313ae17e..52b230b40 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -29,6 +29,7 @@ from django.views.decorators.cache import cache_control
from django.views.generic import TemplateView
from django_filters.rest_framework import DjangoFilterBackend
from documents.tasks import consume_file
+from langdetect import detect
from packaging import version as packaging_version
from paperless import version
from paperless.db import GnuPG
@@ -325,6 +326,13 @@ class DocumentViewSet(
"original_filename": doc.original_filename,
}
+ lang = "en"
+ try:
+ lang = detect(doc.content)
+ except Exception:
+ pass
+ meta["lang"] = lang
+
if doc.has_archive_version:
meta["archive_size"] = self.get_filesize(doc.archive_path)
meta["archive_metadata"] = self.get_metadata(
From 7be9ae9c023d54f39ce4ec03e3b85602949b3492 Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sun, 1 Jan 2023 15:57:22 -0800
Subject: [PATCH 02/32] Try a new way of extracting text from a given PDF file
---
src/paperless_tesseract/parsers.py | 48 ++++++++------------
src/paperless_tesseract/tests/test_parser.py | 26 +++--------
2 files changed, 26 insertions(+), 48 deletions(-)
diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py
index 44671fa11..8e0bac5a7 100644
--- a/src/paperless_tesseract/parsers.py
+++ b/src/paperless_tesseract/parsers.py
@@ -2,6 +2,7 @@ import json
import os
import re
import subprocess
+import tempfile
from pathlib import Path
from typing import Optional
@@ -137,36 +138,27 @@ class RasterisedDocumentParser(DocumentParser):
if not os.path.isfile(pdf_file):
return None
- from pdfminer.high_level import extract_text as pdfminer_extract_text
-
try:
- stripped = post_process_text(pdfminer_extract_text(pdf_file))
+ text = None
+ with tempfile.NamedTemporaryFile(
+ mode="w+",
+ dir=settings.SCRATCH_DIR,
+ ) as tmp:
+ subprocess.run(
+ [
+ "pdftotext",
+ "-q",
+ "-layout",
+ "-enc",
+ "UTF-8",
+ pdf_file,
+ tmp.name,
+ ],
+ )
+ text = tmp.read()
- self.log("debug", f"Extracted text from PDF file {pdf_file}")
+ return post_process_text(text)
- # pdfminer.six does not handle RTL text
- # as a hack, for some languages, return no text, to force
- # OCRMyPdf/Tesseract do handle this correctly
- from langdetect import detect
-
- lang = detect(stripped)
-
- self.log("debug", f"Detected language {lang}")
-
- if (
- lang
- in {
- "ar", # Arabic
- "he", # Hebrew,
- "fa", # Persian
- }
- and pdf_file.name != "archive-fallback.pdf"
- ):
- raise RtlLanguageException()
- return stripped
- except RtlLanguageException:
- self.log("warning", f"Detected RTL language {lang}")
- return None
except Exception:
# TODO catch all for various issues with PDFminer.six.
# If PDFminer fails, fall back to OCR.
@@ -342,7 +334,7 @@ class RasterisedDocumentParser(DocumentParser):
)
if original_has_text:
self.text = text_original
- except (NoTextFoundException, RtlLanguageException, InputFileError) as e:
+ except (NoTextFoundException, InputFileError) as e:
self.log(
"warning",
f"Encountered an error while running OCR: {str(e)}. "
diff --git a/src/paperless_tesseract/tests/test_parser.py b/src/paperless_tesseract/tests/test_parser.py
index 28af8dec1..53af68f8d 100644
--- a/src/paperless_tesseract/tests/test_parser.py
+++ b/src/paperless_tesseract/tests/test_parser.py
@@ -661,28 +661,14 @@ class TestParser(DirectoriesMixin, TestCase):
- Text from the document is extracted
"""
parser = RasterisedDocumentParser(None)
- with mock.patch.object(
- parser,
- "construct_ocrmypdf_parameters",
- wraps=parser.construct_ocrmypdf_parameters,
- ) as wrapped:
- parser.parse(
- os.path.join(self.SAMPLE_FILES, "rtl-test.pdf"),
- "application/pdf",
- )
+ parser.parse(
+ os.path.join(self.SAMPLE_FILES, "rtl-test.pdf"),
+ "application/pdf",
+ )
- # There isn't a good way to actually check this working, with RTL correctly return
- # as it would require tesseract-ocr-ara installed for everyone running the
- # test suite. This test does provide the coverage though and attempts to ensure
- # the force OCR happens
- self.assertIsNotNone(parser.get_text())
-
- self.assertEqual(parser.construct_ocrmypdf_parameters.call_count, 2)
- # Check the last call kwargs
- self.assertTrue(
- parser.construct_ocrmypdf_parameters.call_args.kwargs["safe_fallback"],
- )
+ # Copied from the PDF to here. Don't even look at it
+ self.assertIn("ةﯾﻠﺧﺎدﻻ ةرازو", parser.get_text())
class TestParserFileTypes(DirectoriesMixin, TestCase):
From 1e4923835b7e7eeea49d680630d43d35d9891a9d Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Tue, 3 Jan 2023 13:05:44 -0800
Subject: [PATCH 03/32] Small tweak to use the existing tempdir instead of a
new one
---
src/paperless_tesseract/parsers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/paperless_tesseract/parsers.py b/src/paperless_tesseract/parsers.py
index 8e0bac5a7..14068cb26 100644
--- a/src/paperless_tesseract/parsers.py
+++ b/src/paperless_tesseract/parsers.py
@@ -142,7 +142,7 @@ class RasterisedDocumentParser(DocumentParser):
text = None
with tempfile.NamedTemporaryFile(
mode="w+",
- dir=settings.SCRATCH_DIR,
+ dir=self.tempdir,
) as tmp:
subprocess.run(
[
From 4aa8e9b800e3adf9d4e3a98b1ecaef0da1253b89 Mon Sep 17 00:00:00 2001
From: Felix Eckhofer
Date: Sat, 7 Jan 2023 20:37:02 +0100
Subject: [PATCH 04/32] Use subpath for websocket URL
Fixes #2252
---
src/paperless/urls.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/paperless/urls.py b/src/paperless/urls.py
index 8e8f4b404..490be525a 100644
--- a/src/paperless/urls.py
+++ b/src/paperless/urls.py
@@ -154,7 +154,7 @@ urlpatterns = [
websocket_urlpatterns = [
- re_path(r"ws/status/$", StatusConsumer.as_asgi()),
+ path(settings.BASE_URL.lstrip("/") + "ws/status/", StatusConsumer.as_asgi()),
]
# Text in each page's (and above login form).
From 61a2dca81ff569e3096cd25d996b748c98de0037 Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sat, 7 Jan 2023 07:57:27 -0800
Subject: [PATCH 05/32] Makes a missing file informational only, not fatal
---
docker/env-from-file.sh | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docker/env-from-file.sh b/docker/env-from-file.sh
index 71247f5f6..057b5ed9a 100644
--- a/docker/env-from-file.sh
+++ b/docker/env-from-file.sh
@@ -32,8 +32,7 @@ do
export "${non_file_env_name}"="${val}"
else
- echo "File ${env_value} doesn't exist"
- exit 1
+ echo "File ${env_value} referenced ${env_name} by doesn't exist"
fi
fi
done
From af5cb35531134394de8da9ca5d3799ded8cb8c1a Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sat, 7 Jan 2023 08:03:36 -0800
Subject: [PATCH 06/32] Also filter to only PAPERLESS_ variables
---
docker/env-from-file.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docker/env-from-file.sh b/docker/env-from-file.sh
index 057b5ed9a..3ef44d5af 100644
--- a/docker/env-from-file.sh
+++ b/docker/env-from-file.sh
@@ -14,7 +14,7 @@ do
# Extract the name of the environment variable
env_name=${line%%=*}
# Check if it ends in "_FILE"
- if [[ ${env_name} == *_FILE ]]; then
+ if [[ ${env_name} == PAPERLESS_*_FILE ]]; then
# Extract the value of the environment
env_value=${line#*=}
From fbebd8d7c03ff4346ade7f1ee287192789811053 Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sat, 7 Jan 2023 08:06:28 -0800
Subject: [PATCH 07/32] Fixes up the slightly behind docs
---
docs/setup.md | 14 +++-----------
1 file changed, 3 insertions(+), 11 deletions(-)
diff --git a/docs/setup.md b/docs/setup.md
index 8dcaeae8b..69b4f8417 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -148,17 +148,9 @@ steps described in [Docker setup](#docker_hub) automatically.
!!! note
- You can utilize Docker secrets for some configuration settings by
- appending `_FILE` to some configuration values. This is
- supported currently only by:
-
- - PAPERLESS_DBUSER
- - PAPERLESS_DBPASS
- - PAPERLESS_SECRET_KEY
- - PAPERLESS_AUTO_LOGIN_USERNAME
- - PAPERLESS_ADMIN_USER
- - PAPERLESS_ADMIN_MAIL
- - PAPERLESS_ADMIN_PASSWORD
+ You can utilize Docker secrets for configuration settings by
+ appending `_FILE` to configuration values. For example `PAPERLESS_DBUSER`
+ can be set using `PAPERLESS_DBUSER_FILE=/var/run/secrets/password.txt`.
!!! warning
From 3daee46c3d1df2880e0f1a2447e86c9a1dc0d166 Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sat, 7 Jan 2023 08:16:48 -0800
Subject: [PATCH 08/32] Fixes typo
---
docker/env-from-file.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docker/env-from-file.sh b/docker/env-from-file.sh
index 3ef44d5af..37535c220 100644
--- a/docker/env-from-file.sh
+++ b/docker/env-from-file.sh
@@ -13,7 +13,7 @@ for line in $(printenv)
do
# Extract the name of the environment variable
env_name=${line%%=*}
- # Check if it ends in "_FILE"
+ # Check if it starts with "PAPERLESS_" and ends in "_FILE"
if [[ ${env_name} == PAPERLESS_*_FILE ]]; then
# Extract the value of the environment
env_value=${line#*=}
@@ -32,7 +32,7 @@ do
export "${non_file_env_name}"="${val}"
else
- echo "File ${env_value} referenced ${env_name} by doesn't exist"
+ echo "File ${env_value} referenced by ${env_name} doesn't exist"
fi
fi
done
From 4def3bf5c26511d0c757c03462152750e4f98ad5 Mon Sep 17 00:00:00 2001
From: Felix Eckhofer
Date: Sat, 7 Jan 2023 19:47:16 +0100
Subject: [PATCH 09/32] Simplify parsing of json using jq built-in features
Saves spawning multiple sed processes.
---
.github/workflows/installer-library.yml | 4 ++--
build-docker-image.sh | 12 ++++++------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/installer-library.yml b/.github/workflows/installer-library.yml
index ac241b598..32aaf85ee 100644
--- a/.github/workflows/installer-library.yml
+++ b/.github/workflows/installer-library.yml
@@ -95,8 +95,8 @@ jobs:
name: Setup other versions
id: cache-bust-setup
run: |
- pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
- lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
+ pillow_version=$(jq -r '.default.pillow.version | gsub("=";"")' Pipfile.lock)
+ lxml_version=$(jq -r '.default.lxml.version | gsub("=";"")' Pipfile.lock)
echo "Pillow is ${pillow_version}"
echo "lxml is ${lxml_version}"
diff --git a/build-docker-image.sh b/build-docker-image.sh
index 7ae00066b..01e2251a0 100755
--- a/build-docker-image.sh
+++ b/build-docker-image.sh
@@ -24,12 +24,12 @@ fi
branch_name=$(git rev-parse --abbrev-ref HEAD)
# Parse eithe Pipfile.lock or the .build-config.json
-jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
-qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
-psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
-pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
-pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
-lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
+jbig2enc_version=$(jq -r '.jbig2enc.version' .build-config.json)
+qpdf_version=$(jq -r '.qpdf.version' .build-config.json)
+psycopg2_version=$(jq -r '.default.psycopg2.version | gsub("=";"")' Pipfile.lock)
+pikepdf_version=$(jq -r '.default.pikepdf.version | gsub("=";"")' Pipfile.lock)
+pillow_version=$(jq -r '.default.pillow.version | gsub("=";"")' Pipfile.lock)
+lxml_version=$(jq -r '.default.lxml.version | gsub("=";"")' Pipfile.lock)
base_filename="$(basename -- "${1}")"
build_args_str=""
From 5c9e2d70704da9a159eef93f208937b240e7ac69 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Tue, 10 Jan 2023 07:45:51 -0800
Subject: [PATCH 10/32] Locks everything with 3.8
---
Pipfile.lock | 689 ++++++++++++++++++++++++++++-----------------------
1 file changed, 379 insertions(+), 310 deletions(-)
diff --git a/Pipfile.lock b/Pipfile.lock
index 8e487b7d4..93253a8d4 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -99,7 +99,6 @@
"sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac",
"sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"
],
- "index": "pypi",
"markers": "python_version < '3.9'",
"version": "==0.2.1"
},
@@ -127,7 +126,6 @@
"sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"
],
"index": "pypi",
- "markers": null,
"version": "==5.2.7"
},
"certifi": {
@@ -341,11 +339,11 @@
},
"django": {
"hashes": [
- "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148",
- "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b"
+ "sha256:4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763",
+ "sha256:ff56ebd7ead0fd5dbe06fe157b0024a7aaea2e0593bb3785fb594cf94dad58ef"
],
"index": "pypi",
- "version": "==4.1.4"
+ "version": "==4.1.5"
},
"django-celery-results": {
"hashes": [
@@ -421,50 +419,97 @@
},
"hiredis": {
"hashes": [
- "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e",
- "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27",
- "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163",
- "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc",
- "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26",
- "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e",
- "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579",
- "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a",
- "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048",
- "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87",
- "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63",
- "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54",
- "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05",
- "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb",
- "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea",
- "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5",
- "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e",
- "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc",
- "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99",
- "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a",
- "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581",
- "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426",
- "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db",
- "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a",
- "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a",
- "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d",
- "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443",
- "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79",
- "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d",
- "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9",
- "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d",
- "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485",
- "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5",
- "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048",
- "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0",
- "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6",
- "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41",
- "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298",
- "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce",
- "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
- "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
+ "sha256:0258bb84b4a1e015f14f891d91957042fa88f6f4e86cc0808d735ebbc1e3fc88",
+ "sha256:07e86649773e486a21e170d1396217e15833776d9e8f4a7121c28a1d37e032c9",
+ "sha256:0ae718e9db4b622072ff73d38bc9cd7711edfedc8a1e08efe25a6c8170446da4",
+ "sha256:10c596bce5e9dd379c68c17208716da2767bb6f6f2a71d748f9e4c247ced31e6",
+ "sha256:11801d9e96f39286ab558c6db940c39fc00150450ae1007d18b35437d2f79ad7",
+ "sha256:170c2080966721b42c5a8726e91c5fc271300a4ac9ddf8a5b79856cfd47553e1",
+ "sha256:17deb7d218a5ae9f05d2b19d51936231546973303747924fc17a2869aef0029a",
+ "sha256:19843e4505069085301c3126c91b4e48970070fb242d7c617fb6777e83b55541",
+ "sha256:1c040af9eb9b12602b4b714b90a1c2ac1109e939498d47b0748ec33e7a948747",
+ "sha256:21751e4b7737aaf7261a068758b22f7670155099592b28d8dde340bf6874313d",
+ "sha256:252d4a254f1566012b94e35cba577a001d3a732fa91e824d2076233222232cf9",
+ "sha256:27e89e7befc785a273cccb105840db54b7f93005adf4e68c516d57b19ea2aac2",
+ "sha256:2d39193900a03b900a25d474b9f787434f05a282b402f063d4ca02c62d61bdb9",
+ "sha256:38c1a56a30b953e3543662f950f498cfb17afed214b27f4fc497728fb623e0c9",
+ "sha256:39b61340ff2dcd99d5ded0ca5fc33c878d89a1426e2f7b6dbc7c7381e330bc8a",
+ "sha256:3a284bbf6503cd6ac1183b3542fe853a8be47fb52a631224f6dda46ba229d572",
+ "sha256:40c34aeecccb9474999839299c9d2d5ff46a62ed47c58645b7965f48944abd74",
+ "sha256:436dcbbe3104737e8b4e2d63a019a764d107d72d6b6ee3cd107097c1c263fd1e",
+ "sha256:4732a0bf877bbd69d4d1b38a3db2160252acb31894a48f324fd54f742f6b2123",
+ "sha256:4b51f5eb47e61c6b82cb044a1815903a77a4f840fa050fd2ff40d617c102d16c",
+ "sha256:4beaac5047317a73b27cf15b4f4e0d2abaafa8378e1a6ed4cf9ff420d8f88aba",
+ "sha256:4c7a7e4ccec7164cdf2a9bbedc0e7430492eb56d9355a41377f40058c481bccc",
+ "sha256:4ced076af04e28761d486501c58259247c1882fd19c7f94c18a257d143248eee",
+ "sha256:4d2d0e458c32cdafd9a0f0b0aaeb61b169583d074287721eee740b730b7654bd",
+ "sha256:4fe297a52a8fc1204eef646bebf616263509d089d472e25742913924b1449099",
+ "sha256:5359811bfdb10fca234cba4629e555a1cde6c8136025395421f486ce43129ae3",
+ "sha256:5560b09304ebaac5323a7402f5090f2a8559843200014f5adf1ff7517dd3805b",
+ "sha256:6050b519fb3b62d68a28a1941ae9dc5122e8820fef2b8e20a65cb3c1577332a0",
+ "sha256:61fd1c55efb48ba734628f096e7a50baf0df3f18e91183face5c07fba3b4beb7",
+ "sha256:637e563d5cbf79d8b04224f99cfce8001146647e7ce198f0b032e32e62079e3c",
+ "sha256:646f150fa73f9cbc69419e34a1aae318c9f39bd9640760aa46624b2815da0c2d",
+ "sha256:649c5a1f0952af50f008f0bbec5f0b1e519150220c0a71ef80541a0c128d0c13",
+ "sha256:653e33f69202c00eca35416ee23091447ad1e9f9a556cc2b715b2befcfc31b3c",
+ "sha256:65927e75da4265ec88d06cbdab20113a9e69bbac3aea1ec053d4d940f1c88fc8",
+ "sha256:66eaf6d5ea5207177ba8ffb9ee479eea743292267caf1d6b89b51cf9d5885d23",
+ "sha256:69c20816ac2af11701caf10e5b027fd33c6e8dfe7806ab71bc5191aa2a6d50f9",
+ "sha256:6f45509b43d720d64837c1211fcdea42acd48e71539b7152d74c16413ceea080",
+ "sha256:6f45f296998043345ecfc4f69a51fa4f3e80ca3659864df80b459095580968a6",
+ "sha256:72cab67bcceb2e998da2f28aad9ec7b1a5ece5888f7ac3d3723cccba62338703",
+ "sha256:79f2acf237428dd61faa5b49247999ff68f45b3552c57303fcfabd2002eab249",
+ "sha256:7e0aab2d6e60aa9f9e14c83396b4a58fb4aded712806486c79189bcae4a175ac",
+ "sha256:7e25dc06e02689a45a49fa5e2f48bdfdbc11c5b52bef792a8cb37e0b82a7b0ae",
+ "sha256:816b9ea96e7cc2496a1ac9c4a76db670827c1e31045cc377c66e64a20bb4b3ff",
+ "sha256:82bc6f5b92c9fcd5b5d6506000dd433006b126b193932c52a9bcc10dcc10e4fc",
+ "sha256:86c56359fd7aca6a9ca41af91636aef15d5ad6d19e631ebd662f233c79f7e100",
+ "sha256:8781f5b91d75abef529a33cf3509ba5fe540d2814de0c4602f0f5ba6f1669739",
+ "sha256:8a42e246a03086ae1430f789e37d7192113db347417932745c4700d8999f853a",
+ "sha256:8a92781e466f2f1f9d38720d8920cb094bc0d59f88219591bc12b1c12c9d471c",
+ "sha256:8ceb101095f8cce9ac672ed7244b002d83ea97af7f27bb73f2fbe7fe8e8f03c7",
+ "sha256:8de0334c212e069d49952e476e16c6b42ba9677cc1e2d2f4588bd9a39489a3ab",
+ "sha256:90b4355779970e121c219def3e35533ec2b24773a26fc4aa0f8271dd262fa2f2",
+ "sha256:96add2a205efffe5e19a256a50be0ed78fcb5e9503242c65f57928e95cf4c901",
+ "sha256:9bd6b934794bea92a15b10ac35889df63b28d2abf9d020a7c87c05dd9c6e1edd",
+ "sha256:9f068136e5119f2ba939ecd45c47b4e3cf6dd7ca9a65b6078c838029c5c1f564",
+ "sha256:b04b6c04fe13e1e30ba6f9340d3d0fb776a7e52611d11809fb59341871e050e5",
+ "sha256:b3a437e3af246dd06d116f1615cdf4e620e639dfcc923fe3045e00f6a967fc27",
+ "sha256:b5bd33ac8a572e2aa94b489dec35b0c00ca554b27e56ad19953e0bf2cbcf3ad8",
+ "sha256:b61732d75e2222a3b0060b97395df78693d5c3487fe4a5d0b75f6ac1affc68b9",
+ "sha256:b8e7415b0952b0dd6df3aa2d37b5191c85e54d6a0ac1449ddb1e9039bbb39fa5",
+ "sha256:b901e68f3a6da279388e5dbe8d3bc562dd6dd3ff8a4b90e4f62e94de36461777",
+ "sha256:b964d81db8f11a99552621acd24c97381a0fd401a57187ce9f8cb9a53f4b6f4e",
+ "sha256:bbf80c686e3f63d40b0ab42d3605d3b6d415c368a5d8a9764a314ebda6138650",
+ "sha256:c1d85dfdf37a8df0e0174fc0c762b485b80a2fc7ce9592ae109aaf4a5d45ba9a",
+ "sha256:c2b197e3613c3aef3933b2c6eb095bd4be9c84022aea52057697b709b400c4bc",
+ "sha256:c5a47c964c58c044a323336a798d8729722e09865d7e087eb3512df6146b39a8",
+ "sha256:c7336fddae533cbe786360d7a0316c71fe96313872c06cde20a969765202ab04",
+ "sha256:c7d8d0ca7b4f6136f8a29845d31cfbc3f562cbe71f26da6fca55aa4977e45a18",
+ "sha256:c9632cd480fbc09c14622038a9a5f2f21ef6ce35892e9fa4df8d3308d3f2cedf",
+ "sha256:cd43dbaa73322a0c125122114cbc2c37141353b971751d05798f3b9780091e90",
+ "sha256:cf6d85c1ffb4ec4a859b2f31cd8845e633f91ed971a3cce6f59a722dcc361b8c",
+ "sha256:cfc5e923828714f314737e7f856b3dccf8805e5679fe23f07241b397cd785f6c",
+ "sha256:d2d6e4caaffaf42faf14cfdf20b1d6fff6b557137b44e9569ea6f1877e6f375d",
+ "sha256:d304746e2163d3d2cbc4c08925539e00d2bb3edc9e79fce531b5468d4e264d15",
+ "sha256:d3d60e2af4ce93d6e45a50a9b5795156a8725495e411c7987a2f81ab14e99665",
+ "sha256:d64b2d90302f0dd9e9ba43e89f8640f35b6d5968668da82ba2d2652b2cc3c3d2",
+ "sha256:d67429ff99231137491d8c3daa097c767a9c273bb03ac412ed8f6acb89e2e52f",
+ "sha256:d9145d011b74bef972b485a09f391babaa101626dbb54afc2313d5682a746593",
+ "sha256:db59afa0edf194bea782e4686bfc496fc1cea2e24f310d769641e343d14cc929",
+ "sha256:e51e3fa176fecd19660f898c4238232e8ca0f5709e6451a664c996f9aec1b8e1",
+ "sha256:ea6f0f98e1721741b5bc3167a495a9f16459fe67648054be05365a67e67c29ba",
+ "sha256:ec060d6db9576f6723b5290448aea67160608556b5506eb947997d9d1ca6f7b7",
+ "sha256:ef2aa0485735c8608a92964e52ab9025ceb6003776184a1eb5d1701742cc910b",
+ "sha256:f14cccf931c859ba3169d766e892a3673a79649ec2ceca7ba95ea376b23fd222",
+ "sha256:f15e48545dadf3760220821d2f3c850e0c67bbc66aad2776c9d716e6216b5103",
+ "sha256:f4300e063045e11ee79b79a7c9426813ab8d97e340b15843374093225dde407d",
+ "sha256:f448146b86a8693dda5f02bb4cb2ef65c894db2cf743e7bf351978354ce685e3",
+ "sha256:f60fad285db733b2badba43f7036a1241cb3e19c17260348f3ff702e6eaa4980",
+ "sha256:f8b3233c1de155743ef34b0cae494e33befed5e0adba77762f5d8a8e417c5015",
+ "sha256:fbc960cd91e55e2281e1a330e7d1c4970b6a05567dd973c96e412b4d012e17c6"
],
- "index": "pypi",
- "version": "==2.0.0"
+ "version": "==2.1.1"
},
"httptools": {
"hashes": [
@@ -766,11 +811,11 @@
},
"nltk": {
"hashes": [
- "sha256:3306502f487aa9fb0566e23443fa287a85a8d8d0821e2ef1655b4e3f0ea4aeee",
- "sha256:74b30826a37d78d53427105bbd037dd880251be269fca64ee530838a46ed55fc"
+ "sha256:1834da3d0682cba4f2cede2f9aad6b0fafb6461ba451db0efb6f9c39798d64d3",
+ "sha256:fd5c9109f976fa86bcadba8f91e47f5e9293bd034474752e92a520f81c93dda5"
],
"index": "pypi",
- "version": "==3.8"
+ "version": "==3.8.1"
},
"numpy": {
"hashes": [
@@ -808,19 +853,19 @@
},
"ocrmypdf": {
"hashes": [
- "sha256:77fdd52cb925bb94291ae62d22f2cb8253fb4199d0bf33b43e7fb7f078cac51c",
- "sha256:a34d621cadd4bf8ba15fe41db1f9cb315bcd3cfbd41a218a5df922c04e2e9541"
+ "sha256:68521c088b6b2fb1a77d6e72468e1484d68f0e948aa4d507f5db14449b70c5f1",
+ "sha256:ae9b07a858a50df58b172560b2a565d9864340ac82f9c8ac3f422905a25d3393"
],
"index": "pypi",
- "version": "==14.0.1"
+ "version": "==14.0.2"
},
"packaging": {
"hashes": [
- "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
- "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
+ "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
+ "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.7'",
- "version": "==22.0"
+ "version": "==23.0"
},
"pathvalidate": {
"hashes": [
@@ -832,11 +877,11 @@
},
"pdf2image": {
"hashes": [
- "sha256:84f79f2b8fad943e36323ea4e937fcb05f26ded0caa0a01181df66049e42fb65",
- "sha256:d58ed94d978a70c73c2bb7fdf8acbaf2a7089c29ff8141be5f45433c0c4293bb"
+ "sha256:1469335050a17657f94c2f1ef3a23e57807d631ad5bcbaec997c2c42a8186f4a",
+ "sha256:86761091eee35f4641ea98dfddb254254361d018be698a199aff7c1d37331803"
],
"index": "pypi",
- "version": "==1.16.0"
+ "version": "==1.16.2"
},
"pdfminer.six": {
"hashes": [
@@ -848,109 +893,124 @@
},
"pikepdf": {
"hashes": [
- "sha256:005e6908ab572cde909873ca5177013c066167567a3ae53520e0fe5b1197f8f0",
- "sha256:1495449007b34985409650f1f99bd9d469a1c1d90d43dd576aee2ad60c7788ab",
- "sha256:28803e1b730ffa65e4668baefc8a602c2d6f620f1caea6cb25672b15b1e9473b",
- "sha256:37370179e4f742623f08c6d7d70322ee167be8605053eb6f23ad6135fecea9db",
- "sha256:38107a9048cd6a6b0146b7227e6acef11a0c6975f61fe9a14020e226fb88ec27",
- "sha256:40a649c71ec36795ee9d487024fbcc2f483b868ef37a5398dc49a3c6823fbbcf",
- "sha256:4acbf4e711ce93e8130f38ede1e497d5fe35b4c1eaa8628ffa1ca21284b66d4e",
- "sha256:4c331fc1bb8ffdb5f04502610d1f010fffceea122d9f8429c35e4e0207afc36b",
- "sha256:78c5c9476edf890e4bb078c446a86f676ff78f49fc33d2f10cac90affe84a1b3",
- "sha256:799e981f03718a9d9a7db1467121e987f21582925c049287572c9f982f8d77ea",
- "sha256:7b59f96e8ce9faa35e1ebce662cf33ad33a3351bd6026136c849170876139ca0",
- "sha256:7e461cc756765544b85cec43b2af99609732d27680bb96e6fc9c81b2d27944cc",
- "sha256:80f31a4565e17f5f65dc38f1a5b41d469cc7ce1449b32c2a1fb36cccc746a99a",
- "sha256:87c7ca31ab818121edd4d9f12d81c1b56f61eec56ada1644d68ec8b10bf27622",
- "sha256:87d04107a048537ca132b88e12b4a9d1236362ccd0d3351b80da42f31fcfcda5",
- "sha256:897c0bc83c03b8e65153afb482a790d4b0e7adf603bc11de6293aff3c123a457",
- "sha256:8c8a622c2bd82b4e86fd174f5739abd95872733d0d50760d664a5abcc5b5b533",
- "sha256:96eb80ce4f9661987033b73bf0106cf7cc804745e8381436cda7f5cc7224b8b5",
- "sha256:980d49f443f872728cfab76d56c5a330b049e58c5e47354281fb410dde81c1ad",
- "sha256:9e25cb1efb4242080f8bed1d51f7541a39b8124df8342ef01970e8fa69a7594b",
- "sha256:a43f8d8d389d7b5999a5ef76623fa88ec54791ad746d77409ec6956ce8de6a34",
- "sha256:a68da2e0be0264442eead7fa1e63aab63a491c80ba9c5eee527ff99ec5883bcc",
- "sha256:ac0ef9a15d125157955f5001d3eaf7dc62f37863d2172e12434b9efad4b89bf0",
- "sha256:acc9f49f3b45f158295c7539268d88d00b41f660d4aa42dd507a1469ce81f0eb",
- "sha256:b4b05498f2d80478302cdece2385ff4b4ce62874f883622a52f0efee8cfdb68e",
- "sha256:bb7a7da762976e43ff96cdb3056997280ba47b45ac319cce89a709f34c24ed58",
- "sha256:bda12c977eaee121c34c0d3869a0cd87c95c42ee3c5b9e1c5a66b88c8beb3886",
- "sha256:c42eef99232f6aef231466bf9ebeddc47f81784e7e04df25ba6dce482f45ee0b",
- "sha256:c80d5455965171ad60db38e1a10e063a03e40e4549f8d616c186bfdfcc40475a",
- "sha256:de30dbf6e847bbac7df424d98e550a97ab0cea9b327eddc95d4c26350b20022b",
- "sha256:e2252fb75f6b8b6441b880505e8f2484492821b8f3c8a94e628b8104bbeeb66f",
- "sha256:ecd7ada50e556d6364553f80106e05a3fc11dcba5e45337684e85108d18e8560",
- "sha256:fffdfd4535952756b1f078fa98be5c3da06728f2afc9dff39aed56419a5d1bf6"
+ "sha256:07517bb61cd3fc9b936aa96f5475e5dc94314332ae79de0536b3c249ecc95998",
+ "sha256:0db08ae929f033397fd022faab82887bbe6f8ac00ae6a9a9e787d58a369ec30c",
+ "sha256:16cc3b448cfb1a995871e1aa4e693b6f5c085f00e326cec6514624b8e05a8c2c",
+ "sha256:1e328e664dffb154435b655bf1cdba0a1aa11400340c2e370b8e4d52cf909d9f",
+ "sha256:28f3ab6fca2ec0ab5da334bb2226e52dd51f266d97957eddadc96ebab60d18cc",
+ "sha256:3afdd4ca2ed39addd62d117c38f277b93d10e623a197a0f186733574efba026d",
+ "sha256:3f981177fbb9d9125056d92904a8bcc11aef5bea5cf4744490ec7aa72cc6421b",
+ "sha256:405a1137590061749fd90cd8c8b73991782ae4891113465f02189bf04862885c",
+ "sha256:40edd45db5d2175d4f5cfc7972da78fcb84808d920fff585c13faea1d65445b4",
+ "sha256:52b105b5877cbac87cceb645ff946b7bfb66c0dba206dcc52eadde4465a02043",
+ "sha256:5423ee3f35b60b27cbd16ec973d9947e885c7f62561f53b9bf97af7026df12e2",
+ "sha256:63797bf5f0a13aabca2a3310ac414065ba25b3bae0cdc74e8f028a3fc8910ca4",
+ "sha256:72660d2b07183b523467a9ed31cc05891f7b155710c62a0e6fc52c9e7817105c",
+ "sha256:77c010ef65f36c6670893bde727a2bf89f72845f1695d602c35e1a92f92d57a0",
+ "sha256:83c45b11c982d9b0c0856e53f86a4fddf4a2920c39cd40590b91e82a66043353",
+ "sha256:90463e9b47183847c0156b0453480aac8aac5e036ccef00bf1ea040a1bcefec8",
+ "sha256:9bb2720ea8f389d0a02b562f63c74ae430bb6ac15b74e2ce5c611150b3ee6057",
+ "sha256:a1cd426e36b4ced1801adbc08a52ef9cd86ef8d99ecaacff5a15cb901b6baec0",
+ "sha256:a72fb9f814cef22473e136447689b1167c0b7d6b62cd699c8397f7d15378fb23",
+ "sha256:c7806507865b41644f7b96464f81a1ad56d6444e12cb09de5ac7071ab745c1d2",
+ "sha256:ccf25468bba622b8e92c2ecceb7d9a5933a0ec74b0dcdac0cf2c4d49742fe113",
+ "sha256:cd351a55560d6bc2d543c4448ba4dcca92c1b50121d2d00113193406edb67150",
+ "sha256:db814ab6ea2746984f37d09b57f4e62788eb93491f2a8257ad53f943d346bae1",
+ "sha256:dbae22fadb5f781e54539e2d88bd8c6f93fe1f465c0e657ea468c55175d79033",
+ "sha256:e05513d9d1dc781eeb58a3357d53da0b3c9a582fab58e696d57ea4e88422f896",
+ "sha256:e4b688e2122d5b44de1d83e4f2dba1e689d0fcdc7508f1774de64f6333256ebd",
+ "sha256:e52f8472b7fa26491abbca5baf0225996b7c17bc1de5e664de33e0b022e647e7",
+ "sha256:e5a710949b1bb49e8c1d4869797388b7d6ee97ece107f3b7e7797d26bcc3fdca",
+ "sha256:ea91ebe608a973e9ce78abc26daba0db56b7b6ba78156232adf6cacefd14180a",
+ "sha256:eda70f609f1918c540d274e4ebe31d604681ba7c6495be5d6f8f3043c6a51500",
+ "sha256:edceacd206ee52e48da9381615188e1e9928dfc73dbf94283933c9f9b0cf02c3",
+ "sha256:ef1bc379b6a367aa6048f72ef27a1f3f5b7c93d45517dc0adf9f70bf76c75092",
+ "sha256:f11ec2dda35bc6cdb7b496e1253282de923200d183424611810e4e3f01643adc",
+ "sha256:f155773851703aa986d4b5ad466466c2151fc31f27c8e038e95946cf651bdeca",
+ "sha256:f17a97eb9f89c3837c32336d0c4beaa53bea438e83658467b9c5aa780ab199e7",
+ "sha256:fae94f86904ead60b7f675118507f4840535a11c206d66e68b938285585a82d0",
+ "sha256:fb03b2214687b5c1354d854e14961cec72b181c3cbd80520c6fbcc456e63d222",
+ "sha256:fd4b193a1ba3108c4fb7b4ee962d349fd42e20dc25b8205cc442f88f4cdb111a",
+ "sha256:ff4c60b6fd701a28e5d0ede49dfa3691115c4434e196901e139dcf12ac025c43"
],
"index": "pypi",
- "version": "==6.2.6"
+ "version": "==6.2.8.post1"
},
"pillow": {
"hashes": [
- "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040",
- "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8",
- "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65",
- "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2",
- "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627",
- "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07",
- "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef",
- "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535",
- "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c",
- "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc",
- "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3",
- "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1",
- "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c",
- "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa",
- "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32",
- "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502",
- "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4",
- "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f",
- "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812",
- "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636",
- "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20",
- "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c",
- "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91",
- "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe",
- "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b",
- "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad",
- "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9",
- "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72",
- "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4",
- "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de",
- "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29",
- "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee",
- "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c",
- "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7",
- "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11",
- "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c",
- "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c",
- "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448",
- "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b",
- "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20",
- "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228",
- "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd",
- "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699",
- "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b",
- "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2",
- "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4",
- "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c",
- "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f",
- "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2",
- "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c",
- "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3",
- "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193",
- "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48",
- "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02",
- "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8",
- "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e",
- "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f",
- "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b",
- "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74",
- "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb",
- "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"
+ "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b",
+ "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e",
+ "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35",
+ "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153",
+ "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9",
+ "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569",
+ "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57",
+ "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8",
+ "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1",
+ "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264",
+ "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157",
+ "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9",
+ "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133",
+ "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9",
+ "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab",
+ "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6",
+ "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5",
+ "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df",
+ "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503",
+ "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b",
+ "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa",
+ "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327",
+ "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493",
+ "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d",
+ "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4",
+ "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4",
+ "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35",
+ "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2",
+ "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c",
+ "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011",
+ "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a",
+ "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e",
+ "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f",
+ "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57",
+ "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9",
+ "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5",
+ "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d",
+ "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e",
+ "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815",
+ "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0",
+ "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b",
+ "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd",
+ "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c",
+ "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3",
+ "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab",
+ "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858",
+ "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5",
+ "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee",
+ "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343",
+ "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb",
+ "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47",
+ "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed",
+ "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837",
+ "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286",
+ "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28",
+ "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628",
+ "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df",
+ "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d",
+ "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d",
+ "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a",
+ "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6",
+ "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336",
+ "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132",
+ "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070",
+ "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe",
+ "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a",
+ "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd",
+ "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391",
+ "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a",
+ "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"
],
"index": "pypi",
- "version": "==9.3.0"
+ "version": "==9.4.0"
},
"pluggy": {
"hashes": [
@@ -1048,10 +1108,10 @@
},
"pyopenssl": {
"hashes": [
- "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968",
- "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"
+ "sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f",
+ "sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0"
],
- "version": "==22.1.0"
+ "version": "==23.0.0"
},
"python-dateutil": {
"hashes": [
@@ -1254,12 +1314,11 @@
"hiredis"
],
"hashes": [
- "sha256:7b8c87d19c45d3f1271b124858d2a5c13160c4e74d4835e28273400fa34d5228",
- "sha256:cae3ee5d1f57d8caf534cd8764edf3163c77e073bdd74b6f54a87ffafdc5e7d9"
+ "sha256:a721fd4d715fcd947848ed8fa02c2efd8224279979e0b721d9fdac6c4db35e93",
+ "sha256:f7a870c44868ab87bbecd6211c6d7c8720b1e9a796b743fbc4725d7ec75651c3"
],
"index": "pypi",
- "markers": null,
- "version": "==4.4.0"
+ "version": "==4.4.1"
},
"regex": {
"hashes": [
@@ -1597,10 +1656,11 @@
},
"tika": {
"hashes": [
- "sha256:c2c50f405622f74531841104f9e85c17511aede11de8e5385eab1a29a31f191b"
+ "sha256:3b136ae517db6c69c5ddee3a6a5c98e8966fedfc7c9155ebaaf3b9269121f992",
+ "sha256:56670eb812944eb25ed73f1b3b075aa41e7a135b74b240822f28b819e5b373da"
],
"index": "pypi",
- "version": "==1.24"
+ "version": "==2.6.0"
},
"tornado": {
"hashes": [
@@ -1687,7 +1747,6 @@
"sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"
],
"index": "pypi",
- "markers": null,
"version": "==0.20.0"
},
"uvloop": {
@@ -1735,37 +1794,37 @@
},
"watchdog": {
"hashes": [
- "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60",
- "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5",
- "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37",
- "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a",
- "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3",
- "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1",
- "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1",
- "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c",
- "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f",
- "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7",
- "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa",
- "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1",
- "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f",
- "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b",
- "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01",
- "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090",
- "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6",
- "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba",
- "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6",
- "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512",
- "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61",
- "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9",
- "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4",
- "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e",
- "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318",
- "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e",
- "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca",
- "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"
+ "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1",
+ "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e",
+ "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9",
+ "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e",
+ "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3",
+ "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd",
+ "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3",
+ "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7",
+ "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3",
+ "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9",
+ "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d",
+ "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c",
+ "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce",
+ "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640",
+ "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e",
+ "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344",
+ "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260",
+ "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8",
+ "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae",
+ "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73",
+ "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c",
+ "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f",
+ "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b",
+ "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8",
+ "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646",
+ "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e",
+ "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661",
+ "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"
],
"index": "pypi",
- "version": "==2.2.0"
+ "version": "==2.2.1"
},
"watchfiles": {
"hashes": [
@@ -1880,11 +1939,11 @@
},
"whitenoise": {
"hashes": [
- "sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2",
- "sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567"
+ "sha256:cf8ecf56d86ba1c734fdb5ef6127312e39e92ad5947fef9033dc9e43ba2777d9",
+ "sha256:fe0af31504ab08faa1ec7fc02845432096e40cc1b27e6a7747263d7b30fb51fa"
],
"index": "pypi",
- "version": "==6.2.0"
+ "version": "==6.3.0"
},
"whoosh": {
"hashes": [
@@ -2116,11 +2175,11 @@
},
"faker": {
"hashes": [
- "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc",
- "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76"
+ "sha256:4a8bc3cec832dde1928f8ce0817452bdadf63863d9e4d8307817247a38e51523",
+ "sha256:e15becbddc3a69a342e03ca6810caab7299e28e48106ae113a07f65c627d6fd7"
],
"markers": "python_version >= '3.7'",
- "version": "==15.3.4"
+ "version": "==16.1.0"
},
"filelock": {
"hashes": [
@@ -2139,11 +2198,11 @@
},
"identify": {
"hashes": [
- "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c",
- "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"
+ "sha256:0bc96b09c838310b6fcfcc61f78a981ea07f94836ef6ef553da5bb5d4745d662",
+ "sha256:e8a400c3062d980243d27ce10455a52832205649bbcaf27ffddb3dfaaf477bad"
],
"markers": "python_version >= '3.7'",
- "version": "==2.5.11"
+ "version": "==2.5.12"
},
"idna": {
"hashes": [
@@ -2171,10 +2230,11 @@
},
"iniconfig": {
"hashes": [
- "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
- "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+ "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
+ "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
],
- "version": "==1.1.1"
+ "markers": "python_version >= '3.7'",
+ "version": "==2.0.0"
},
"jinja2": {
"hashes": [
@@ -2256,11 +2316,11 @@
},
"mkdocs-material": {
"hashes": [
- "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7",
- "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"
+ "sha256:918fe38f504ca397b388b6c45445c22cb9acab61f00ade78d5f3edf299b6c9df",
+ "sha256:cedbbf84e156370489907d3c5b79999fcf6563f61a96965ec4c2513d303fa706"
],
"index": "pypi",
- "version": "==8.5.11"
+ "version": "==9.0.3"
},
"mkdocs-material-extensions": {
"hashes": [
@@ -2321,11 +2381,11 @@
},
"packaging": {
"hashes": [
- "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
- "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
+ "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
+ "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.7'",
- "version": "==22.0"
+ "version": "==23.0"
},
"pathspec": {
"hashes": [
@@ -2337,70 +2397,79 @@
},
"pillow": {
"hashes": [
- "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040",
- "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8",
- "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65",
- "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2",
- "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627",
- "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07",
- "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef",
- "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535",
- "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c",
- "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc",
- "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3",
- "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1",
- "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c",
- "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa",
- "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32",
- "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502",
- "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4",
- "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f",
- "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812",
- "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636",
- "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20",
- "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c",
- "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91",
- "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe",
- "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b",
- "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad",
- "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9",
- "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72",
- "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4",
- "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de",
- "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29",
- "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee",
- "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c",
- "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7",
- "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11",
- "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c",
- "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c",
- "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448",
- "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b",
- "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20",
- "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228",
- "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd",
- "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699",
- "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b",
- "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2",
- "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4",
- "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c",
- "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f",
- "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2",
- "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c",
- "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3",
- "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193",
- "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48",
- "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02",
- "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8",
- "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e",
- "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f",
- "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b",
- "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74",
- "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb",
- "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"
+ "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b",
+ "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e",
+ "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35",
+ "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153",
+ "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9",
+ "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569",
+ "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57",
+ "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8",
+ "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1",
+ "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264",
+ "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157",
+ "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9",
+ "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133",
+ "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9",
+ "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab",
+ "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6",
+ "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5",
+ "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df",
+ "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503",
+ "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b",
+ "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa",
+ "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327",
+ "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493",
+ "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d",
+ "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4",
+ "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4",
+ "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35",
+ "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2",
+ "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c",
+ "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011",
+ "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a",
+ "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e",
+ "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f",
+ "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57",
+ "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9",
+ "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5",
+ "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d",
+ "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e",
+ "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815",
+ "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0",
+ "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b",
+ "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd",
+ "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c",
+ "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3",
+ "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab",
+ "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858",
+ "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5",
+ "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee",
+ "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343",
+ "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb",
+ "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47",
+ "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed",
+ "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837",
+ "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286",
+ "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28",
+ "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628",
+ "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df",
+ "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d",
+ "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d",
+ "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a",
+ "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6",
+ "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336",
+ "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132",
+ "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070",
+ "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe",
+ "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a",
+ "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd",
+ "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391",
+ "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a",
+ "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"
],
"index": "pypi",
- "version": "==9.3.0"
+ "version": "==9.4.0"
},
"platformdirs": {
"hashes": [
@@ -2428,11 +2497,11 @@
},
"pygments": {
"hashes": [
- "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
- "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"
+ "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297",
+ "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"
],
"markers": "python_version >= '3.6'",
- "version": "==2.13.0"
+ "version": "==2.14.0"
},
"pymdown-extensions": {
"hashes": [
@@ -2731,11 +2800,11 @@
},
"termcolor": {
"hashes": [
- "sha256:67cee2009adc6449c650f6bcf3bdeed00c8ba53a8cda5362733c53e0a39fb70b",
- "sha256:fa852e957f97252205e105dd55bbc23b419a70fec0085708fc0515e399f304fd"
+ "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7",
+ "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"
],
"markers": "python_version >= '3.7'",
- "version": "==2.1.1"
+ "version": "==2.2.0"
},
"tomli": {
"hashes": [
@@ -2771,37 +2840,37 @@
},
"watchdog": {
"hashes": [
- "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60",
- "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5",
- "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37",
- "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a",
- "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3",
- "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1",
- "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1",
- "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c",
- "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f",
- "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7",
- "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa",
- "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1",
- "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f",
- "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b",
- "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01",
- "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090",
- "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6",
- "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba",
- "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6",
- "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512",
- "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61",
- "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9",
- "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4",
- "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e",
- "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318",
- "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e",
- "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca",
- "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"
+ "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1",
+ "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e",
+ "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9",
+ "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e",
+ "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3",
+ "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd",
+ "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3",
+ "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7",
+ "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3",
+ "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9",
+ "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d",
+ "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c",
+ "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce",
+ "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640",
+ "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e",
+ "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344",
+ "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260",
+ "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8",
+ "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae",
+ "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73",
+ "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c",
+ "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f",
+ "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b",
+ "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8",
+ "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646",
+ "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e",
+ "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661",
+ "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"
],
"index": "pypi",
- "version": "==2.2.0"
+ "version": "==2.2.1"
},
"zipp": {
"hashes": [
From eb8f37d8462b43a8277633d89301f316ee68dddc Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Sat, 31 Dec 2022 13:16:07 -0800
Subject: [PATCH 11/32] Allows scheduling tasks via cron
---
src/paperless/settings.py | 71 +++++++++++++++++++++++++++------------
1 file changed, 49 insertions(+), 22 deletions(-)
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
index cc1b9e096..55c95018d 100644
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -5,6 +5,7 @@ import multiprocessing
import os
import re
import tempfile
+from typing import Dict
from typing import Final
from typing import Optional
from typing import Set
@@ -107,6 +108,51 @@ def _parse_redis_url(env_redis: Optional[str]) -> Tuple[str]:
return (env_redis, env_redis)
+def _parse_beat_schedule() -> Dict:
+ schedule = {}
+ tasks = [
+ {
+ "name": "Check all e-mail accounts",
+ "env_key": "PAPERLESS_EMAIL_TASK_CRON",
+ # Default every ten minutes
+ "env_default": "*/10 * * * *",
+ "task": "paperless_mail.tasks.process_mail_accounts",
+ },
+ {
+ "name": "Train the classifier",
+ "env_key": "PAPERLESS_TRAIN_TASK_CRON",
+ # Default hourly at 5 minutes past the hour
+ "env_default": "5 */1 * * *",
+ "task": "documents.tasks.train_classifier",
+ },
+ {
+ "name": "Optimize the index",
+ "env_key": "PAPERLESS_INDEX_TASK_CRON",
+ # Default daily at midnight
+ "env_default": "0 0 * * *",
+ "task": "documents.tasks.index_optimize",
+ },
+ {
+ "name": "Perform sanity check",
+ "env_key": "PAPERLESS_SANITY_TASK_CRON",
+ # Default Sunday at 00:30
+ "env_default": "30 0 * * sun",
+ "task": "documents.tasks.sanity_check",
+ },
+ ]
+ for task in tasks:
+ value = os.getenv(task["env_key"], task["env_default"])
+ if value == "disable":
+ continue
+ minute, hour, day_month, month, day_week = value.split(" ")
+ schedule[task["name"]] = {
+ "task": task["task"],
+ "schedule": crontab(minute, hour, day_week, day_month, month),
+ }
+
+ return schedule
+
+
# NEVER RUN WITH DEBUG IN PRODUCTION.
DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO")
@@ -530,29 +576,10 @@ CELERY_RESULT_EXTENDED = True
CELERY_RESULT_BACKEND = "django-db"
CELERY_CACHE_BACKEND = "default"
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-schedule
+CELERY_BEAT_SCHEDULE = _parse_beat_schedule()
-CELERY_BEAT_SCHEDULE = {
- # Every ten minutes
- "Check all e-mail accounts": {
- "task": "paperless_mail.tasks.process_mail_accounts",
- "schedule": crontab(minute="*/10"),
- },
- # Hourly at 5 minutes past the hour
- "Train the classifier": {
- "task": "documents.tasks.train_classifier",
- "schedule": crontab(minute="5", hour="*/1"),
- },
- # Daily at midnight
- "Optimize the index": {
- "task": "documents.tasks.index_optimize",
- "schedule": crontab(minute=0, hour=0),
- },
- # Weekly, Sunday at 00:30
- "Perform sanity check": {
- "task": "documents.tasks.sanity_check",
- "schedule": crontab(minute=30, hour=0, day_of_week="sun"),
- },
-}
+# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-schedule-filename
CELERY_BEAT_SCHEDULE_FILENAME = os.path.join(DATA_DIR, "celerybeat-schedule.db")
# django setting.
From 19ab62c06ceb372845d0e8e59688abc5990d4964 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Thu, 5 Jan 2023 08:04:32 -0800
Subject: [PATCH 12/32] Adds documentation of how to configure tasks
---
docs/configuration.md | 37 ++++++++++++++++++++++++++++++++++++-
docs/usage.md | 2 +-
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index 7e47f2178..bc259a5c4 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -713,10 +713,45 @@ for details on how to set it.
used during automatic classification. If disabled, paperless will
still preform some basic text pre-processing before matching.
-See also `PAPERLESS_NLTK_DIR`.
+: See also `PAPERLESS_NLTK_DIR`.
Defaults to 1.
+`PAPERLESS_EMAIL_TASK_CRON=`
+
+: Configures the scheduled email fetching frequency. The value
+should be a valid crontab(5) expression describing when to run.
+
+: If set to the string "disable", no emails will be fetched automatically.
+
+ Defaults to `*/10 * * * *` or every ten minutes.
+
+`PAPERLESS_TRAIN_TASK_CRON=`
+
+: Configures the scheduled automatic classifier training frequency. The value
+should be a valid crontab(5) expression describing when to run.
+
+: If set to the string "disable", the classifier will not be trained automatically.
+
+ Defaults to `5 */1 * * *` or every hour at 5 minutes past the hour.
+
+`PAPERLESS_INDEX_TASK_CRON=`
+
+: Configures the scheduled search index update frequency. The value
+should be a valid crontab(5) expression describing when to run.
+
+: If set to the string "disable", the search index will not be automatically updated.
+
+ Defaults to `0 0 * * *` or daily at midnight.
+
+`PAPERLESS_SANITY_TASK_CRON=`
+
+: Configures the scheduled sanity checker frequency.
+
+: If set to the string "disable", the sanity checker will not run automatically.
+
+ Defaults to `30 0 * * sun` or Sunday at 30 minutes past midnight.
+
## Polling {#polling}
`PAPERLESS_CONSUMER_POLLING=`
diff --git a/docs/usage.md b/docs/usage.md
index db5af042b..79769c822 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -191,7 +191,7 @@ different means. These are as follows:
them further.
Paperless is set up to check your mails every 10 minutes. This can be
-configured on the 'Scheduled tasks' page in the admin.
+configured via `PAPERLESS_EMAIL_TASK_CRON` (see [software tweaks](/configuration#software_tweaks))
### REST API
From 9763b72f8126dea707c0b90632e24223bf2a573f Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Thu, 5 Jan 2023 08:25:35 -0800
Subject: [PATCH 13/32] Adds testing coverage of parsing the celery beat
schedule
---
src/paperless/settings.py | 6 ++
src/paperless/tests/test_settings.py | 130 +++++++++++++++++++++++++++
2 files changed, 136 insertions(+)
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
index 55c95018d..79a7a553b 100644
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -141,9 +141,15 @@ def _parse_beat_schedule() -> Dict:
},
]
for task in tasks:
+ # Either get the environment setting or use the default
value = os.getenv(task["env_key"], task["env_default"])
+ # Don't add disabled tasks to the schedule
if value == "disable":
continue
+ # I find https://crontab.guru/ super helpful
+ # crontab(5) format
+ # - five time-and-date fields
+ # - separated by at least one blank
minute, hour, day_month, month, day_week = value.split(" ")
schedule[task["name"]] = {
"task": task["task"],
diff --git a/src/paperless/tests/test_settings.py b/src/paperless/tests/test_settings.py
index 71926542d..a8b5cd11f 100644
--- a/src/paperless/tests/test_settings.py
+++ b/src/paperless/tests/test_settings.py
@@ -1,7 +1,10 @@
import datetime
+import os
from unittest import mock
from unittest import TestCase
+from celery.schedules import crontab
+from paperless.settings import _parse_beat_schedule
from paperless.settings import _parse_ignore_dates
from paperless.settings import _parse_redis_url
from paperless.settings import default_threads_per_worker
@@ -139,3 +142,130 @@ class TestIgnoreDateParsing(TestCase):
]:
result = _parse_redis_url(input)
self.assertTupleEqual(expected, result)
+
+ def test_schedule_configuration_default(self):
+ """
+ GIVEN:
+ - No configured task schedules
+ WHEN:
+ - The celery beat schedule is built
+ THEN:
+ - The default schedule is returned
+ """
+ schedule = _parse_beat_schedule()
+
+ self.assertDictEqual(
+ {
+ "Check all e-mail accounts": {
+ "task": "paperless_mail.tasks.process_mail_accounts",
+ "schedule": crontab(minute="*/10"),
+ },
+ "Train the classifier": {
+ "task": "documents.tasks.train_classifier",
+ "schedule": crontab(minute="5", hour="*/1"),
+ },
+ "Optimize the index": {
+ "task": "documents.tasks.index_optimize",
+ "schedule": crontab(minute=0, hour=0),
+ },
+ "Perform sanity check": {
+ "task": "documents.tasks.sanity_check",
+ "schedule": crontab(minute=30, hour=0, day_of_week="sun"),
+ },
+ },
+ schedule,
+ )
+
+ def test_schedule_configuration_changed(self):
+ """
+ GIVEN:
+ - Email task is configured non-default
+ WHEN:
+ - The celery beat schedule is built
+ THEN:
+ - The email task is configured per environment
+ - The default schedule is returned for other tasks
+ """
+ with mock.patch.dict(
+ os.environ,
+ {"PAPERLESS_EMAIL_TASK_CRON": "*/50 * * * mon"},
+ ):
+ schedule = _parse_beat_schedule()
+
+ self.assertDictEqual(
+ {
+ "Check all e-mail accounts": {
+ "task": "paperless_mail.tasks.process_mail_accounts",
+ "schedule": crontab(minute="*/50", day_of_week="mon"),
+ },
+ "Train the classifier": {
+ "task": "documents.tasks.train_classifier",
+ "schedule": crontab(minute="5", hour="*/1"),
+ },
+ "Optimize the index": {
+ "task": "documents.tasks.index_optimize",
+ "schedule": crontab(minute=0, hour=0),
+ },
+ "Perform sanity check": {
+ "task": "documents.tasks.sanity_check",
+ "schedule": crontab(minute=30, hour=0, day_of_week="sun"),
+ },
+ },
+ schedule,
+ )
+
+ def test_schedule_configuration_disabled(self):
+ """
+ GIVEN:
+ - Search index task is disabled
+ WHEN:
+ - The celery beat schedule is built
+ THEN:
+ - The search index task is not present
+ - The default schedule is returned for other tasks
+ """
+ with mock.patch.dict(os.environ, {"PAPERLESS_INDEX_TASK_CRON": "disable"}):
+ schedule = _parse_beat_schedule()
+
+ self.assertDictEqual(
+ {
+ "Check all e-mail accounts": {
+ "task": "paperless_mail.tasks.process_mail_accounts",
+ "schedule": crontab(minute="*/10"),
+ },
+ "Train the classifier": {
+ "task": "documents.tasks.train_classifier",
+ "schedule": crontab(minute="5", hour="*/1"),
+ },
+ "Perform sanity check": {
+ "task": "documents.tasks.sanity_check",
+ "schedule": crontab(minute=30, hour=0, day_of_week="sun"),
+ },
+ },
+ schedule,
+ )
+
+ def test_schedule_configuration_disabled_all(self):
+ """
+ GIVEN:
+ - All tasks are disabled
+ WHEN:
+ - The celery beat schedule is built
+ THEN:
+ - No tasks are scheduled
+ """
+ with mock.patch.dict(
+ os.environ,
+ {
+ "PAPERLESS_EMAIL_TASK_CRON": "disable",
+ "PAPERLESS_TRAIN_TASK_CRON": "disable",
+ "PAPERLESS_SANITY_TASK_CRON": "disable",
+ "PAPERLESS_INDEX_TASK_CRON": "disable",
+ },
+ ):
+ schedule = _parse_beat_schedule()
+
+ self.assertDictEqual(
+ {},
+ schedule,
+ )
From 2460c3e076cc07f07a5c0018af70425c985804ad Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Tue, 10 Jan 2023 14:36:32 -0800
Subject: [PATCH 14/32] Correctly split up the test cases
---
src/paperless/tests/test_settings.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/paperless/tests/test_settings.py b/src/paperless/tests/test_settings.py
index a8b5cd11f..f6d25f6fd 100644
--- a/src/paperless/tests/test_settings.py
+++ b/src/paperless/tests/test_settings.py
@@ -63,6 +63,8 @@ class TestIgnoreDateParsing(TestCase):
self._parse_checker(test_cases)
+
+class TestThreadCalculation(TestCase):
def test_workers_threads(self):
"""
GIVEN:
@@ -87,6 +89,8 @@ class TestIgnoreDateParsing(TestCase):
self.assertLessEqual(default_workers * default_threads, i)
+
+class TestRedisSocketConversion(TestCase):
def test_redis_socket_parsing(self):
"""
GIVEN:
@@ -143,6 +147,8 @@ class TestIgnoreDateParsing(TestCase):
result = _parse_redis_url(input)
self.assertTupleEqual(expected, result)
+
+class TestCeleryScheduleParsing(TestCase):
def test_schedule_configuration_default(self):
"""
GIVEN:
From a88b318d7dcbc4960f5e808a1f256e66b2251e0c Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Mon, 26 Dec 2022 13:33:43 -0800
Subject: [PATCH 15/32] Simplifies file upload naming to use the document name,
instead in needing to keep it around
---
src/documents/tests/test_api.py | 9 +++++++--
src/documents/views.py | 19 +++++++++----------
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
index dace48578..4547288d2 100644
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -7,6 +7,7 @@ import tempfile
import urllib.request
import uuid
import zipfile
+from pathlib import Path
from unittest import mock
from unittest.mock import MagicMock
@@ -808,7 +809,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
m.assert_called_once()
args, kwargs = m.call_args
- self.assertEqual(kwargs["override_filename"], "simple.pdf")
+ file_path = Path(args[0])
+ self.assertEqual(file_path.name, "simple.pdf")
+ self.assertIn(Path(settings.SCRATCH_DIR), file_path.parents)
self.assertIsNone(kwargs["override_title"])
self.assertIsNone(kwargs["override_correspondent_id"])
self.assertIsNone(kwargs["override_document_type_id"])
@@ -833,7 +836,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
m.assert_called_once()
args, kwargs = m.call_args
- self.assertEqual(kwargs["override_filename"], "simple.pdf")
+ file_path = Path(args[0])
+ self.assertEqual(file_path.name, "simple.pdf")
+ self.assertIn(Path(settings.SCRATCH_DIR), file_path.parents)
self.assertIsNone(kwargs["override_title"])
self.assertIsNone(kwargs["override_correspondent_id"])
self.assertIsNone(kwargs["override_document_type_id"])
diff --git a/src/documents/views.py b/src/documents/views.py
index 46cf06cfd..147dd2e07 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -7,6 +7,7 @@ import urllib
import uuid
import zipfile
from datetime import datetime
+from pathlib import Path
from time import mktime
from unicodedata import normalize
from urllib.parse import quote
@@ -623,20 +624,18 @@ class PostDocumentView(GenericAPIView):
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
- with tempfile.NamedTemporaryFile(
- prefix="paperless-upload-",
- dir=settings.SCRATCH_DIR,
- delete=False,
- ) as f:
- f.write(doc_data)
- os.utime(f.name, times=(t, t))
- temp_filename = f.name
+ temp_file_path = Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR)) / Path(
+ doc_name,
+ )
+
+ temp_file_path.write_bytes(doc_data)
+
+ os.utime(temp_file_path, times=(t, t))
task_id = str(uuid.uuid4())
async_task = consume_file.delay(
- temp_filename,
- override_filename=doc_name,
+ temp_file_path,
override_title=title,
override_correspondent_id=correspondent_id,
override_document_type_id=document_type_id,
From efaa1c4dd77fd327a55a9c0d20a2c08125e0f56b Mon Sep 17 00:00:00 2001
From: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
Date: Mon, 26 Dec 2022 13:43:30 -0800
Subject: [PATCH 16/32] Fixes minor depracation I noticed
---
src/documents/signals/handlers.py | 2 +-
src/documents/views.py | 8 +++++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py
index 4c2554f02..cd42c9030 100644
--- a/src/documents/signals/handlers.py
+++ b/src/documents/signals/handlers.py
@@ -447,7 +447,7 @@ def update_filename_and_move_files(sender, instance, **kwargs):
)
except (OSError, DatabaseError, CannotMoveFilesException) as e:
- logger.warn(f"Exception during file handling: {e}")
+ logger.warning(f"Exception during file handling: {e}")
# This happens when either:
# - moving the files failed due to file system errors
# - saving to the database failed due to database errors
diff --git a/src/documents/views.py b/src/documents/views.py
index 147dd2e07..abd2e5f2f 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -12,6 +12,7 @@ from time import mktime
from unicodedata import normalize
from urllib.parse import quote
+import pathvalidate
from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Case
@@ -625,7 +626,7 @@ class PostDocumentView(GenericAPIView):
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
temp_file_path = Path(tempfile.mkdtemp(dir=settings.SCRATCH_DIR)) / Path(
- doc_name,
+ pathvalidate.sanitize_filename(doc_name),
)
temp_file_path.write_bytes(doc_data)
@@ -634,8 +635,9 @@ class PostDocumentView(GenericAPIView):
task_id = str(uuid.uuid4())
- async_task = consume_file.delay(
- temp_file_path,
+ consume_file.delay(
+ # Paths are not JSON friendly
+ str(temp_file_path),
override_title=title,
override_correspondent_id=correspondent_id,
override_document_type_id=document_type_id,
From bba1fc7194f633c0d3e16ca4d5dd4a877f8aeb24 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Tue, 10 Jan 2023 15:01:43 -0800
Subject: [PATCH 17/32] Fixes merge conflict
---
src/documents/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/documents/views.py b/src/documents/views.py
index abd2e5f2f..6f0be2164 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -635,7 +635,7 @@ class PostDocumentView(GenericAPIView):
task_id = str(uuid.uuid4())
- consume_file.delay(
+ async_task = consume_file.delay(
# Paths are not JSON friendly
str(temp_file_path),
override_title=title,
From 9e333448088de82715584cde621e095b7bd1eef5 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Wed, 11 Jan 2023 07:33:04 -0800
Subject: [PATCH 18/32] Include the optional socket file in the release
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index daaadb42b..d83b2a3a0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -455,7 +455,7 @@ jobs:
cp paperless.conf.example dist/paperless-ngx/paperless.conf
cp gunicorn.conf.py dist/paperless-ngx/gunicorn.conf.py
cp -r docker/ dist/paperless-ngx/docker
- cp scripts/*.service scripts/*.sh dist/paperless-ngx/scripts/
+ cp scripts/*.service scripts/*.sh scripts/*.socket dist/paperless-ngx/scripts/
cp -r src/ dist/paperless-ngx/src
cp -r docs/_build/html/ dist/paperless-ngx/docs
mv static dist/paperless-ngx
From b25f083687994e37cfa794e84ea552225fe72147 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Thu, 12 Jan 2023 12:46:28 -0800
Subject: [PATCH 19/32] Updates the exporter to use pathlib and add a few more
tests for coverage
---
.../management/commands/document_consumer.py | 2 +-
.../management/commands/document_exporter.py | 93 +++++++++----------
.../tests/test_management_exporter.py | 59 ++++++++++++
3 files changed, 106 insertions(+), 48 deletions(-)
diff --git a/src/documents/management/commands/document_consumer.py b/src/documents/management/commands/document_consumer.py
index a405f590c..9107d574a 100644
--- a/src/documents/management/commands/document_consumer.py
+++ b/src/documents/management/commands/document_consumer.py
@@ -19,7 +19,7 @@ from watchdog.observers.polling import PollingObserver
try:
from inotifyrecursive import INotify, flags
-except ImportError:
+except ImportError: # pragma: nocover
INotify = flags = None
logger = logging.getLogger("paperless.management.consumer")
diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py
index 07b4643f2..3cd028f01 100644
--- a/src/documents/management/commands/document_exporter.py
+++ b/src/documents/management/commands/document_exporter.py
@@ -4,6 +4,9 @@ import os
import shutil
import tempfile
import time
+from pathlib import Path
+from typing import List
+from typing import Set
import tqdm
from django.conf import settings
@@ -96,16 +99,16 @@ class Command(BaseCommand):
def __init__(self, *args, **kwargs):
BaseCommand.__init__(self, *args, **kwargs)
- self.target = None
- self.files_in_export_dir = []
- self.exported_files = []
+ self.target: Path = None
+ self.files_in_export_dir: Set[Path] = set()
+ self.exported_files: List[Path] = []
self.compare_checksums = False
self.use_filename_format = False
self.delete = False
def handle(self, *args, **options):
- self.target = options["target"]
+ self.target = Path(options["target"]).resolve()
self.compare_checksums = options["compare_checksums"]
self.use_filename_format = options["use_filename_format"]
self.delete = options["delete"]
@@ -121,11 +124,14 @@ class Command(BaseCommand):
dir=settings.SCRATCH_DIR,
prefix="paperless-export",
)
- self.target = temp_dir.name
+ self.target = Path(temp_dir.name).resolve()
- if not os.path.exists(self.target):
+ if not self.target.exists():
raise CommandError("That path doesn't exist")
+ if not self.target.is_dir():
+ raise CommandError("That path isn't a directory")
+
if not os.access(self.target, os.W_OK):
raise CommandError("That path doesn't appear to be writable")
@@ -152,10 +158,9 @@ class Command(BaseCommand):
def dump(self, progress_bar_disable=False):
# 1. Take a snapshot of what files exist in the current export folder
- for root, dirs, files in os.walk(self.target):
- self.files_in_export_dir.extend(
- map(lambda f: os.path.abspath(os.path.join(root, f)), files),
- )
+ for x in self.target.glob("**/*"):
+ if x.is_file():
+ self.files_in_export_dir.add(x.resolve())
# 2. Create manifest, containing all correspondents, types, tags, storage paths
# comments, documents and ui_settings
@@ -238,16 +243,16 @@ class Command(BaseCommand):
# 3.3. write filenames into manifest
original_name = base_name
- original_target = os.path.join(self.target, original_name)
+ original_target = (self.target / Path(original_name)).resolve()
document_dict[EXPORTER_FILE_NAME] = original_name
thumbnail_name = base_name + "-thumbnail.webp"
- thumbnail_target = os.path.join(self.target, thumbnail_name)
+ thumbnail_target = (self.target / Path(thumbnail_name)).resolve()
document_dict[EXPORTER_THUMBNAIL_NAME] = thumbnail_name
if document.has_archive_version:
archive_name = base_name + "-archive.pdf"
- archive_target = os.path.join(self.target, archive_name)
+ archive_target = (self.target / Path(archive_name)).resolve()
document_dict[EXPORTER_ARCHIVE_NAME] = archive_name
else:
archive_target = None
@@ -256,24 +261,21 @@ class Command(BaseCommand):
t = int(time.mktime(document.created.timetuple()))
if document.storage_type == Document.STORAGE_TYPE_GPG:
- os.makedirs(os.path.dirname(original_target), exist_ok=True)
- with open(original_target, "wb") as f:
- with document.source_file as out_file:
- f.write(GnuPG.decrypted(out_file))
- os.utime(original_target, times=(t, t))
+ original_target.parent.mkdir(parents=True, exist_ok=True)
+ with document.source_file as out_file:
+ original_target.write_bytes(GnuPG.decrypted(out_file))
+ os.utime(original_target, times=(t, t))
- os.makedirs(os.path.dirname(thumbnail_target), exist_ok=True)
- with open(thumbnail_target, "wb") as f:
- with document.thumbnail_file as out_file:
- f.write(GnuPG.decrypted(out_file))
- os.utime(thumbnail_target, times=(t, t))
+ thumbnail_target.parent.mkdir(parents=True, exist_ok=True)
+ with document.thumbnail_file as out_file:
+ thumbnail_target.write_bytes(GnuPG.decrypted(out_file))
+ os.utime(thumbnail_target, times=(t, t))
if archive_target:
- os.makedirs(os.path.dirname(archive_target), exist_ok=True)
- with open(archive_target, "wb") as f:
- with document.archive_path as out_file:
- f.write(GnuPG.decrypted(out_file))
- os.utime(archive_target, times=(t, t))
+ archive_target.parent.mkdir(parents=True, exist_ok=True)
+ with document.archive_path as out_file:
+ archive_target.write_bytes(GnuPG.decrypted(out_file))
+ os.utime(archive_target, times=(t, t))
else:
self.check_and_copy(
document.source_path,
@@ -291,16 +293,14 @@ class Command(BaseCommand):
)
# 4.1 write manifest to target folder
- manifest_path = os.path.abspath(os.path.join(self.target, "manifest.json"))
-
- with open(manifest_path, "w") as f:
- json.dump(manifest, f, indent=2)
+ manifest_path = (self.target / Path("manifest.json")).resolve()
+ manifest_path.write_text(json.dumps(manifest, indent=2))
# 4.2 write version information to target folder
- version_path = os.path.abspath(os.path.join(self.target, "version.json"))
-
- with open(version_path, "w") as f:
- json.dump({"version": version.__full_version_str__}, f, indent=2)
+ version_path = (self.target / Path("version.json")).resolve()
+ version_path.write_text(
+ json.dumps({"version": version.__full_version_str__}, indent=2),
+ )
if self.delete:
# 5. Remove files which we did not explicitly export in this run
@@ -309,25 +309,24 @@ class Command(BaseCommand):
self.files_in_export_dir.remove(manifest_path)
for f in self.files_in_export_dir:
- os.remove(f)
+ f.unlink()
delete_empty_directories(
- os.path.abspath(os.path.dirname(f)),
- os.path.abspath(self.target),
+ f.parent,
+ self.target,
)
- def check_and_copy(self, source, source_checksum, target):
- if os.path.abspath(target) in self.files_in_export_dir:
- self.files_in_export_dir.remove(os.path.abspath(target))
+ def check_and_copy(self, source, source_checksum, target: Path):
+ if target in self.files_in_export_dir:
+ self.files_in_export_dir.remove(target)
perform_copy = False
- if os.path.exists(target):
+ if target.exists():
source_stat = os.stat(source)
- target_stat = os.stat(target)
+ target_stat = target.stat()
if self.compare_checksums and source_checksum:
- with open(target, "rb") as f:
- target_checksum = hashlib.md5(f.read()).hexdigest()
+ target_checksum = hashlib.md5(target.read_bytes()).hexdigest()
perform_copy = target_checksum != source_checksum
elif source_stat.st_mtime != target_stat.st_mtime:
perform_copy = True
@@ -338,5 +337,5 @@ class Command(BaseCommand):
perform_copy = True
if perform_copy:
- os.makedirs(os.path.dirname(target), exist_ok=True)
+ target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py
index a24b292d7..5aff05793 100644
--- a/src/documents/tests/test_management_exporter.py
+++ b/src/documents/tests/test_management_exporter.py
@@ -8,6 +8,7 @@ from unittest import mock
from zipfile import ZipFile
from django.core.management import call_command
+from django.core.management.base import CommandError
from django.test import override_settings
from django.test import TestCase
from django.utils import timezone
@@ -438,3 +439,61 @@ class TestExportImport(DirectoriesMixin, TestCase):
self.assertEqual(len(zip.namelist()), 14)
self.assertIn("manifest.json", zip.namelist())
self.assertIn("version.json", zip.namelist())
+
+ def test_export_target_not_exists(self):
+ """
+ GIVEN:
+ - Request to export documents to directory that doesn't exist
+ WHEN:
+ - Export command is called
+ THEN:
+ - Error is raised
+ """
+ args = ["document_exporter", "/tmp/foo/bar"]
+
+ with self.assertRaises(CommandError) as e:
+
+ call_command(*args)
+
+ self.assertEqual("That path isn't a directory", str(e))
+
+ def test_export_target_exists_but_is_file(self):
+ """
+ GIVEN:
+ - Request to export documents to file instead of directory
+ WHEN:
+ - Export command is called
+ THEN:
+ - Error is raised
+ """
+
+ with tempfile.NamedTemporaryFile() as tmp_file:
+
+ args = ["document_exporter", tmp_file.name]
+
+ with self.assertRaises(CommandError) as e:
+
+ call_command(*args)
+
+ self.assertEqual("That path isn't a directory", str(e))
+
+ def test_export_target_not_writable(self):
+ """
+ GIVEN:
+ - Request to export documents to directory that's not writeable
+ WHEN:
+ - Export command is called
+ THEN:
+ - Error is raised
+ """
+ with tempfile.TemporaryDirectory() as tmp_dir:
+
+ os.chmod(tmp_dir, 0o000)
+
+ args = ["document_exporter", tmp_dir]
+
+ with self.assertRaises(CommandError) as e:
+
+ call_command(*args)
+
+ self.assertEqual("That path doesn't appear to be writable", str(e))
From c9683808c98682d0c638182644f86faa4e5bfbc9 Mon Sep 17 00:00:00 2001
From: Trenton H <797416+stumpylog@users.noreply.github.com>
Date: Thu, 12 Jan 2023 12:48:54 -0800
Subject: [PATCH 20/32] Add 2 warnings about potential path length issues
---
docs/administration.md | 6 ++++++
docs/advanced_usage.md | 9 ++++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/docs/administration.md b/docs/administration.md
index 320ad7a9c..4daa446c5 100644
--- a/docs/administration.md
+++ b/docs/administration.md
@@ -267,6 +267,12 @@ The filenames generated by this command follow the format
paperless to use `PAPERLESS_FILENAME_FORMAT` for exported filenames
instead, specify `--use-filename-format`.
+!!! warning
+
+ If exporting with the file name format, there may be errors due to
+ your operating system's maximum path lengths. Try adjusting the export
+ target or consider not using the filename format.
+
### Document importer {#importer}
The document importer takes the export produced by the [Document
diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md
index ef2cc28c2..61b1c072e 100644
--- a/docs/advanced_usage.md
+++ b/docs/advanced_usage.md
@@ -336,6 +336,13 @@ value.
However, keep in mind that inside docker, if files get stored outside of
the predefined volumes, they will be lost after a restart of paperless.
+!!! warning
+
+ When file naming handling, in particular when using `{tag_list}`,
+ you may run into the limits of your operating system's maximum
+ path lengths. Files will retain the previous path instead and
+ the issue logged.
+
## Storage paths
One of the best things in Paperless is that you can not only access the
@@ -392,7 +399,7 @@ structure as in the previous example above.
If you adjust the format of an existing storage path, old documents
don't get relocated automatically. You need to run the
[document renamer](/administration#renamer) to
- adjust their pathes.
+ adjust their paths.
## Celery Monitoring {#celery-monitoring}
From 133532a4631191d8a174cf1fbd2a5e1b4c4631d8 Mon Sep 17 00:00:00 2001
From: Michael Shamoon <4887959+shamoon@users.noreply.github.com>
Date: Sun, 15 Jan 2023 15:06:35 -0800
Subject: [PATCH 21/32] Better Handle arbitrary ISO 8601 strings with
dateutil.parser.isoparse
---
src/documents/tasks.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/documents/tasks.py b/src/documents/tasks.py
index b5dc264fb..0168b42ba 100644
--- a/src/documents/tasks.py
+++ b/src/documents/tasks.py
@@ -3,10 +3,10 @@ import logging
import os
import shutil
import uuid
-from datetime import datetime
from pathlib import Path
from typing import Type
+import dateutil.parser
import tqdm
from asgiref.sync import async_to_sync
from celery import shared_task
@@ -105,7 +105,7 @@ def consume_file(
# More types will be retained through JSON encode/decode
if override_created is not None and isinstance(override_created, str):
try:
- override_created = datetime.fromisoformat(override_created)
+ override_created = dateutil.parser.isoparse(override_created)
except Exception:
pass
From 22142203ce258118628092fc112c5834ff05252c Mon Sep 17 00:00:00 2001
From: Peter Kappelt
Date: Mon, 16 Jan 2023 09:57:23 +0100
Subject: [PATCH 22/32] Fix formatting of config variable in docs
---
docs/configuration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index bc259a5c4..1b361f9aa 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -848,7 +848,7 @@ PAPERLESS_CONSUMER_ENABLE_BARCODES has been enabled.
Defaults to false.
-PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
+`PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT`
: Defines the string to be detected as a separator barcode. If
paperless is used with the PATCH-T separator pages, users shouldn't
From 94f0808a2ffc9890b48d1dbb38e06e64f0f17e55 Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 14:15:45 +0100
Subject: [PATCH 23/32] add AppleMail color tag support
---
src/paperless_mail/mail.py | 75 ++++++++++++++++++++++++++++++++++++--
1 file changed, 71 insertions(+), 4 deletions(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index 9ac03db6e..fffa07ac9 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -1,3 +1,4 @@
+import itertools
import os
import re
import tempfile
@@ -25,6 +26,29 @@ from imap_tools.mailbox import MailBoxTls
from paperless_mail.models import MailAccount
from paperless_mail.models import MailRule
+# Apple Mail sets multiple IMAP KEYWORD and the general "\Flagged" FLAG
+
+# imaplib => conn.fetch(b"", "FLAGS")
+
+# no flag - (FLAGS (\\Seen $NotJunk NotJunk))'
+# red - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk))'
+# orange - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0))'
+# violet - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0 $MailFlagBit2))'
+# blue - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit2))'
+# yellow - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit1))'
+# green - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0 $MailFlagBit1))'
+# grey - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit1 $MailFlagBit2))'
+
+APPLE_MAIL_TAG_COLORS = {
+ "red": [],
+ "orange": ["$MailFlagBit0"],
+ "yellow": ["$MailFlagBit1"],
+ "blue": ["$MailFlagBit2"],
+ "green": ["$MailFlagBit0", "$MailFlagBit1"],
+ "violet": ["$MailFlagBit0", "$MailFlagBit2"],
+ "grey": ["$MailFlagBit1", "$MailFlagBit2"],
+}
+
class MailError(Exception):
pass
@@ -66,18 +90,61 @@ class FlagMailAction(BaseMailAction):
class TagMailAction(BaseMailAction):
def __init__(self, parameter):
- self.keyword = parameter
+
+ # The custom tag should look like "apple:"
+ if "apple" in parameter.lower():
+ try:
+ _, self.color = parameter.split(":")
+ self.color = self.color.strip()
+
+ if not self.color.lower() in APPLE_MAIL_TAG_COLORS.keys():
+ raise MailError("Not a valid AppleMail tag color.")
+ except Exception as e:
+ raise MailError(
+ """Could not parse the parameters.
+ Make sure they look like this: apple:""",
+ ) from e
+ self.keyword = None
+
+ else:
+ self.keyword = parameter
def get_criteria(self):
+
+ # AppleMail: We only need to check if mails are \Flagged
+ if self.color:
+ return {"flagged": False}
+
return {"no_keyword": self.keyword, "gmail_label": self.keyword}
def post_consume(self, M: MailBox, message_uids, parameter):
if re.search(r"gmail\.com$|googlemail\.com$", M._host):
for uid in message_uids:
M.client.uid("STORE", uid, "X-GM-LABELS", self.keyword)
- else:
+
+ # AppleMail
+ elif self.color:
+
+ # Remove all existing $MailFlagBits
+ M.flag(
+ message_uids,
+ set(itertools.chain(*APPLE_MAIL_TAG_COLORS.values())),
+ False,
+ )
+
+ # Set new $MailFlagBits
+ M.flag(message_uids, APPLE_MAIL_TAG_COLORS.get(self.color), True)
+
+ # Set the general \Flagged
+ # This defaults to the "red" flag and "stars" in Thunderbird or GMail
+ M.flag(message_uids, [MailMessageFlags.FLAGGED], True)
+
+ elif self.keyword:
M.flag(message_uids, [self.keyword], True)
+ else:
+ raise MailError("No keyword specified.")
+
def get_rule_action(rule) -> BaseMailAction:
if rule.action == MailRule.MailAction.FLAG:
@@ -197,14 +264,14 @@ class MailAccountHandler(LoggingMixin):
try:
M.login_utf8(account.username, account.password)
- except Exception as err:
+ except Exception as e:
self.log(
"error",
"Unable to authenticate with mail server using AUTH=PLAIN",
)
raise MailError(
f"Error while authenticating account {account}",
- ) from err
+ ) from e
except Exception as e:
self.log(
"error",
From 195f3a5dbfc04c8c281eea4e63a22627661d72a3 Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 14:33:18 +0100
Subject: [PATCH 24/32] update documentation
---
docs/usage.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/usage.md b/docs/usage.md
index 79769c822..41c7f31ea 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -151,6 +151,8 @@ different means. These are as follows:
will not consume mails already tagged. Not all mail servers support
this feature!
+ - **AppleMail support:** AppleMail allows differently colored tags. For this to work use `apple:` (e.g. "apple:green") as a custom tag. Available colors are "red", "orange", "yellow", "blue", "green", "violet" and "grey".
+
!!! warning
The mail consumer will perform these actions on all mails it has
From 6024a862d6dd7669a0d2a2d70c15c0ccc5f55241 Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 18:00:51 +0100
Subject: [PATCH 25/32] add basic tests and fix error
---
src/paperless_mail/mail.py | 6 +++--
src/paperless_mail/tests/test_mail.py | 39 +++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index fffa07ac9..d0cc64f1c 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -101,13 +101,15 @@ class TagMailAction(BaseMailAction):
raise MailError("Not a valid AppleMail tag color.")
except Exception as e:
raise MailError(
- """Could not parse the parameters.
- Make sure they look like this: apple:""",
+ """Could not parse parameters.
+ Make sure they look like this: apple: and
+ only use allowed colors.""",
) from e
self.keyword = None
else:
self.keyword = parameter
+ self.color = None
def get_criteria(self):
diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py
index 3a17d7ab3..309e846d4 100644
--- a/src/paperless_mail/tests/test_mail.py
+++ b/src/paperless_mail/tests/test_mail.py
@@ -24,6 +24,7 @@ from imap_tools import NOT
from paperless_mail import tasks
from paperless_mail.mail import MailAccountHandler
from paperless_mail.mail import MailError
+from paperless_mail.mail import TagMailAction
from paperless_mail.models import MailAccount
from paperless_mail.models import MailRule
@@ -674,6 +675,44 @@ class TestMail(DirectoriesMixin, TestCase):
self.assertEqual(len(self.bogus_mailbox.fetch(criteria, False)), 0)
self.assertEqual(len(self.bogus_mailbox.messages), 3)
+ def test_tag_mail_action_applemail_wrong_input(self):
+
+ self.assertRaises(
+ MailError,
+ TagMailAction,
+ "apple:black",
+ )
+ self.assertRaises(
+ MailError,
+ TagMailAction,
+ "applegreen",
+ )
+
+ def test_handle_mail_account_tag_applemail(self):
+ # all mails will be FLAGGED afterwards
+
+ account = MailAccount.objects.create(
+ name="test",
+ imap_server="",
+ username="admin",
+ password="secret",
+ )
+
+ _ = MailRule.objects.create(
+ name="testrule",
+ account=account,
+ action=MailRule.MailAction.TAG,
+ action_parameter="apple:green",
+ )
+
+ self.assertEqual(len(self.bogus_mailbox.messages), 3)
+ self.assertEqual(self.async_task.call_count, 0)
+ self.assertEqual(len(self.bogus_mailbox.fetch("UNFLAGGED", False)), 2)
+ self.mail_account_handler.handle_mail_account(account)
+ self.assertEqual(self.async_task.call_count, 2)
+ self.assertEqual(len(self.bogus_mailbox.fetch("UNFLAGGED", False)), 0)
+ self.assertEqual(len(self.bogus_mailbox.messages), 3)
+
def test_error_login(self):
account = MailAccount.objects.create(
name="test",
From 6fe5674ac3b5b4ca3118d5bc4a873336df2c8904 Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 18:18:00 +0100
Subject: [PATCH 26/32] better code documentation
---
src/paperless_mail/mail.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index d0cc64f1c..a4610911b 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -33,10 +33,10 @@ from paperless_mail.models import MailRule
# no flag - (FLAGS (\\Seen $NotJunk NotJunk))'
# red - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk))'
# orange - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0))'
-# violet - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0 $MailFlagBit2))'
-# blue - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit2))'
# yellow - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit1))'
+# blue - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit2))'
# green - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0 $MailFlagBit1))'
+# violet - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit0 $MailFlagBit2))'
# grey - (FLAGS (\\Flagged \\Seen $NotJunk NotJunk $MailFlagBit1 $MailFlagBit2))'
APPLE_MAIL_TAG_COLORS = {
@@ -138,7 +138,9 @@ class TagMailAction(BaseMailAction):
M.flag(message_uids, APPLE_MAIL_TAG_COLORS.get(self.color), True)
# Set the general \Flagged
- # This defaults to the "red" flag and "stars" in Thunderbird or GMail
+ # This defaults to the "red" flag in AppleMail and
+ # "stars" in Thunderbird or GMail
+
M.flag(message_uids, [MailMessageFlags.FLAGGED], True)
elif self.keyword:
From 93d272f50b46b2af49e7087ac309ba74fe07707a Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 18:19:36 +0100
Subject: [PATCH 27/32] remove unnecessary whitespaces
---
src/paperless_mail/mail.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index a4610911b..ca9a3079c 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -27,7 +27,6 @@ from paperless_mail.models import MailAccount
from paperless_mail.models import MailRule
# Apple Mail sets multiple IMAP KEYWORD and the general "\Flagged" FLAG
-
# imaplib => conn.fetch(b"", "FLAGS")
# no flag - (FLAGS (\\Seen $NotJunk NotJunk))'
@@ -140,7 +139,6 @@ class TagMailAction(BaseMailAction):
# Set the general \Flagged
# This defaults to the "red" flag in AppleMail and
# "stars" in Thunderbird or GMail
-
M.flag(message_uids, [MailMessageFlags.FLAGGED], True)
elif self.keyword:
From 7ed4dedd5edf48daa07628af13c0874f0d6c5a39 Mon Sep 17 00:00:00 2001
From: clemensrieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 23:51:38 +0100
Subject: [PATCH 28/32] Update src/paperless_mail/mail.py
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
---
src/paperless_mail/mail.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index ca9a3079c..4031f26d0 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -91,7 +91,7 @@ class TagMailAction(BaseMailAction):
def __init__(self, parameter):
# The custom tag should look like "apple:"
- if "apple" in parameter.lower():
+ if "apple:" in parameter.lower():
try:
_, self.color = parameter.split(":")
self.color = self.color.strip()
From a4829ce26abbbe56899a7546cf86f68d5343c8fb Mon Sep 17 00:00:00 2001
From: clemensrieder <68914047+clemensrieder@users.noreply.github.com>
Date: Wed, 11 Jan 2023 23:51:49 +0100
Subject: [PATCH 29/32] Update docs/usage.md
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
---
docs/usage.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/usage.md b/docs/usage.md
index 41c7f31ea..c630d5369 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -151,7 +151,7 @@ different means. These are as follows:
will not consume mails already tagged. Not all mail servers support
this feature!
- - **AppleMail support:** AppleMail allows differently colored tags. For this to work use `apple:` (e.g. "apple:green") as a custom tag. Available colors are "red", "orange", "yellow", "blue", "green", "violet" and "grey".
+ - **Apple Mail support:** Apple Mail clients allow differently colored tags. For this to work use `apple:` (e.g. "apple:green") as a custom tag. Available colors are "red", "orange", "yellow", "blue", "green", "violet" and "grey".
!!! warning
From dee691b72bb97bf35c3a8bcb1aaa76c8652ef374 Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Thu, 12 Jan 2023 00:16:08 +0100
Subject: [PATCH 30/32] replace quotation marks with italics
---
docs/usage.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/usage.md b/docs/usage.md
index c630d5369..56fcf6a86 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -151,7 +151,7 @@ different means. These are as follows:
will not consume mails already tagged. Not all mail servers support
this feature!
- - **Apple Mail support:** Apple Mail clients allow differently colored tags. For this to work use `apple:` (e.g. "apple:green") as a custom tag. Available colors are "red", "orange", "yellow", "blue", "green", "violet" and "grey".
+ - **Apple Mail support:** Apple Mail clients allow differently colored tags. For this to work use `apple:` (e.g. _apple:green_) as a custom tag. Available colors are _red_, _orange_, _yellow_, _blue_, _green_, _violet_ and _grey_.
!!! warning
From 959f80604a326579bc17cdf1e823bcbc37898efb Mon Sep 17 00:00:00 2001
From: Clemens Rieder <68914047+clemensrieder@users.noreply.github.com>
Date: Thu, 12 Jan 2023 09:36:55 +0100
Subject: [PATCH 31/32] Remove try/except + test
Changes in d064ff5 made try/except unnecessary and the subsequent test failed.
---
src/paperless_mail/mail.py | 17 ++++++-----------
src/paperless_mail/tests/test_mail.py | 5 -----
2 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py
index 4031f26d0..04caca63c 100644
--- a/src/paperless_mail/mail.py
+++ b/src/paperless_mail/mail.py
@@ -92,18 +92,13 @@ class TagMailAction(BaseMailAction):
# The custom tag should look like "apple:"
if "apple:" in parameter.lower():
- try:
- _, self.color = parameter.split(":")
- self.color = self.color.strip()
- if not self.color.lower() in APPLE_MAIL_TAG_COLORS.keys():
- raise MailError("Not a valid AppleMail tag color.")
- except Exception as e:
- raise MailError(
- """Could not parse parameters.
- Make sure they look like this: apple: and
- only use allowed colors.""",
- ) from e
+ _, self.color = parameter.split(":")
+ self.color = self.color.strip()
+
+ if not self.color.lower() in APPLE_MAIL_TAG_COLORS.keys():
+ raise MailError("Not a valid AppleMail tag color.")
+
self.keyword = None
else:
diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py
index 309e846d4..cd1001861 100644
--- a/src/paperless_mail/tests/test_mail.py
+++ b/src/paperless_mail/tests/test_mail.py
@@ -682,11 +682,6 @@ class TestMail(DirectoriesMixin, TestCase):
TagMailAction,
"apple:black",
)
- self.assertRaises(
- MailError,
- TagMailAction,
- "applegreen",
- )
def test_handle_mail_account_tag_applemail(self):
# all mails will be FLAGGED afterwards
From c4dbd58efd09fecfc1686befc4b7f99fe395f943 Mon Sep 17 00:00:00 2001
From: amo13
Date: Sat, 14 Jan 2023 15:37:27 +0100
Subject: [PATCH 32/32] Use correct canonical path for nltk_data
---
Dockerfile | 6 +++---
docs/configuration.md | 2 +-
src/paperless/settings.py | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index b616c70e7..687d993c7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -234,9 +234,9 @@ RUN set -eux \
&& echo "Installing Python requirements" \
&& python3 -m pip install --default-timeout=1000 --no-cache-dir --requirement requirements.txt \
&& echo "Installing NLTK data" \
- && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" snowball_data \
- && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" stopwords \
- && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/local/share/nltk_data" punkt \
+ && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
+ && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
+ && python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" punkt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \
diff --git a/docs/configuration.md b/docs/configuration.md
index 1b361f9aa..873db795e 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -179,7 +179,7 @@ Previously, the location defaulted to `PAPERLESS_DATA_DIR/nltk`.
Unless you are using this in a bare metal install or other setup,
this folder is no longer needed and can be removed manually.
-Defaults to `/usr/local/share/nltk_data`
+Defaults to `/usr/share/nltk_data`
## Logging
diff --git a/src/paperless/settings.py b/src/paperless/settings.py
index 79a7a553b..c5bb4801c 100644
--- a/src/paperless/settings.py
+++ b/src/paperless/settings.py
@@ -178,7 +178,7 @@ THUMBNAIL_DIR = os.path.join(MEDIA_ROOT, "documents", "thumbnails")
DATA_DIR = __get_path("PAPERLESS_DATA_DIR", os.path.join(BASE_DIR, "..", "data"))
-NLTK_DIR = __get_path("PAPERLESS_NLTK_DIR", "/usr/local/share/nltk_data")
+NLTK_DIR = __get_path("PAPERLESS_NLTK_DIR", "/usr/share/nltk_data")
TRASH_DIR = os.getenv("PAPERLESS_TRASH_DIR")