Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Package dependencies and version changes should be approved by a member of 'presidio-administrators' team
**/pyproject.toml @data-privacy-stack/presidio-administrators
**/uv.lock @data-privacy-stack/presidio-administrators
26 changes: 17 additions & 9 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ Use atomic grouping: (?>a+)b or possessive quantifier a++b"

### Technology Stack
- **Python** - Must support all versions
- **Poetry** - Package manager, not pip
- **uv** - Dependency management and installation (not pip or Poetry). Each package commits a `uv.lock`; `poetry-core` is retained only as the build backend for now.
- **Ruff** - Linting and formatting (replaces flake8, black, isort)
- **spaCy** - Default NLP engine (en_core_web_lg for production), although one can use other NLP engines via provider pattern
- **Docker** - Deployment via GitHub Container Registry (`ghcr.io/data-privacy-stack`)
Expand All @@ -587,20 +587,28 @@ Use atomic grouping: (?>a+)b or possessive quantifier a++b"

### Local Development
```bash
# Setup
# Setup (uv reads the committed uv.lock; --locked fails if it is stale)
cd presidio-analyzer # or presidio-anonymizer, presidio-cli, etc.
poetry install --all-extras
poetry run python -m spacy download en_core_web_lg # For analyzer/CLI only
uv sync --locked --all-extras --group dev
Comment thread
RonShakutai marked this conversation as resolved.
uv run python -m spacy download en_core_web_lg # For analyzer/CLI only

# Run tests
poetry run pytest -xvv # Stop on first failure with verbose output
poetry run pytest tests/test_us_ssn_recognizer.py -k "test_valid" # Specific test
uv run pytest -xvv # Stop on first failure with verbose output
uv run pytest tests/test_us_ssn_recognizer.py -k "test_valid" # Specific test

# Lint
ruff check . # From repo root
ruff format . # Auto-format
uv run ruff check .
uv run ruff format .
```

> **Dependency changes:** whenever you edit a package's `pyproject.toml`
> dependencies (add/remove/bump `[project]` deps, extras, or
> `[dependency-groups]`), you MUST regenerate and commit that package's
> `uv.lock` in the same change (`cd <package> && uv lock`). CI installs with
> `uv sync --locked` and fails if `pyproject.toml` and `uv.lock` are out of
> sync, so an updated `pyproject.toml` without its matching `uv.lock` will
> break the build.

### Docker Testing
```bash
# Quick test with pre-built images
Expand All @@ -624,7 +632,7 @@ pytest -v # Run all E2E tests
## Common Issues to Watch For

### Build/Test Issues
- **Poetry version conflicts** - Use `poetry lock --no-update` to preserve versions
- **Stale `uv.lock`** - If `uv sync --locked` fails with "lockfile needs to be updated", run `uv lock` in that package and commit the result.
- **Missing spaCy models** - Download en_core_web_lg before running tests
- **AHDS test skips** - Expected when AHDS_ENDPOINT not set
- **Transformers test failures** - Expected without HuggingFace access in restricted environments
Expand Down
82 changes: 65 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ jobs:
with:
fail-on-severity: low
allow-licenses: MIT, Apache-2.0, BSD-3-Clause, 0BSD, 0BSD AND Apache-2.0 AND BSD-3-Clause AND MIT
# Pre-existing transformers advisories surfaced by the newly committed
# uv.lock files (they already exist on main, capped at transformers
# <5 by spacy-huggingface-pipelines). They are unrelated to the uv
# migration and are handled in separate security PRs; allow them here
# so this PR's dependency review is not blocked by prior debt.
allow-ghsas: GHSA-29pf-2h5f-8g72, GHSA-69w3-r845-3855
comment-summary-in-pr: on-failure

test:
name: Test ${{ matrix.component.name }} (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 60
Comment thread
RonShakutai marked this conversation as resolved.
permissions:
contents: write
pull-requests: write
Expand Down Expand Up @@ -96,9 +103,16 @@ jobs:
extras: '--all-extras'
spacy-models: 'en_core_web_lg en_core_web_sm'
env:
POETRY_CACHE_DIR: /mnt/poetry_cache
COVERAGE_THRESHOLD: 90
PRIMARY_PYTHON: '3.13'
# Keep uv's (potentially large) wheel cache on the runner's roomy
# ephemeral disk (/mnt, ~65 GB) rather than the smaller root volume, as
# the previous Poetry setup did via POETRY_CACHE_DIR. The cache is
# persisted across runs by the "Cache uv downloads" step below, keyed on
# the committed uv.lock. UV_LINK_MODE=copy avoids the cross-filesystem
# hardlink warning since the project venv lives on the root volume.
UV_CACHE_DIR: /mnt/uv-cache
UV_LINK_MODE: copy
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.0
Expand All @@ -113,19 +127,24 @@ jobs:
cache: 'pip'
cache-dependency-path: ${{ matrix.component.path }}/pyproject.toml

- name: Install Poetry
run: |
python -m pip install poetry==2.3.2
- name: Install uv
run: python -m pip install uv==0.11.6
Comment thread
RonShakutai marked this conversation as resolved.
Comment thread
RonShakutai marked this conversation as resolved.

- name: Setup Poetry Cache Directory
- name: Prepare uv cache directory on /mnt
run: |
sudo mkdir -p $POETRY_CACHE_DIR
sudo chown -R runner $POETRY_CACHE_DIR
# Remove stale virtualenvs to prevent cache corruption on re-run attempts.
# GitHub Actions may reuse the same runner VM when re-running failed jobs,
# which leaves the old /mnt/poetry_cache intact. Installing a new numpy
# version over an existing one causes mixed file state and IndentationErrors.
rm -rf $POETRY_CACHE_DIR/virtualenvs 2>/dev/null || true
# /mnt is root-owned, so create the cache dir and hand it to the
# runner user before uv (or the cache restore) writes to it.
sudo mkdir -p "$UV_CACHE_DIR"
sudo chown -R "$(id -u):$(id -g)" "$UV_CACHE_DIR"

- name: Cache uv downloads
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.0
with:
path: ${{ env.UV_CACHE_DIR }}
key: uv-${{ runner.os }}-${{ matrix.component.path }}-py${{ matrix.python-version }}-${{ hashFiles(format('{0}/uv.lock', matrix.component.path)) }}
restore-keys: |
uv-${{ runner.os }}-${{ matrix.component.path }}-py${{ matrix.python-version }}-
uv-${{ runner.os }}-${{ matrix.component.path }}-

- name: Install system dependencies for Image Redactor
if: matrix.component.name == 'Image Redactor'
Expand All @@ -134,33 +153,48 @@ jobs:
sudo apt-get install tesseract-ocr -y

- name: Install dependencies
timeout-minutes: 30
working-directory: ${{ matrix.component.path }}
run: |
poetry install ${{ matrix.component.extras }}
# Install from the committed uv.lock with `uv sync --locked`: uv
# errors if pyproject.toml and uv.lock disagree instead of silently
# resolving new versions, so CI installs exactly the locked graph and
# never mutates the lockfile. (This also cured the Poetry hang, whose
# lockless universal resolve backtracked for minutes on the analyzer's
# --all-extras graph.) The `dev` dependency group supplies the test
# tooling (pytest, diff-cover, ...) plus `pip`, which spaCy's model
# download shells out to via `python -m pip install`. Later steps run
# inside the synced venv with `uv run --no-sync` (the wheel-build step
# deliberately uses the system interpreter, as it did under Poetry).
uv sync --locked ${{ matrix.component.extras }} --group dev --python ${{ matrix.python-version }}

- name: Install presidio-analyzer for Image Redactor
if: matrix.component.name == 'Image Redactor'
working-directory: ${{ matrix.component.path }}
run: |
poetry run pip install -e ../presidio-analyzer/.
# Override the locked PyPI presidio-analyzer with the in-repo copy so
# the image-redactor tests run against local analyzer changes. This
# editable sibling install isn't expressible in a single-package lock;
# the following steps use `uv run --no-sync` so uv won't revert it.
uv pip install -e ../presidio-analyzer/.

- name: Download spaCy models
if: matrix.component.spacy-models != ''
working-directory: ${{ matrix.component.path }}
run: |
for model in ${{ matrix.component.spacy-models }}; do
poetry run python -m spacy download $model
uv run --no-sync python -m spacy download $model
Comment thread
RonShakutai marked this conversation as resolved.
done

- name: Run tests with coverage
working-directory: ${{ matrix.component.path }}
run: |
PACKAGE_NAME=$(echo "${{ matrix.component.path }}" | sed 's/-/_/g')
poetry run pytest --cov=$PACKAGE_NAME --cov-report=xml:coverage.xml --cov-report=term --cov-report=html -vv --tb=short
uv run --no-sync pytest --cov=$PACKAGE_NAME --cov-report=xml:coverage.xml --cov-report=term --cov-report=html -vv --tb=short
Comment thread
RonShakutai marked this conversation as resolved.

if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "Checking PR difference coverage (>= ${{ env.COVERAGE_THRESHOLD }}%) with diff-cover..."
poetry run diff-cover coverage.xml \
uv run --no-sync diff-cover coverage.xml \
Comment thread
RonShakutai marked this conversation as resolved.
--compare-branch=origin/${{ github.base_ref }} \
--fail-under=${{ env.COVERAGE_THRESHOLD }} \
--show-uncovered
Expand All @@ -180,9 +214,23 @@ jobs:
- name: Build wheel package
working-directory: ${{ matrix.component.path }}
run: |
# `build` needs `tomli` on Python < 3.11 to read pyproject.toml, but
# it is absent from the hashed requirements file (pip-compiled on
# 3.11+). Poetry used to install it onto the runner incidentally; uv
# has no Python dependencies, so pre-install it (marker-guarded) here
# so `pip install --require-hashes` finds it already satisfied rather
# than trying to fetch it unhashed.
python -m pip install "tomli==2.4.1; python_version < '3.11'"
pip install --require-hashes -r ${{ github.workspace }}/.github/pipelines/requirements-build.txt
python -m build --wheel

- name: Minimize uv cache
if: always()
run: |
# Drop pre-built wheels that uv can cheaply rebuild, keeping the
# persisted /mnt cache small (recommended for CI by the uv docs).
uv cache prune --ci

build-platform-images:
name: Build ${{ matrix.image }} (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.

### Anonymizer
### General
#### Changed
- Migrated dependency management from Poetry to [uv](https://github.com/astral-sh/uv) as the first step toward a fully uv-based repository. Each tested package now declares its development tooling with a PEP 735 `[dependency-groups]` table and ships a committed `uv.lock`; CI and the service Docker images install from the lockfile with `uv sync --locked` (no dependency resolution happens in CI or image builds). This also fixes the Poetry CI hang — its lockless universal resolve backtracked for many minutes on the analyzer's large `--all-extras` graph — and a job-level `timeout-minutes` safety net was added. Package metadata (the `[project]` tables) is unchanged; the `poetry-core` build backend is retained for now.
#### Fixed
- Retried the Zensical documentation build on transient crashes (e.g. SIGKILL/exit 247) so the docs release pipeline no longer fails intermittently (Thanks @Copilot)

Expand Down
16 changes: 10 additions & 6 deletions presidio-analyzer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ ARG NLP_CONF_FILE=presidio_analyzer/conf/default.yaml
ARG ANALYZER_CONF_FILE=presidio_analyzer/conf/default_analyzer.yaml
ARG RECOGNIZER_REGISTRY_CONF_FILE=presidio_analyzer/conf/default_recognizers.yaml
ENV PIP_NO_CACHE_DIR=1
ENV POETRY_VIRTUALENVS_CREATE=false
# Install the locked dependencies into the system environment (no venv).
ENV UV_PROJECT_ENVIRONMENT=/usr/local

ENV ANALYZER_CONF_FILE=${ANALYZER_CONF_FILE}
ENV RECOGNIZER_REGISTRY_CONF_FILE=${RECOGNIZER_REGISTRY_CONF_FILE}
Expand All @@ -24,16 +25,19 @@ RUN apt-get update \
&& apt-get install curl --no-install-recommends -y \
&& rm -rf /var/lib/apt/lists/*

COPY ./pyproject.toml /app/
COPY ./pyproject.toml ./uv.lock /app/

RUN pip install poetry==2.3.2 \
&& poetry install --no-root --only=main -E server \
&& rm -rf $(poetry config cache-dir)
# Install exactly the locked dependency graph (main + server extra, no dev
# group, project itself not installed — it is run from the copied source).
# Splitting this from the source COPY keeps the dependency layer cached until
# pyproject.toml/uv.lock change.
RUN pip install uv==0.11.6 \
&& uv sync --locked --no-cache --no-default-groups --extra server --no-install-project

# install nlp models specified in NLP_CONF_FILE or via nlp_configuration in ANALYZER_CONF_FILE
COPY ./install_nlp_models.py /app/

RUN poetry run python install_nlp_models.py \
RUN python install_nlp_models.py \
Comment thread
RonShakutai marked this conversation as resolved.
--conf_file ${NLP_CONF_FILE} \
--analyzer_conf_file ${ANALYZER_CONF_FILE}

Expand Down
2 changes: 1 addition & 1 deletion presidio-analyzer/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
exec poetry run gunicorn -w "$WORKERS" -b "0.0.0.0:$PORT" "app:create_app()"
exec gunicorn -w "$WORKERS" -b "0.0.0.0:$PORT" "app:create_app()"
Comment thread
RonShakutai marked this conversation as resolved.
20 changes: 11 additions & 9 deletions presidio-analyzer/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,17 @@ langextract = [
"jinja2 (>=3.0.0,<4.0.0)",
]

[tool.poetry.group.dev.dependencies]
pip = "*"
ruff = "*"
pytest = "*"
pytest-cov = "*"
pytest-mock = "*"
python-dotenv = "*"
pre_commit = "*"
diff-cover = "*"
[dependency-groups]
dev = [
Comment thread
RonShakutai marked this conversation as resolved.
"pip",
"ruff",
"pytest",
"pytest-cov",
"pytest-mock",
"python-dotenv",
"pre-commit",
"diff-cover",
]
Comment thread
RonShakutai marked this conversation as resolved.

[tool.coverage.run]
relative_files = true
Expand Down
Loading
Loading