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")