Chore: Switch from pipenv to uv (#9251)

This commit is contained in:
Trenton H 2025-03-04 08:15:51 -08:00 committed by GitHub
parent 1bc77546eb
commit eb8e124971
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 4281 additions and 5034 deletions

View File

@ -76,18 +76,15 @@ RUN set -eux \
&& apt-get update \ && apt-get update \
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES} && apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES}
ARG PYTHON_PACKAGES="\ ARG PYTHON_PACKAGES="ca-certificates"
python3 \
python3-pip \
python3-wheel \
pipenv \
ca-certificates"
RUN set -eux \ RUN set -eux \
echo "Installing python packages" \ echo "Installing python packages" \
&& apt-get update \ && apt-get update \
&& apt-get install --yes --quiet ${PYTHON_PACKAGES} && apt-get install --yes --quiet ${PYTHON_PACKAGES}
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
RUN set -eux \ RUN set -eux \
&& echo "Installing pre-built updates" \ && echo "Installing pre-built updates" \
&& echo "Installing qpdf ${QPDF_VERSION}" \ && echo "Installing qpdf ${QPDF_VERSION}" \
@ -131,6 +128,8 @@ RUN set -eux \
&& echo "Configuring ImageMagick" \ && echo "Configuring ImageMagick" \
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml && mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
# Packages needed only for building a few quick Python # Packages needed only for building a few quick Python
# dependencies # dependencies
ARG BUILD_PACKAGES="\ ARG BUILD_PACKAGES="\
@ -140,11 +139,10 @@ ARG BUILD_PACKAGES="\
libpq-dev \ libpq-dev \
# https://github.com/PyMySQL/mysqlclient#linux # https://github.com/PyMySQL/mysqlclient#linux
default-libmysqlclient-dev \ default-libmysqlclient-dev \
pkg-config \ pkg-config"
pre-commit"
# hadolint ignore=DL3042 # hadolint ignore=DL3042
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \ RUN --mount=type=cache,target=/root/.cache/uv,id=pip-cache \
set -eux \ set -eux \
&& echo "Installing build system packages" \ && echo "Installing build system packages" \
&& apt-get update \ && apt-get update \
@ -169,9 +167,6 @@ RUN set -eux \
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \ && mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \
&& echo "Adjusting all permissions" \ && echo "Adjusting all permissions" \
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless && chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless
# && echo "Collecting static files" \
# && gosu paperless python3 manage.py collectstatic --clear --no-input --link \
# && gosu paperless python3 manage.py compilemessages
VOLUME ["/usr/src/paperless/paperless-ngx/data", \ VOLUME ["/usr/src/paperless/paperless-ngx/data", \
"/usr/src/paperless/paperless-ngx/media", \ "/usr/src/paperless/paperless-ngx/media", \

117
.devcontainer/README.md Normal file
View File

@ -0,0 +1,117 @@
# Paperless-ngx Development Environment
## Overview
Welcome to the Paperless-ngx development environment! This setup uses VSCode DevContainers to provide a consistent and seamless development experience.
### What are DevContainers?
DevContainers are a feature in VSCode that allows you to develop within a Docker container. This ensures that your development environment is consistent across different machines and setups. By defining a containerized environment, you can eliminate the "works on my machine" problem.
### Advantages of DevContainers
- **Consistency**: Same environment for all developers.
- **Isolation**: Separate development environment from your local machine.
- **Reproducibility**: Easily recreate the environment on any machine.
- **Pre-configured Tools**: Include all necessary tools and dependencies in the container.
## DevContainer Setup
The DevContainer configuration provides up all the necessary services for Paperless-ngx, including:
- Redis
- Gotenberg
- Tika
Data is stored using Docker volumes to ensure persistence across container restarts.
## Configuration Files
The setup includes debugging configurations (`launch.json`) and tasks (`tasks.json`) to help you manage and debug various parts of the project:
- **Backend Debugging:**
- `manage.py runserver`
- `manage.py document-consumer`
- `celery`
- **Maintenance Tasks:**
- Create superuser
- Run migrations
- Recreate virtual environment (`.venv` with `uv`)
- Compile frontend assets
## Getting Started
### Step 1: Running the DevContainer
To start the DevContainer:
1. Open VSCode.
2. Open the project folder.
3. Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
4. Type and select `Dev Containers: Rebuild and Reopen in Container`.
VSCode will build and start the DevContainer environment.
### Step 2: Initial Setup
Once the DevContainer is up and running, perform the following steps:
1. **Compile Frontend Assets**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Frontend Compile`.
2. **Run Database Migrations**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Migrate Database`.
3. **Create Superuser**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Create Superuser`.
### Debugging and Running Services
You can start and debug backend services either as debugging sessions via `launch.json` or as tasks.
#### Using `launch.json`
1. Press `F5` or go to the **Run and Debug** view in VSCode.
2. Select the desired configuration:
- `Runserver`
- `Document Consumer`
- `Celery`
#### Using Tasks
1. Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
2. Select `Tasks: Run Task`.
3. Choose the desired task:
- `Runserver`
- `Document Consumer`
- `Celery`
### Additional Maintenance Tasks
Additional tasks are available for common maintenance operations:
- **Recreate .venv**: For setting up the virtual environment using `uv`.
- **Migrate Database**: To apply database migrations.
- **Create Superuser**: To create an admin user for the application.
## Let's Get Started!
Follow the steps above to get your development environment up and running. Happy coding!

View File

@ -3,7 +3,7 @@
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml", "dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
"service": "paperless-development", "service": "paperless-development",
"workspaceFolder": "/usr/src/paperless/paperless-ngx", "workspaceFolder": "/usr/src/paperless/paperless-ngx",
"postCreateCommand": "pipenv install --dev && pipenv run pre-commit install", "postCreateCommand": "/bin/bash -c uv sync --dev && uv run pre-commit install",
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [

View File

@ -43,7 +43,7 @@ services:
volumes: volumes:
- ..:/usr/src/paperless/paperless-ngx:delegated - ..:/usr/src/paperless/paperless-ngx:delegated
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files - ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
- pipenv:/usr/src/paperless/paperless-ngx/.venv - virtualenv:/usr/src/paperless/paperless-ngx/.venv # Virtual environment persisted in volume
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container - /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
- /usr/src/paperless/paperless-ngx/src/.pytest_cache - /usr/src/paperless/paperless-ngx/src/.pytest_cache
- /usr/src/paperless/paperless-ngx/.ruff_cache - /usr/src/paperless/paperless-ngx/.ruff_cache
@ -80,4 +80,7 @@ services:
restart: unless-stopped restart: unless-stopped
volumes: volumes:
pipenv: data:
media:
redisdata:
virtualenv:

View File

@ -5,7 +5,7 @@
"label": "Start: Celery Worker", "label": "Start: Celery Worker",
"description": "Start the Celery Worker which processes background and consume tasks", "description": "Start the Celery Worker which processes background and consume tasks",
"type": "shell", "type": "shell",
"command": "pipenv run celery --app paperless worker -l DEBUG", "command": "uv run celery --app paperless worker -l DEBUG",
"isBackground": true, "isBackground": true,
"options": { "options": {
"cwd": "${workspaceFolder}/src" "cwd": "${workspaceFolder}/src"
@ -61,7 +61,7 @@
"label": "Start: Consumer Service (manage.py document_consumer)", "label": "Start: Consumer Service (manage.py document_consumer)",
"description": "Start the Consumer Service which processes files from a directory", "description": "Start the Consumer Service which processes files from a directory",
"type": "shell", "type": "shell",
"command": "pipenv run python manage.py document_consumer", "command": "uv run python manage.py document_consumer",
"group": "build", "group": "build",
"presentation": { "presentation": {
"echo": true, "echo": true,
@ -80,7 +80,7 @@
"label": "Start: Backend Server (manage.py runserver)", "label": "Start: Backend Server (manage.py runserver)",
"description": "Start the Backend Server which serves the Django API and the compiled Angular frontend", "description": "Start the Backend Server which serves the Django API and the compiled Angular frontend",
"type": "shell", "type": "shell",
"command": "pipenv run python manage.py runserver", "command": "uv run python manage.py runserver",
"group": "build", "group": "build",
"presentation": { "presentation": {
"echo": true, "echo": true,
@ -99,7 +99,7 @@
"label": "Maintenance: manage.py migrate", "label": "Maintenance: manage.py migrate",
"description": "Apply database migrations", "description": "Apply database migrations",
"type": "shell", "type": "shell",
"command": "pipenv run python manage.py migrate", "command": "uv run python manage.py migrate",
"group": "none", "group": "none",
"presentation": { "presentation": {
"echo": true, "echo": true,
@ -118,7 +118,7 @@
"label": "Maintenance: Build Documentation", "label": "Maintenance: Build Documentation",
"description": "Build the documentation with MkDocs", "description": "Build the documentation with MkDocs",
"type": "shell", "type": "shell",
"command": "pipenv run mkdocs build --config-file mkdocs.yml && pipenv run mkdocs serve", "command": "uv run mkdocs build --config-file mkdocs.yml && uv run mkdocs serve",
"group": "none", "group": "none",
"presentation": { "presentation": {
"echo": true, "echo": true,
@ -137,7 +137,7 @@
"label": "Maintenance: manage.py createsuperuser", "label": "Maintenance: manage.py createsuperuser",
"description": "Create a superuser", "description": "Create a superuser",
"type": "shell", "type": "shell",
"command": "pipenv run python manage.py createsuperuser", "command": "uv run python manage.py createsuperuser",
"group": "none", "group": "none",
"presentation": { "presentation": {
"echo": true, "echo": true,
@ -156,7 +156,7 @@
"label": "Maintenance: recreate .venv", "label": "Maintenance: recreate .venv",
"description": "Recreate the python virtual environment and install python dependencies", "description": "Recreate the python virtual environment and install python dependencies",
"type": "shell", "type": "shell",
"command": "rm -R -v .venv/* || pipenv install --dev", "command": "rm -R -v .venv/* || uv install --dev",
"group": "none", "group": "none",
"presentation": { "presentation": {
"echo": true, "echo": true,

View File

@ -27,9 +27,6 @@ indent_style = space
[*.md] [*.md]
indent_style = space indent_style = space
[Pipfile.lock]
indent_style = space
# Tests don't get a line width restriction. It's still a good idea to follow # Tests don't get a line width restriction. It's still a good idea to follow
# the 79 character rule, but in the interests of clarity, tests often need to # the 79 character rule, but in the interests of clarity, tests often need to
# violate it. # violate it.

View File

@ -1,6 +1,8 @@
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem
version: 2 version: 2
# Required for uv support for now
enable-beta-ecosystems: true
updates: updates:
# Enable version updates for npm # Enable version updates for npm
@ -34,9 +36,8 @@ updates:
- "eslint" - "eslint"
# Enable version updates for Python # Enable version updates for Python
- package-ecosystem: "pip" - package-ecosystem: "uv"
target-branch: "dev" target-branch: "dev"
# Look for a `Pipfile` in the `root` directory
directory: "/" directory: "/"
# Check for updates once a week # Check for updates once a week
schedule: schedule:
@ -53,6 +54,7 @@ updates:
- "*pytest*" - "*pytest*"
- "ruff" - "ruff"
- "mkdocs-material" - "mkdocs-material"
- "pre-commit*"
django: django:
patterns: patterns:
- "*django*" - "*django*"
@ -63,6 +65,10 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
pre-built:
patterns:
- psycopg*
- zxing-cpp
# Enable updates for GitHub Actions # Enable updates for GitHub Actions
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"

View File

@ -14,9 +14,7 @@ on:
- 'translations**' - 'translations**'
env: env:
# This is the version of pipenv all the steps will use DEFAULT_UV_VERSION: "0.6.x"
# If changing this, change Dockerfile
DEFAULT_PIP_ENV_VERSION: "2024.4.1"
# This is the default version of Python to use in most steps which aren't specific # This is the default version of Python to use in most steps which aren't specific
DEFAULT_PYTHON_VERSION: "3.11" DEFAULT_PYTHON_VERSION: "3.11"
@ -59,24 +57,25 @@ jobs:
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
cache-dependency-path: 'Pipfile.lock'
- -
name: Install pipenv name: Install uv
run: | uses: astral-sh/setup-uv@v5
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- -
name: Install dependencies name: Install Python dependencies
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
-
name: List installed Python dependencies
run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list
- -
name: Make documentation name: Make documentation
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs build --config-file ./mkdocs.yml uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
--dev \
--frozen \
mkdocs build --config-file ./mkdocs.yml
- -
name: Deploy documentation name: Deploy documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@ -84,7 +83,11 @@ jobs:
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME" echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
git config --global user.name "${{ github.actor }}" git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com" git config --global user.email "${{ github.actor }}@users.noreply.github.com"
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs gh-deploy --force --no-history uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
--dev \
--frozen \
mkdocs gh-deploy --force --no-history
- -
name: Upload artifact name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -117,12 +120,13 @@ jobs:
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: "${{ matrix.python-version }}" python-version: "${{ matrix.python-version }}"
cache: "pipenv"
cache-dependency-path: 'Pipfile.lock'
- -
name: Install pipenv name: Install uv
run: | uses: astral-sh/setup-uv@v5
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
python-version: ${{ steps.setup-python.outputs.python-version }}
- -
name: Install system dependencies name: Install system dependencies
run: | run: |
@ -135,12 +139,14 @@ jobs:
- -
name: Install Python dependencies name: Install Python dependencies
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python --version uv sync \
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev --python ${{ steps.setup-python.outputs.python-version }} \
--group testing \
--frozen
- -
name: List installed Python dependencies name: List installed Python dependencies
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list uv pip list
- -
name: Tests name: Tests
env: env:
@ -151,7 +157,11 @@ jobs:
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }} PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
run: | run: |
cd src/ cd src/
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pytest -ra uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
--dev \
--frozen \
pytest -ra
- -
name: Upload coverage name: Upload coverage
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }} if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
@ -472,16 +482,17 @@ jobs:
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
cache-dependency-path: 'Pipfile.lock'
- -
name: Install pipenv + tools name: Install uv
run: | uses: astral-sh/setup-uv@v5
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
python-version: ${{ steps.setup-python.outputs.python-version }}
- -
name: Install Python dependencies name: Install Python dependencies
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
- -
name: Install system dependencies name: Install system dependencies
run: | run: |
@ -502,17 +513,21 @@ jobs:
- -
name: Generate requirements file name: Generate requirements file
run: | run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} requirements > requirements.txt uv export --quiet --no-dev --format requirements-txt --output-file requirements.txt
- -
name: Compile messages name: Compile messages
run: | run: |
cd src/ cd src/
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python3 manage.py compilemessages uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
manage.py compilemessages
- -
name: Collect static files name: Collect static files
run: | run: |
cd src/ cd src/
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python3 manage.py collectstatic --no-input uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
manage.py collectstatic --no-input
- -
name: Move files name: Move files
run: | run: |
@ -528,8 +543,8 @@ jobs:
for file_name in .dockerignore \ for file_name in .dockerignore \
.env \ .env \
Dockerfile \ Dockerfile \
Pipfile \ pyproject.toml \
Pipfile.lock \ uv.lock \
requirements.txt \ requirements.txt \
LICENSE \ LICENSE \
README.md \ README.md \
@ -631,15 +646,17 @@ jobs:
ref: main ref: main
- -
name: Set up Python name: Set up Python
id: setup-python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
cache-dependency-path: 'Pipfile.lock'
- -
name: Install pipenv + tools name: Install uv
run: | uses: astral-sh/setup-uv@v5
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- -
name: Append Changelog to docs name: Append Changelog to docs
id: append-Changelog id: append-Changelog
@ -655,7 +672,10 @@ jobs:
CURRENT_CHANGELOG=`tail --lines +2 changelog.md` CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
mv changelog-new.md changelog.md mv changelog-new.md changelog.md
pipenv run pre-commit run --files changelog.md || true uv run \
--python ${{ steps.setup-python.outputs.python-version }} \
--dev \
pre-commit run --files changelog.md || true
git config --global user.name "github-actions" git config --global user.name "github-actions"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA" git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"

View File

@ -45,7 +45,6 @@ repos:
- javascript - javascript
- ts - ts
- markdown - markdown
exclude: "(^Pipfile\\.lock$)"
additional_dependencies: additional_dependencies:
- prettier@3.3.3 - prettier@3.3.3
- 'prettier-plugin-organize-imports@4.1.0' - 'prettier-plugin-organize-imports@4.1.0'
@ -55,6 +54,10 @@ repos:
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "2.0.4"
hooks:
- id: pyproject-fmt
# Dockerfile hooks # Dockerfile hooks
- repo: https://github.com/AleksaC/hadolint-py - repo: https://github.com/AleksaC/hadolint-py
rev: v2.12.0.3 rev: v2.12.0.3

View File

@ -5,5 +5,6 @@
/src-ui/ @paperless-ngx/frontend /src-ui/ @paperless-ngx/frontend
/src/ @paperless-ngx/backend /src/ @paperless-ngx/backend
Pipfile* @paperless-ngx/backend pyproject.toml @paperless-ngx/backend
uv.lock @paperless-ngx/backend
*.py @paperless-ngx/backend *.py @paperless-ngx/backend

View File

@ -26,28 +26,11 @@ esac
RUN set -eux \ RUN set -eux \
&& ./node_modules/.bin/ng build --configuration production && ./node_modules/.bin/ng build --configuration production
# Stage: pipenv-base
# Purpose: Generates a requirements.txt file for building
# Comments:
# - pipenv dependencies are not left in the final image
# - pipenv can't touch the final image somehow
FROM --platform=$BUILDPLATFORM docker.io/python:3.12-alpine AS pipenv-base
WORKDIR /usr/src/pipenv
COPY Pipfile* ./
RUN set -eux \
&& echo "Installing pipenv" \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2024.4.1 \
&& echo "Generating requirement.txt" \
&& pipenv requirements > requirements.txt
# Stage: s6-overlay-base # Stage: s6-overlay-base
# Purpose: Installs s6-overlay and rootfs # Purpose: Installs s6-overlay and rootfs
# Comments: # Comments:
# - Don't leave anything extra in here either # - Don't leave anything extra in here either
FROM docker.io/python:3.12-slim-bookworm AS s6-overlay-base FROM ghcr.io/astral-sh/uv:0.6.3-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6 WORKDIR /usr/src/s6
@ -123,9 +106,12 @@ ARG GS_VERSION=10.03.1
# Set Python environment variables # Set Python environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
# Ignore warning from Whitenoise # Ignore warning from Whitenoise about async iterators
PYTHONWARNINGS="ignore:::django.http.response:517" \ PYTHONWARNINGS="ignore:::django.http.response:517" \
PNGX_CONTAINERIZED=1 PNGX_CONTAINERIZED=1 \
# https://docs.astral.sh/uv/reference/settings/#link-mode
UV_LINK_MODE=copy \
UV_CACHE_DIR=/cache/uv/
# #
# Begin installation and configuration # Begin installation and configuration
@ -213,7 +199,7 @@ WORKDIR /usr/src/paperless/src/
# Python dependencies # Python dependencies
# Change pretty frequently # Change pretty frequently
COPY --chown=1000:1000 --from=pipenv-base /usr/src/pipenv/requirements.txt ./ COPY --chown=1000:1000 ["pyproject.toml", "uv.lock", "/usr/src/paperless/src/"]
# Packages needed only for building a few quick Python # Packages needed only for building a few quick Python
# dependencies # dependencies
@ -226,23 +212,15 @@ ARG BUILD_PACKAGES="\
default-libmysqlclient-dev \ default-libmysqlclient-dev \
pkg-config" pkg-config"
ARG ZXING_VERSION=2.3.0
ARG PSYCOPG_VERSION=3.2.4
# hadolint ignore=DL3042 # hadolint ignore=DL3042
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \ RUN --mount=type=cache,target=${UV_CACHE_DIR},id=python-cache \
set -eux \ set -eux \
&& echo "Installing build system packages" \ && echo "Installing build system packages" \
&& apt-get update \ && apt-get update \
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \ && apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
&& python3 -m pip install --upgrade wheel \
&& echo "Installing Python requirements" \ && echo "Installing Python requirements" \
&& curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \ && uv export --quiet --no-dev --format requirements-txt --output-file requirements.txt \
https://github.com/paperless-ngx/builder/releases/download/psycopg-${PSYCOPG_VERSION}/psycopg_c-${PSYCOPG_VERSION}-cp312-cp312-linux_x86_64.whl \ && uv pip install --system --no-python-downloads --python-preference system --requirements requirements.txt \
https://github.com/paperless-ngx/builder/releases/download/psycopg-${PSYCOPG_VERSION}/psycopg_c-${PSYCOPG_VERSION}-cp312-cp312-linux_aarch64.whl \
https://github.com/paperless-ngx/builder/releases/download/zxing-${ZXING_VERSION}/zxing_cpp-${ZXING_VERSION}-cp312-cp312-linux_aarch64.whl \
https://github.com/paperless-ngx/builder/releases/download/zxing-${ZXING_VERSION}/zxing_cpp-${ZXING_VERSION}-cp312-cp312-linux_x86_64.whl \
&& python3 -m pip install --default-timeout=1000 --find-links . --requirement requirements.txt \
&& echo "Installing NLTK data" \ && echo "Installing NLTK data" \
&& 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" 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" stopwords \

99
Pipfile
View File

@ -1,99 +0,0 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
dateparser = "~=1.2"
# WARNING: django does not use semver.
# Only patch versions are guaranteed to not introduce breaking changes.
django = "~=5.1.5"
django-allauth = {extras = ["mfa", "socialaccount"], version = "*"}
django-auditlog = "*"
django-celery-results = "*"
django-compression-middleware = "*"
django-cors-headers = "*"
django-extensions = "*"
django-filter = "~=25.1"
django-guardian = "*"
django-multiselectfield = "*"
django-soft-delete = "*"
djangorestframework = "~=3.15.2"
djangorestframework-guardian = "*"
drf-spectacular = "*"
drf-spectacular-sidecar = "*"
drf-writable-nested = "*"
bleach = "*"
celery = {extras = ["redis"], version = "*"}
channels = "~=4.2"
channels-redis = "*"
concurrent-log-handler = "*"
filelock = "*"
flower = "*"
gotenberg-client = "*"
granian = "*"
httpx-oauth = "*"
imap-tools = "*"
inotifyrecursive = "~=0.3"
jinja2 = "~=3.1"
langdetect = "*"
mysqlclient = "*"
nltk = "*"
ocrmypdf = "~=16.9"
pathvalidate = "*"
pdf2image = "*"
psycopg = {version = "*", extras = ["c"]}
python-dateutil = "*"
python-dotenv = "*"
python-gnupg = "*"
python-ipware = "*"
python-magic = "*"
pyzbar = "*"
rapidfuzz = "*"
redis = {extras = ["hiredis"], version = "*"}
scikit-learn = "~=1.6"
setproctitle = "*"
tika-client = "*"
tqdm = "*"
watchdog = "~=6.0"
whitenoise = "~=6.9"
whoosh = "~=2.7"
zxing-cpp = "*"
[dev-packages]
# Linting
pre-commit = "*"
ruff = "*"
# Testing
factory-boy = "*"
pytest = "*"
pytest-cov = "*"
pytest-django = "*"
pytest-httpx = "*"
pytest-env = "*"
pytest-sugar = "*"
pytest-xdist = "*"
pytest-mock = "*"
pytest-rerunfailures = "*"
imagehash = "*"
daphne = "*"
# Documentation
mkdocs-material = "*"
mkdocs-glightbox = "*"
[typing-dev]
mypy = "*"
types-Pillow = "*"
django-filter-stubs = "*"
types-python-dateutil = "*"
djangorestframework-stubs = {extras= ["compatible-mypy"], version="*"}
celery-types = "*"
django-stubs = {extras= ["compatible-mypy"], version="*"}
types-dateparser = "*"
types-bleach = "*"
types-redis = "*"
types-tqdm = "*"
types-Markdown = "*"
types-Pygments = "*"
types-colorama = "*"
types-setuptools = "*"

4812
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,7 @@ first-time setup.
Every command is executed directly from the root folder of the project unless specified otherwise. Every command is executed directly from the root folder of the project unless specified otherwise.
1. Install prerequisites + pipenv as mentioned in 1. Install prerequisites + [uv](https://github.com/astral-sh/uv) as mentioned in
[Bare metal route](setup.md#bare_metal). [Bare metal route](setup.md#bare_metal).
2. Copy `paperless.conf.example` to `paperless.conf` and enable debug 2. Copy `paperless.conf.example` to `paperless.conf` and enable debug
@ -75,17 +75,13 @@ first-time setup.
4. Install the Python dependencies: 4. Install the Python dependencies:
```bash ```bash
pipenv install --dev $ uv sync --dev
``` ```
!!! note
Using a virtual environment is highly recommended. You can spawn one via `pipenv shell`.
5. Install pre-commit hooks: 5. Install pre-commit hooks:
```bash ```bash
pre-commit install $ uv run pre-commit install
``` ```
6. Apply migrations and create a superuser for your development instance: 6. Apply migrations and create a superuser for your development instance:
@ -93,8 +89,8 @@ first-time setup.
```bash ```bash
# src/ # src/
python3 manage.py migrate $ uv run manage.py migrate
python3 manage.py createsuperuser $ uv run manage.py createsuperuser
``` ```
7. You can now either ... 7. You can now either ...
@ -164,6 +160,19 @@ $ ng build --configuration production
complicated IF cases. Append `# noqa: E501` to disable this check complicated IF cases. Append `# noqa: E501` to disable this check
for certain lines. for certain lines.
### Package Management
Paperless uses `uv` to manage packages and virtual environments for both development and production.
To accomplish some common tasks using `uv`, follow the shortcuts below:
To upgrade all locked packages to the latest allowed versions: `uv lock --upgrade`
To upgrade a single locked package: `uv lock --upgrade-package <package>`
To add a new package: `uv add <package>`
To add a new development package `uv add --dev <package>`
## Front end development ## Front end development
The front end is built using AngularJS. In order to get started, you need Node.js (version 14.15+) and The front end is built using AngularJS. In order to get started, you need Node.js (version 14.15+) and
@ -332,27 +341,21 @@ LANGUAGES = [
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/). The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
If you want to build the documentation locally, this is how you do it: If you want to build the documentation locally, this is how you do it:
1. Have an active pipenv shell (`pipenv shell`) and install Python dependencies: 1. Build the documentation
```bash ```bash
pipenv install --dev $ uv run mkdocs build --config-file mkdocs.yml
```
2. Build the documentation
```bash
mkdocs build --config-file mkdocs.yml
``` ```
_alternatively..._ _alternatively..._
3. Serve the documentation. This will spin up a 2. Serve the documentation. This will spin up a
copy of the documentation at http://127.0.0.1:8000 copy of the documentation at http://127.0.0.1:8000
that will automatically refresh every time you change that will automatically refresh every time you change
something. something.
```bash ```bash
mkdocs serve $ uv run mkdocs serve
``` ```
## Building the Docker image ## Building the Docker image

144
pyproject.toml Normal file
View File

@ -0,0 +1,144 @@
[project]
name = "paperless-ngx"
version = "2.14.7"
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",
]
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.6",
"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.2.1",
"drf-writable-nested~=0.7.1",
"filelock~=3.17.0",
"flower~=2.0.1",
"gotenberg-client~=0.9.0",
"granian~=1.7.6",
"httpx-oauth~=0.16",
"imap-tools~=1.10.0",
"inotifyrecursive~=0.3",
"jinja2~=3.1.5",
"langdetect~=1.0.9",
"mysqlclient~=2.2.7",
"nltk~=3.9.1",
"ocrmypdf~=16.9.0",
"pathvalidate~=3.2.3",
"pdf2image~=1.17.0",
"psycopg[c]==3.2.4",
# Direct dependency for proper resolution of the pre-build wheels
"psycopg-c==3.2.4",
"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",
]
# TODO: Move certain things to groups and then utilize that further
# This will allow testing to not install a webserver, mysql, etc
[dependency-groups]
dev = [
{ "include-group" = "docs" },
{ "include-group" = "testing" },
{ "include-group" = "lint" },
]
testing = [
"factory-boy~=3.3.1",
"pytest~=8.3.3",
"pytest-cov~=6.0.0",
"pytest-django~=4.10.0",
"pytest-httpx",
"pytest-env",
"pytest-sugar",
"pytest-xdist",
"pytest-mock",
"pytest-rerunfailures",
"imagehash",
"daphne",
]
lint = [
"pre-commit~=4.1.0",
"pre-commit-uv~=4.1.3",
"ruff~=0.9.9",
]
docs = [
"mkdocs-material~=9.6.4",
"mkdocs-glightbox~=0.4.0",
]
typing = [
"mypy",
"django-filter-stubs",
"types-python-dateutil",
"djangorestframework-stubs[compatible-mypy]",
"celery-types",
"django-stubs[compatible-mypy]",
"types-dateparser",
"types-bleach",
"types-redis",
"types-tqdm",
"types-Markdown",
"types-Pygments",
"types-colorama",
"types-setuptools",
]
[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.4/psycopg_c-3.2.4-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.4/psycopg_c-3.2.4-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'" },
]

3891
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ if __name__ == "__main__":
Granian( Granian(
"paperless.asgi:application", "paperless.asgi:application",
interface=Interfaces.ASGI, interface=Interfaces.ASGINL,
address=os.getenv("GRANIAN_HOST") or os.getenv("PAPERLESS_BIND_ADDR", "::"), address=os.getenv("GRANIAN_HOST") or os.getenv("PAPERLESS_BIND_ADDR", "::"),
port=int(os.getenv("GRANIAN_PORT") or os.getenv("PAPERLESS_PORT") or 8000), port=int(os.getenv("GRANIAN_PORT") or os.getenv("PAPERLESS_PORT") or 8000),
workers=int( workers=int(