[project] name = "paperless-ngx" version = "2.15.0" description = "A community-supported supercharged version of paperless: scan, index and archive all your physical documents" readme = "README.md" requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] # TODO: Move certain things to groups and then utilize that further # This will allow testing to not install a webserver, mysql, etc dependencies = [ "bleach~=6.2.0", "celery[redis]~=5.4.0", "channels~=4.2", "channels-redis~=4.2", "concurrent-log-handler~=0.9.25", "dateparser~=1.2", # WARNING: django does not use semver. # Only patch versions are guaranteed to not introduce breaking changes. "django~=5.1.7", "django-allauth[socialaccount,mfa]~=65.4.0", "django-auditlog~=3.0.0", "django-celery-results~=2.5.1", "django-compression-middleware~=0.5.0", "django-cors-headers~=4.7.0", "django-extensions~=3.2.3", "django-filter~=25.1", "django-guardian~=2.4.0", "django-multiselectfield~=0.1.13", "django-soft-delete~=1.0.18", "djangorestframework~=3.15", "djangorestframework-guardian~=0.3.0", "drf-spectacular~=0.28", "drf-spectacular-sidecar~=2025.3.1", "drf-writable-nested~=0.7.1", "filelock~=3.17.0", "flower~=2.0.1", "gotenberg-client~=0.9.0", "httpx-oauth~=0.16", "imap-tools~=1.10.0", "inotifyrecursive~=0.3", "jinja2~=3.1.5", "langdetect~=1.0.9", "nltk~=3.9.1", "ocrmypdf~=16.10.0", "pathvalidate~=3.2.3", "pdf2image~=1.17.0", "python-dateutil~=2.9.0", "python-dotenv~=1.0.1", "python-gnupg~=0.5.4", "python-ipware~=3.0.0", "python-magic~=0.4.27", "pyzbar~=0.1.9", "rapidfuzz~=3.12.1", "redis[hiredis]~=5.2.1", "scikit-learn~=1.6.1", "setproctitle~=1.3.4", "tika-client~=0.9.0", "tqdm~=4.67.1", "watchdog~=6.0", "whitenoise~=6.9", "whoosh~=2.7", "zxing-cpp~=2.3.0", ] optional-dependencies.mariadb = [ "mysqlclient~=2.2.7", ] optional-dependencies.postgres = [ "psycopg[c]==3.2.5", # Direct dependency for proper resolution of the pre-built wheels "psycopg-c==3.2.5", ] optional-dependencies.webserver = [ "granian[uvloop]~=2.2.0", ] [dependency-groups] dev = [ { "include-group" = "docs" }, { "include-group" = "testing" }, { "include-group" = "lint" }, ] docs = [ "mkdocs-glightbox~=0.4.0", "mkdocs-material~=9.6.4", ] testing = [ "daphne", "factory-boy~=3.3.1", "imagehash", "pytest~=8.3.3", "pytest-cov~=6.0.0", "pytest-django~=4.10.0", "pytest-env", "pytest-httpx", "pytest-mock", "pytest-rerunfailures", "pytest-sugar", "pytest-xdist", ] lint = [ "pre-commit~=4.1.0", "pre-commit-uv~=4.1.3", "ruff~=0.9.9", ] typing = [ "celery-types", "django-filter-stubs", "django-stubs[compatible-mypy]", "djangorestframework-stubs[compatible-mypy]", "mypy", "types-bleach", "types-colorama", "types-dateparser", "types-markdown", "types-pygments", "types-python-dateutil", "types-redis", "types-setuptools", "types-tqdm", ] [tool.ruff] target-version = "py310" line-length = 88 src = [ "src", ] respect-gitignore = true # https://docs.astral.sh/ruff/settings/ fix = true show-fixes = true output-format = "grouped" # https://docs.astral.sh/ruff/rules/ lint.extend-select = [ "COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com "DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj "EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe "FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt "FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly "G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g "I", # https://docs.astral.sh/ruff/rules/#isort-i "ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn "INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc "PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie "PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl "PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl "PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth "Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q "RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse "RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf "SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim "T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20 "TC", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc "TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w ] lint.ignore = [ "DJ001", "RUF012", "SIM105", ] # Migrations lint.per-file-ignores."*/migrations/*.py" = [ "E501", "SIM", "T201", ] # Testing lint.per-file-ignores."*/tests/*.py" = [ "E501", "SIM117", ] lint.per-file-ignores.".github/scripts/*.py" = [ "E501", "INP001", "SIM117", ] # Docker specific lint.per-file-ignores."docker/rootfs/usr/local/bin/wait-for-redis.py" = [ "INP001", "T201", ] lint.per-file-ignores."docker/wait-for-redis.py" = [ "INP001", "T201", ] lint.per-file-ignores."src/documents/file_handling.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/management/commands/document_consumer.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/management/commands/document_exporter.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/models.py" = [ "SIM115", ] lint.per-file-ignores."src/documents/parsers.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/signals/handlers.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_consumer.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_file_handling.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_management.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_management_consumer.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_management_exporter.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_migration_archive_files.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_migration_document_pages_count.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_migration_mime_type.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/tests/test_sanity_check.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/documents/views.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/paperless/checks.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/paperless/settings.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/paperless/views.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/paperless_mail/mail.py" = [ "PTH", ] # TODO Enable & remove lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [ "PTH", "RUF001", ] # TODO PTH Enable & remove lint.isort.force-single-line = true [tool.pytest.ini_options] minversion = "8.0" pythonpath = [ "src", ] testpaths = [ "src/documents/tests/", "src/paperless/tests/", "src/paperless_mail/tests/", "src/paperless_tesseract/tests/", "src/paperless_tika/tests", ] addopts = [ "--pythonwarnings=all", "--cov", "--cov-report=html", "--cov-report=xml", "--numprocesses=auto", "--maxprocesses=16", "--quiet", "--durations=50", "--junitxml=junit.xml", "-o junit_family=legacy", ] norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ] DJANGO_SETTINGS_MODULE = "paperless.settings" [tool.pytest_env] PAPERLESS_DISABLE_DBHANDLER = "true" PAPERLESS_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache" [tool.coverage.run] source = [ "src/", ] omit = [ "*/tests/*", "manage.py", "paperless/wsgi.py", "paperless/auth.py", ] [tool.coverage.report] exclude_also = [ "if settings.AUDIT_LOG_ENABLED:", "if AUDIT_LOG_ENABLED:", "if TYPE_CHECKING:", ] [tool.mypy] plugins = [ "mypy_django_plugin.main", "mypy_drf_plugin.main", "numpy.typing.mypy_plugin", ] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true warn_redundant_casts = true warn_unused_ignores = true [tool.uv] required-version = ">=0.5.14" package = false environments = [ "sys_platform == 'darwin'", "sys_platform == 'linux'", ] [tool.uv.sources] # Markers are chosen to select these almost exclusively when building the Docker image psycopg-c = [ { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" }, { url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.5/psycopg_c-3.2.5-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" }, ] zxing-cpp = [ { url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" }, { url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" }, ] [tool.django-stubs] django_settings_module = "paperless.settings"