6.3 KiB
Presidio — Agent Guidelines
Presidio is a Python SDK for detecting (presidio-analyzer) and anonymizing (presidio-anonymizer) PII in text and images, plus CLI, structured-data, and image-redaction components. It is a widely used library: users depend on current detection behavior, and configuration files written years ago must keep working. Correctness and backward compatibility outrank cleverness.
The review-side versions of these rules — which the Copilot PR review agent
also enforces — live in .github/copilot-instructions.md and
.github/instructions/*.instructions.md. Follow them at authoring time so the
review finds nothing.
Working in this repo
cd presidio-analyzer # or presidio-anonymizer, presidio-cli, ...
uv sync --locked --all-extras --group dev
uv run python -m spacy download en_core_web_lg # analyzer/CLI only
uv run pytest -xvv
uv run ruff check . && uv run ruff format .
- Python
>=3.10,<3.15; code must run on every version in range. - Dependencies are managed with uv, not pip/Poetry. If you touch a
package's
pyproject.tomldependencies, runuv lockin that package and commit the updateduv.lockin the same change — CI fails on drift. - Do not edit
CHANGELOG.md; release entries are generated from merged PRs. - Never log PII values (
entity.text) — only entity types and positions. - Modules that process records are stateless; do not add state.
- Terminology: "threshold", not "cutoff"; ISO 639-1 language codes everywhere.
Adding a PII recognizer
The full rulebook — score bands, context-word rules, validation-hook
semantics, the configuration-path test template, and the test-quality bar —
is .github/instructions/recognizers.instructions.md. Read it before
starting; those rules apply at authoring time, not just in review. The
workflow, in order:
- Place and name it under
predefined_recognizers/: full lowercase country name for new country directories (south_africa, notza; don't imitate the pre-existing short formsus/uk/thai), orgeneric/,nlp_engine_recognizers/,ner/,third_party/as appropriate. - Use ISO 639-1 language codes (
kofor Korean, neverkr) — a mismatch loads nothing, silently. - Make the constructor loader-compatible: accept the YAML loader's
kwargs (
name,supported_entity,context, ...) and forward them to the base class, or the recognizer crashes the whole registry the moment a user enables it. - Design the pattern for accuracy first — this is the top review priority: as specific as the format allows, score calibrated to the pattern alone, unambiguous context words, the correct checksum if one exists (and none invented if it doesn't), and the pattern's source documented in the docstring, preferably an official specification.
- Register it everywhere: exports in
predefined_recognizers/__init__.pyand the country/category__init__.py; an entry inconf/default_recognizers.yaml(country-specific shipsenabled: false); a row indocs/supported_entities.md. - Write the configuration-path test — the most-missed step and the one
that matters most: enable the recognizer in a YAML config, load it through
RecognizerRegistryProvider, and assert detection (template in the instructions file). Non-English recognizers must set the top-levelsupported_languagesin the test config — it defaults to["en"]and silently filters everything else.
When modifying an existing recognizer: changed patterns, scores, or context change detection results for existing users — state that in the PR description, and never change an existing recognizer as a side effect of adding a new one.
Changing the YAML configuration layer
The rulebook for the pydantic models in presidio_analyzer/input_validation/
is .github/instructions/yaml-config.instructions.md — read it before
touching the layer. The short version:
- Constructor parameters and schema fields must never drift apart: a
YAML-settable kwarg without a matching pydantic field is silently dropped
today. Model-specific kwargs need a dedicated config model registered in
CONFIG_MODEL_MAP. - Choose
extradeliberately (forbidfails fast,allowpasses through); pass-through models dump withexclude_none=Trueso YAML omissions keep constructor defaults. - Validate at parse time with actionable messages, and never break existing
YAML — legacy singular fields, bare-string entries, and inferred
typeall stay supported. - Test through
RecognizerRegistryProvider, not just the pydantic model: prove new fields reach the constructed object and assert error messages for invalid YAML.
General engineering rules
- Declare behavior changes. Any edit outside a brand-new file needs the PR description to say what existing behavior changes. Defaults on shared base classes, properties on abstract interfaces, and anything altering returned entities or scores all count, even with no signature change.
- Explainability: anything that changes how a score is derived must be
reflected in
AnalysisExplanation. - Component boundaries: data flows Analyzer → Anonymizer → Output; import
public interfaces, never another component's internals; shared models
(
RecognizerResult,OperatorConfig) are contracts — update all consumers in the same changeset. - Anonymizer operators: non-reversible by default — no deterministic hashing (rainbow tables), unpredictable replacement values, no preserved PII characteristics. Deterministic or format-preserving output is sometimes a hard requirement (e.g. referential integrity across a dataset); support it as an explicit, documented opt-in, never as the default behavior.
- Security: never log PII values; validate untrusted inputs before use (user-supplied regexes before compilation, file paths, API payloads); no hardcoded secrets; no unsafe deserialization of untrusted models or pickles.
- Performance: no catastrophic regex backtracking (test long adversarial
inputs); cache compiled regexes; batch NLP with
nlp.pipe. - Docs move with code:
docs/supported_entities.mdfor entities,docs/api-docs/api-docs.ymlfor API changes, reST docstrings on public APIs, samples for complex features.