From a806280c1b06e03b9e6f7dea318b1b226b1b5a07 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:57:23 -0700 Subject: [PATCH] Moves the checks and tests to the main application and removes the old applications --- pyproject.toml | 2 - src/paperless/checks.py | 60 ++++++++++ src/paperless/settings/__init__.py | 2 - src/paperless/tests/test_checks.py | 116 +++++++++++++++++++ src/paperless_remote/__init__.py | 4 - src/paperless_remote/apps.py | 5 - src/paperless_remote/checks.py | 17 --- src/paperless_remote/tests/__init__.py | 0 src/paperless_remote/tests/test_checks.py | 24 ---- src/paperless_tesseract/__init__.py | 5 - src/paperless_tesseract/apps.py | 5 - src/paperless_tesseract/checks.py | 52 --------- src/paperless_tesseract/tests/__init__.py | 0 src/paperless_tesseract/tests/test_checks.py | 67 ----------- 14 files changed, 176 insertions(+), 183 deletions(-) delete mode 100644 src/paperless_remote/__init__.py delete mode 100644 src/paperless_remote/apps.py delete mode 100644 src/paperless_remote/checks.py delete mode 100644 src/paperless_remote/tests/__init__.py delete mode 100644 src/paperless_remote/tests/test_checks.py delete mode 100644 src/paperless_tesseract/__init__.py delete mode 100644 src/paperless_tesseract/apps.py delete mode 100644 src/paperless_tesseract/checks.py delete mode 100644 src/paperless_tesseract/tests/__init__.py delete mode 100644 src/paperless_tesseract/tests/test_checks.py diff --git a/pyproject.toml b/pyproject.toml index ccd3519f4..fdc802c4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -269,8 +269,6 @@ testpaths = [ "src/documents/tests/", "src/paperless/tests/", "src/paperless_mail/tests/", - "src/paperless_tesseract/tests/", - "src/paperless_remote/tests/", "src/paperless_ai/tests", ] diff --git a/src/paperless/checks.py b/src/paperless/checks.py index bcea6ef24..5f069b547 100644 --- a/src/paperless/checks.py +++ b/src/paperless/checks.py @@ -3,6 +3,7 @@ import os import pwd import shutil import stat +import subprocess from pathlib import Path from django.conf import settings @@ -299,3 +300,62 @@ def check_deprecated_db_settings( ) return warnings + + +@register() +def check_remote_parser_configured(app_configs, **kwargs) -> list[Error]: + if settings.REMOTE_OCR_ENGINE == "azureai" and not ( + settings.REMOTE_OCR_ENDPOINT and settings.REMOTE_OCR_API_KEY + ): + return [ + Error( + "Azure AI remote parser requires endpoint and API key to be configured.", + ), + ] + + return [] + + +def get_tesseract_langs(): + proc = subprocess.run( + [shutil.which("tesseract"), "--list-langs"], + capture_output=True, + ) + + # Decode bytes to string, split on newlines, trim out the header + proc_lines = proc.stdout.decode("utf8", errors="ignore").strip().split("\n")[1:] + + return [x.strip() for x in proc_lines] + + +@register() +def check_default_language_available(app_configs, **kwargs): + errs = [] + + if not settings.OCR_LANGUAGE: + errs.append( + Warning( + "No OCR language has been specified with PAPERLESS_OCR_LANGUAGE. " + "This means that tesseract will fallback to english.", + ), + ) + return errs + + # binaries_check in paperless will check and report if this doesn't exist + # So skip trying to do anything here and let that handle missing binaries + if shutil.which("tesseract") is not None: + installed_langs = get_tesseract_langs() + + specified_langs = [x.strip() for x in settings.OCR_LANGUAGE.split("+")] + + for lang in specified_langs: + if lang not in installed_langs: + errs.append( + Error( + f"The selected ocr language {lang} is " + f"not installed. Paperless cannot OCR your documents " + f"without it. Please fix PAPERLESS_OCR_LANGUAGE.", + ), + ) + + return errs diff --git a/src/paperless/settings/__init__.py b/src/paperless/settings/__init__.py index 4a120d861..1c33db7c6 100644 --- a/src/paperless/settings/__init__.py +++ b/src/paperless/settings/__init__.py @@ -121,9 +121,7 @@ INSTALLED_APPS = [ "django_extensions", "paperless", "documents.apps.DocumentsConfig", - "paperless_tesseract.apps.PaperlessTesseractConfig", "paperless_mail.apps.PaperlessMailConfig", - "paperless_remote.apps.PaperlessRemoteParserConfig", "django.contrib.admin", "rest_framework", "rest_framework.authtoken", diff --git a/src/paperless/tests/test_checks.py b/src/paperless/tests/test_checks.py index 3572f02a4..87e64a90e 100644 --- a/src/paperless/tests/test_checks.py +++ b/src/paperless/tests/test_checks.py @@ -5,6 +5,7 @@ from pathlib import Path from unittest import mock import pytest +from django.core.checks import ERROR from django.core.checks import Error from django.core.checks import Warning from pytest_django.fixtures import SettingsWrapper @@ -12,7 +13,9 @@ from pytest_mock import MockerFixture from paperless.checks import audit_log_check from paperless.checks import binaries_check +from paperless.checks import check_default_language_available from paperless.checks import check_deprecated_db_settings +from paperless.checks import check_remote_parser_configured from paperless.checks import check_v3_minimum_upgrade_version from paperless.checks import debug_mode_check from paperless.checks import paths_check @@ -626,3 +629,116 @@ class TestV3MinimumUpgradeVersionCheck: conn.introspection.table_names.side_effect = OperationalError("DB unavailable") mocker.patch.dict("paperless.checks.connections", {"default": conn}) assert check_v3_minimum_upgrade_version(None) == [] + + +class TestRemoteParserChecks: + def test_no_engine(self, settings: SettingsWrapper) -> None: + settings.REMOTE_OCR_ENGINE = None + msgs = check_remote_parser_configured(None) + + assert len(msgs) == 0 + + def test_azure_no_endpoint(self, settings: SettingsWrapper) -> None: + + settings.REMOTE_OCR_ENGINE = "azureai" + settings.REMOTE_OCR_API_KEY = "somekey" + settings.REMOTE_OCR_ENDPOINT = None + + msgs = check_remote_parser_configured(None) + + assert len(msgs) == 1 + + msg = msgs[0] + + assert ( + "Azure AI remote parser requires endpoint and API key to be configured." + in msg.msg + ) + + +class TestTesseractChecks: + def test_default_language(self) -> None: + check_default_language_available(None) + + def test_no_language(self, settings: SettingsWrapper) -> None: + + settings.OCR_LANGUAGE = "" + + msgs = check_default_language_available(None) + + assert len(msgs) == 1 + msg = msgs[0] + + assert ( + "No OCR language has been specified with PAPERLESS_OCR_LANGUAGE" in msg.msg + ) + + def test_invalid_language( + self, + settings: SettingsWrapper, + mocker: MockerFixture, + ) -> None: + + settings.OCR_LANGUAGE = "ita" + + tesser_lang_mock = mocker.patch("paperless.checks.get_tesseract_langs") + tesser_lang_mock.return_value = ["deu", "eng"] + + msgs = check_default_language_available(None) + + assert len(msgs) == 1 + msg = msgs[0] + + assert msg.level == ERROR + assert "The selected ocr language ita is not installed" in msg.msg + + def test_multi_part_language( + self, + settings: SettingsWrapper, + mocker: MockerFixture, + ) -> None: + """ + GIVEN: + - An OCR language which is multi part (ie chi-sim) + - The language is correctly formatted + WHEN: + - Installed packages are checked + THEN: + - No errors are reported + """ + + settings.OCR_LANGUAGE = "chi_sim" + + tesser_lang_mock = mocker.patch("paperless.checks.get_tesseract_langs") + tesser_lang_mock.return_value = ["chi_sim", "eng"] + + msgs = check_default_language_available(None) + + assert len(msgs) == 0 + + def test_multi_part_language_bad_format( + self, + settings: SettingsWrapper, + mocker: MockerFixture, + ) -> None: + """ + GIVEN: + - An OCR language which is multi part (ie chi-sim) + - The language is correctly NOT formatted + WHEN: + - Installed packages are checked + THEN: + - No errors are reported + """ + settings.OCR_LANGUAGE = "chi-sim" + + tesser_lang_mock = mocker.patch("paperless.checks.get_tesseract_langs") + tesser_lang_mock.return_value = ["chi_sim", "eng"] + + msgs = check_default_language_available(None) + + assert len(msgs) == 1 + msg = msgs[0] + + assert msg.level == ERROR + assert "The selected ocr language chi-sim is not installed" in msg.msg diff --git a/src/paperless_remote/__init__.py b/src/paperless_remote/__init__.py deleted file mode 100644 index 5380ea5ac..000000000 --- a/src/paperless_remote/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# this is here so that django finds the checks. -from paperless_remote.checks import check_remote_parser_configured - -__all__ = ["check_remote_parser_configured"] diff --git a/src/paperless_remote/apps.py b/src/paperless_remote/apps.py deleted file mode 100644 index 7417221ab..000000000 --- a/src/paperless_remote/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class PaperlessRemoteParserConfig(AppConfig): - name = "paperless_remote" diff --git a/src/paperless_remote/checks.py b/src/paperless_remote/checks.py deleted file mode 100644 index b9abb0592..000000000 --- a/src/paperless_remote/checks.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.conf import settings -from django.core.checks import Error -from django.core.checks import register - - -@register() -def check_remote_parser_configured(app_configs, **kwargs): - if settings.REMOTE_OCR_ENGINE == "azureai" and not ( - settings.REMOTE_OCR_ENDPOINT and settings.REMOTE_OCR_API_KEY - ): - return [ - Error( - "Azure AI remote parser requires endpoint and API key to be configured.", - ), - ] - - return [] diff --git a/src/paperless_remote/tests/__init__.py b/src/paperless_remote/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/paperless_remote/tests/test_checks.py b/src/paperless_remote/tests/test_checks.py deleted file mode 100644 index 0512fb257..000000000 --- a/src/paperless_remote/tests/test_checks.py +++ /dev/null @@ -1,24 +0,0 @@ -from unittest import TestCase - -from django.test import override_settings - -from paperless_remote import check_remote_parser_configured - - -class TestChecks(TestCase): - @override_settings(REMOTE_OCR_ENGINE=None) - def test_no_engine(self) -> None: - msgs = check_remote_parser_configured(None) - self.assertEqual(len(msgs), 0) - - @override_settings(REMOTE_OCR_ENGINE="azureai") - @override_settings(REMOTE_OCR_API_KEY="somekey") - @override_settings(REMOTE_OCR_ENDPOINT=None) - def test_azure_no_endpoint(self) -> None: - msgs = check_remote_parser_configured(None) - self.assertEqual(len(msgs), 1) - self.assertTrue( - msgs[0].msg.startswith( - "Azure AI remote parser requires endpoint and API key to be configured.", - ), - ) diff --git a/src/paperless_tesseract/__init__.py b/src/paperless_tesseract/__init__.py deleted file mode 100644 index cc0b886aa..000000000 --- a/src/paperless_tesseract/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# this is here so that django finds the checks. -from paperless_tesseract.checks import check_default_language_available -from paperless_tesseract.checks import get_tesseract_langs - -__all__ = ["check_default_language_available", "get_tesseract_langs"] diff --git a/src/paperless_tesseract/apps.py b/src/paperless_tesseract/apps.py deleted file mode 100644 index 830768c99..000000000 --- a/src/paperless_tesseract/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class PaperlessTesseractConfig(AppConfig): - name = "paperless_tesseract" diff --git a/src/paperless_tesseract/checks.py b/src/paperless_tesseract/checks.py deleted file mode 100644 index 0d7a1d90d..000000000 --- a/src/paperless_tesseract/checks.py +++ /dev/null @@ -1,52 +0,0 @@ -import shutil -import subprocess - -from django.conf import settings -from django.core.checks import Error -from django.core.checks import Warning -from django.core.checks import register - - -def get_tesseract_langs(): - proc = subprocess.run( - [shutil.which("tesseract"), "--list-langs"], - capture_output=True, - ) - - # Decode bytes to string, split on newlines, trim out the header - proc_lines = proc.stdout.decode("utf8", errors="ignore").strip().split("\n")[1:] - - return [x.strip() for x in proc_lines] - - -@register() -def check_default_language_available(app_configs, **kwargs): - errs = [] - - if not settings.OCR_LANGUAGE: - errs.append( - Warning( - "No OCR language has been specified with PAPERLESS_OCR_LANGUAGE. " - "This means that tesseract will fallback to english.", - ), - ) - return errs - - # binaries_check in paperless will check and report if this doesn't exist - # So skip trying to do anything here and let that handle missing binaries - if shutil.which("tesseract") is not None: - installed_langs = get_tesseract_langs() - - specified_langs = [x.strip() for x in settings.OCR_LANGUAGE.split("+")] - - for lang in specified_langs: - if lang not in installed_langs: - errs.append( - Error( - f"The selected ocr language {lang} is " - f"not installed. Paperless cannot OCR your documents " - f"without it. Please fix PAPERLESS_OCR_LANGUAGE.", - ), - ) - - return errs diff --git a/src/paperless_tesseract/tests/__init__.py b/src/paperless_tesseract/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/paperless_tesseract/tests/test_checks.py b/src/paperless_tesseract/tests/test_checks.py deleted file mode 100644 index ab3ba0c16..000000000 --- a/src/paperless_tesseract/tests/test_checks.py +++ /dev/null @@ -1,67 +0,0 @@ -from unittest import mock - -from django.core.checks import ERROR -from django.test import TestCase -from django.test import override_settings - -from paperless_tesseract import check_default_language_available - - -class TestChecks(TestCase): - def test_default_language(self) -> None: - check_default_language_available(None) - - @override_settings(OCR_LANGUAGE="") - def test_no_language(self) -> None: - msgs = check_default_language_available(None) - self.assertEqual(len(msgs), 1) - self.assertTrue( - msgs[0].msg.startswith( - "No OCR language has been specified with PAPERLESS_OCR_LANGUAGE", - ), - ) - - @override_settings(OCR_LANGUAGE="ita") - @mock.patch("paperless_tesseract.checks.get_tesseract_langs") - def test_invalid_language(self, m) -> None: - m.return_value = ["deu", "eng"] - msgs = check_default_language_available(None) - self.assertEqual(len(msgs), 1) - self.assertEqual(msgs[0].level, ERROR) - - @override_settings(OCR_LANGUAGE="chi_sim") - @mock.patch("paperless_tesseract.checks.get_tesseract_langs") - def test_multi_part_language(self, m) -> None: - """ - GIVEN: - - An OCR language which is multi part (ie chi-sim) - - The language is correctly formatted - WHEN: - - Installed packages are checked - THEN: - - No errors are reported - """ - m.return_value = ["chi_sim", "eng"] - - msgs = check_default_language_available(None) - - self.assertEqual(len(msgs), 0) - - @override_settings(OCR_LANGUAGE="chi-sim") - @mock.patch("paperless_tesseract.checks.get_tesseract_langs") - def test_multi_part_language_bad_format(self, m) -> None: - """ - GIVEN: - - An OCR language which is multi part (ie chi-sim) - - The language is correctly NOT formatted - WHEN: - - Installed packages are checked - THEN: - - No errors are reported - """ - m.return_value = ["chi_sim", "eng"] - - msgs = check_default_language_available(None) - - self.assertEqual(len(msgs), 1) - self.assertEqual(msgs[0].level, ERROR)