API Reference¶
This page is automatically generated from source code using mkdocstrings.
It reflects the current public surface of pdf-anonymizer-core. The hand-written usage guide is in SDK & API Usage. For practical examples see Recipes & Common Workflows.
Info
The CLI (pdf-anonymizer-cli) is a thin wrapper built with Typer. Most logic lives in the core package.
Core Functions¶
pdf_anonymizer_core.core.anonymize_file(file_path: str, characters_to_anonymize: int, prompt_template: str, model_name: str, anonymized_entities: Optional[List[str]] = None, chunk_overlap: int = DEFAULT_CHUNK_OVERLAP, regex_patterns: Optional[Dict[str, str]] = None, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0, operators: Optional[Dict[str, str]] = None, fake_secret: Optional[str] = None, encrypt_secret: Optional[str] = None, seed_mapping: Optional[Dict[str, str]] = None, keep_list: Optional[List[str]] = None, deny_list: Optional[List[str]] = None, use_llm: bool = True, use_ner: bool = False, ocr: bool = False, min_confidence: float = 0.0) -> Tuple[Optional[str], Optional[Dict[str, str]]]
¶
Anonymize a file by processing its text content.
Performs a two-stage entity detection (fast regex first pass followed by LLM-based semantic detection), deduplicates, consolidates base forms for coreference (e.g. "Dr. Smith" / "Smith"), generates typed placeholders (PERSON_1, ORGANIZATION_3.v_1, ...), and replaces non-overlapping spans (longest-first, written from the end) to produce reversible anonymized output.
The function streams large inputs via chunking (Markdown-aware for PDF/MD) so that very large files (hundreds of MB) can be processed without exhausting context windows or memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
Path to the file to anonymize (.pdf, .md, .txt, .csv,
.xlsx, or .docx). |
required |
characters_to_anonymize
|
int
|
Target character size of each chunk sent to the LLM. |
required |
prompt_template
|
str
|
The full prompt template string (use one from pdf_anonymizer_core.prompts or supply your own). |
required |
model_name
|
str
|
Model identifier or "provider/model" string (e.g. "gemini-2.5-flash", "ollama/phi4-mini", "google/gemini-2.0-flash-exp"). |
required |
anonymized_entities
|
Optional[List[str]]
|
Optional whitelist of entity types (e.g. ["PERSON", "ORGANIZATION"]). When provided, only matching entities are replaced. |
None
|
chunk_overlap
|
int
|
Number of characters of overlap between consecutive chunks. |
DEFAULT_CHUNK_OVERLAP
|
regex_patterns
|
Optional[Dict[str, str]]
|
Custom first-stage regex map. Defaults to the large built-in collection in DEFAULT_REGEX_PATTERNS (see conf.py). The collection covers universal PII (email, URLs, credit cards, crypto wallets, IBANs, VIN, MAC, IPv4/IPv6, dates) plus country-partitioned national IDs, tax IDs, driver licenses, VAT/business numbers, passports etc. for 30+ countries (mandatory: US, CA, GB, ES, IT, FR, IN, CN plus DE, JP, BR, AU, NL, ...). Keys become entity TYPEs (IPV4_ADDRESS, SSN_US, IBAN, CRYPTO_ETH, ...). All patterns are RE2 (google-re2) safe. |
None
|
max_retries
|
int
|
Maximum LLM call attempts per chunk (with exponential backoff). |
3
|
base_retry_delay
|
float
|
Base delay in seconds for retry backoff. |
1.0
|
max_retry_delay
|
float
|
Maximum delay cap for retry backoff. |
10.0
|
operators
|
Optional[Dict[str, str]]
|
Optional map of entity type → operator (replace, mask, hash,
generalize, shift, fake, encrypt). Types not listed keep |
None
|
fake_secret
|
Optional[str]
|
Optional seed material for the |
None
|
encrypt_secret
|
Optional[str]
|
Secret for the |
None
|
seed_mapping
|
Optional[Dict[str, str]]
|
Optional original → written map from a previous file so the same person keeps PERSON_1 (or the same fake) across documents. |
None
|
keep_list
|
Optional[List[str]]
|
Phrases that must stay visible even if detected. |
None
|
deny_list
|
Optional[List[str]]
|
Phrases that must be replaced even if detection missed them. |
None
|
use_llm
|
bool
|
When False, skip |
True
|
use_ner
|
bool
|
When True, run local span NER (GLiNER extra) for names and organizations. Default False so the SDK never downloads a checkpoint unless the caller asks. |
False
|
min_confidence
|
float
|
Drop entities whose |
0.0
|
ocr
|
bool
|
When True and a PDF has no text layer, run Tesseract via PyMuPDF and stash word boxes for a later native-PDF write. A scan with OCR off (or OCR that returns nothing) raises. |
False
|
Returns:
| Type | Description |
|---|---|
Tuple[Optional[str], Optional[Dict[str, str]]]
|
A tuple (anonymized_text, mapping) where: - anonymized_text is the masked document (or None on failure) - mapping is a dict of original_value -> placeholder (or None on failure) |
Note
The returned mapping is in original -> placeholder direction. The CLI later converts it to placeholder -> original for deanonymization.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/core.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 | |
pdf_anonymizer_core.core.anonymize_tabular_file(file_path: str, characters_to_anonymize: int, prompt_template: str, model_name: str, anonymized_entities: Optional[List[str]] = None, chunk_overlap: int = DEFAULT_CHUNK_OVERLAP, regex_patterns: Optional[Dict[str, str]] = None, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0, operators: Optional[Dict[str, str]] = None, fake_secret: Optional[str] = None, encrypt_secret: Optional[str] = None, seed_mapping: Optional[Dict[str, str]] = None, keep_list: Optional[List[str]] = None, deny_list: Optional[List[str]] = None, use_llm: bool = True, use_ner: bool = False, min_confidence: float = 0.0) -> Tuple[str, Dict[str, str], Tuple[str, ...]]
¶
Anonymize a CSV/Excel file cell by cell.
Returns (review_flatten, orig→written, entity_texts). entity_texts
is the same entity["text"] list the text engine passes to
replace_entities.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/core.py
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 | |
pdf_anonymizer_core.core.anonymize_docx_file(file_path: str, characters_to_anonymize: int, prompt_template: str, model_name: str, anonymized_entities: Optional[List[str]] = None, chunk_overlap: int = DEFAULT_CHUNK_OVERLAP, regex_patterns: Optional[Dict[str, str]] = None, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0, operators: Optional[Dict[str, str]] = None, fake_secret: Optional[str] = None, encrypt_secret: Optional[str] = None, seed_mapping: Optional[Dict[str, str]] = None, keep_list: Optional[List[str]] = None, deny_list: Optional[List[str]] = None, use_llm: bool = True, use_ner: bool = False, min_confidence: float = 0.0) -> Tuple[str, Dict[str, str], Tuple[str, ...]]
¶
Anonymize a Word .docx file paragraph by paragraph.
Returns (review_flatten, orig→written, entity_texts). entity_texts
is the same entity["text"] list the text engine passes to
replace_entities.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/core.py
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 | |
pdf_anonymizer_core.utils.deanonymize_file(anonymized_file_path: str, mapping_file_path: str, mapping_passphrase: str | None = None, *, expected_source_sha256: str | None = None, encrypt_secret: str | None = None) -> tuple[str, str]
¶
Deanonymize a file using a mapping file.
Restores original PII values from placeholders. Supports both current (placeholder -> original) and legacy (original -> placeholder) mapping directions via auto-detection.
The implementation correctly handles variation placeholders (PERSON_1.v_2 etc.) by falling back to the base placeholder's original value.
After processing it also produces an audit statistics JSON file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
anonymized_file_path
|
str
|
Path to the previously anonymized document. |
required |
mapping_file_path
|
str
|
Path to the JSON mapping file (plaintext or |
required |
mapping_passphrase
|
str | None
|
Required when the mapping file is encrypted. |
None
|
expected_source_sha256
|
str | None
|
Optional SHA-256 of the original source document. When set, an encrypted mapping locked to a different file is rejected (AAD mismatch). |
None
|
encrypt_secret
|
str | None
|
Secret used by the |
None
|
Returns:
| Type | Description |
|---|---|
str
|
A tuple (deanonymized_file_path, stats_file_path). |
str
|
The stats file contains: - unused_mappings: placeholders in the map that did not appear in text - not_found_mappings: placeholders seen in text but missing from the map |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/utils.py
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 | |
pdf_anonymizer_core.utils.consolidate_mapping(anonymized_text: str, mapping: Dict[str, str]) -> Tuple[str, Dict[str, str]]
¶
Consolidates the mapping to ensure one-to-one correspondence and updates the text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
anonymized_text
|
str
|
The text with anonymized placeholders. |
required |
mapping
|
Dict[str, str]
|
The dictionary mapping placeholders to original PII. |
required |
Returns:
| Type | Description |
|---|---|
Tuple[str, Dict[str, str]]
|
A tuple containing the updated anonymized text and the consolidated mapping. |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/utils.py
pdf_anonymizer_core.utils.save_results(full_anonymized_text: str, final_mapping: dict[str, str], file_path: str, mapping_passphrase: str | None = None, *, ephemeral_mapping: bool = False, entity_texts: Optional[Iterable[str]] = None, orig_to_written: Optional[Dict[str, str]] = None, output_pdf: bool = False, redact: bool = False) -> tuple[str, str]
¶
Save the anonymized text and the mapping to files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full_anonymized_text
|
str
|
The anonymized text. |
required |
final_mapping
|
dict[str, str]
|
Mapping written to the mapping file (CLI invert: placeholder → original). |
required |
file_path
|
str
|
The path to the original file. |
required |
mapping_passphrase
|
str | None
|
If set, write |
None
|
ephemeral_mapping
|
bool
|
If true, never write a mapping file. The second
return value is an empty string. The caller already holds
|
False
|
entity_texts
|
Optional[Iterable[str]]
|
Detected |
None
|
orig_to_written
|
Optional[Dict[str, str]]
|
Engine original → written map used to re-apply on
tables, Word files, and native PDF writes. Required for colliding
mask/generalize/fake forms; when omitted,
|
None
|
output_pdf
|
bool
|
Also write a sanitized |
False
|
redact
|
bool
|
Irreversible native PDF: black boxes, no stand-in text.
Implies |
False
|
Returns:
| Type | Description |
|---|---|
str
|
tuple[str, str]: The paths to the anonymized text file and the mapping |
str
|
file. The mapping path is |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/utils.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
pdf_anonymizer_api.app.create_app()
¶
Build the FastAPI app.
Source code in packages/pdf-anonymizer-api/src/pdf_anonymizer_api/app.py
pdf_anonymizer_api.app.anonymize_text_request(text: str, *, use_llm: bool = False, use_ner: bool = False, min_confidence: float = 0.0, keep_list: Optional[List[str]] = None, deny_list: Optional[List[str]] = None, operators: Optional[Dict[str, str]] = None, seed_mapping: Optional[Dict[str, str]] = None, fake_secret: Optional[str] = None, encrypt_secret: Optional[str] = None, model_name: Optional[str] = None, prompt_name: str = 'simple', anonymized_entities: Optional[List[str]] = None, countries: Optional[List[str]] = None) -> Dict[str, Any]
¶
Run the same engine as the CLI on an in-memory string.
Source code in packages/pdf-anonymizer-api/src/pdf_anonymizer_api/app.py
Configuration & Models¶
pdf_anonymizer_core.conf
¶
Central configuration, defaults, profiles, and model/provider enums.
This module defines: - Default constants and directories used by the anonymizer. - Built-in ConfigProfiles (best-quality, best-speed, best-cost, regex-only) that bundle model, prompt, chunk size, retries, and whether to call an LLM. - The AppConfig Pydantic model. - get_config_for_profile() helper (used heavily by the CLI). - Legacy enums (PromptEnum, ModelName, etc.) for compatibility and dynamic provider/model resolution.
EntityProfile
¶
Bases: str, Enum
Named type + operator bundles. Separate from quality/speed ConfigProfile.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/conf.py
filter_regex_patterns(countries: Optional[Iterable[str]] = None, patterns: Optional[Dict[str, str]] = None) -> Dict[str, str]
¶
Keep universal patterns plus national-ID patterns for countries.
Universal keys (EMAIL, IBAN, CREDIT_CARD, VIN, ...) always stay.
Country keys (SSN_US, NINO_GB, PESEL_PL, legacy SSN, ...) stay only when
their ISO-2 code is in countries.
countries is a list of ISO-2 codes such as ["US", "GB"].
None or an empty list returns a copy of all patterns.
Unknown codes raise ValueError.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/conf.py
get_config_for_profile(profile: ConfigProfile, model_name: Optional[str] = None, prompt_name: Optional[str] = None, chunk_size: Optional[int] = None, chunk_overlap: Optional[int] = None, countries: Optional[Iterable[str]] = None) -> AppConfig
¶
Return an AppConfig populated from one of the built-in profiles.
Profiles provide convenient quality/speed/cost presets. Any of the scalar overrides (model_name, prompt_name, chunk_size, chunk_overlap) take precedence over the profile defaults.
The other fields (retries, delays, cache settings, directories) always come from the chosen profile.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
profile
|
ConfigProfile
|
One of ConfigProfile.BEST_QUALITY, BEST_SPEED, BEST_COST, or REGEX_ONLY. |
required |
model_name
|
Optional[str]
|
Optional override for the model (string or provider/model). |
None
|
prompt_name
|
Optional[str]
|
Optional override ("simple" or "detailed"). |
None
|
chunk_size
|
Optional[int]
|
Optional override for characters_to_anonymize / chunk_size. |
None
|
chunk_overlap
|
Optional[int]
|
Optional override for chunk overlap. |
None
|
countries
|
Optional[Iterable[str]]
|
Optional ISO-2 codes that limit national-ID regexes (universal patterns always stay). |
None
|
Returns:
| Type | Description |
|---|---|
AppConfig
|
A fully populated AppConfig instance ready to drive anonymize_file |
AppConfig
|
(or to be passed through configure_cache, etc.). |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/conf.py
pattern_country(key: str) -> Optional[str]
¶
Return the ISO-2 country for a pattern key, or None if it is universal.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/conf.py
Prompts¶
The package ships two ready-to-use prompt templates.
pdf_anonymizer_core.prompts.detailed
¶
Detailed (higher-quality) PII identification prompt.
This prompt instructs the LLM to: - Use deep contextual understanding - Return base_form for coreference handling - Identify a rich set of entity types including JOB_TITLE, ID, DATE (birthdates only), and INDIRECT (phrases that point to one person without saying their name) - Explicitly handle variations, possessives, and implied identity
Use this prompt (via pdf_anonymizer_core.prompts.detailed.prompt_template) when you want maximum accuracy (pairs well with BEST_QUALITY profile).
pdf_anonymizer_core.prompts.simple
¶
Simple (faster/cheaper) PII identification prompt.
This prompt is a lightweight version that asks the model only for basic "text" + "type" entities. It is faster and cheaper but does not request base_form or the full entity type list.
Use this prompt (via pdf_anonymizer_core.prompts.simple.prompt_template) for speed/cost sensitive workloads (pairs well with BEST_SPEED or BEST_COST).
pdf_anonymizer_core.prompts.hipaa
¶
HIPAA Safe Harbor aid prompt.
Asks for the identifier classes that apply to text (names, small-area geography, dates about a person, phones, emails, IDs, URLs, IPs, and so on).
This is a helper for coverage. It is not a legal determination that the output meets the HIPAA Safe Harbor standard.
Detection, operators, and reports¶
pdf_anonymizer_core.regex_ner
¶
Fast first-stage Named Entity Recognition using regular expressions (RE2 engine).
The hybrid pipeline (core.anonymize_file) ALWAYS runs this regex NER pass before the (more expensive) LLM stage. Matches are later merged/deduped with LLM results via a priority system in core.py (EMAIL, CREDIT_CARD, IBAN, crypto, SSN_* etc. receive elevated priority).
ENGINE¶
This module uses the RE2 engine (package "google-re2") instead of Python's stdlib
re. Benefits:
* Linear-time matching (O(length) worst-case). Immune to catastrophic backtracking
(ReDoS) even with complex patterns or adversarial input.
* Predictable performance on large PDFs / logs / code dumps.
* Same fundamental API surface: compile / finditer / match.group(0) / error.
RE2 LIMITATIONS (patterns here are written to be compliant)¶
- No look-ahead / look-behind assertions ((?=...), (?!...), (?<=...)).
- Limited support for some advanced Python re features (no recursive patterns, limited backreferences). All DEFAULT patterns and documented examples stay within the safe subset.
- word boundaries are supported and used for many PII tokens.
COUNTRY PARTITIONING & EXTENSIVE PII COVERAGE¶
Patterns are defined in pdf_anonymizer_core.conf.DEFAULT_REGEX_PATTERNS and are partitioned by country using ISO-2 suffixes (SSN_US, NINO_GB, INSEE_FR, AADHAAR_IN, RESIDENT_ID_CN, DNI_ES, CODICE_FISCALE_IT, ...).
Mandatory countries covered: USA, Canada, UK, Spain, Italy, France, India, China. 30+ total countries supported via dedicated national/tax/driver/VAT/business ID patterns + universal patterns (IBAN covers most EU+ countries, credit cards, crypto, VIN, MAC, etc. are global).
Supported categories (non-exhaustive): EMAIL, PHONE, URL, IPV4/IPV6, MAC, CREDIT_CARD, CURRENCY_AMOUNT, CRYPTO_BTC / CRYPTO_ETH, IBAN, BIC_SWIFT, VIN, DATE_ISO, SSN / SSN_US / SIN_CA / NINO_GB / INSEE_FR / AADHAAR_IN / RESIDENT_ID_CN, EIN_US, VAT_, PASSPORT_, DRIVERS_LICENSE_, MEDICAL_ , business regs, CURP_MX, CPF_BR, TFN_AU, NRIC_SG, HKID_HK, PESEL_PL, etc.
CUSTOMISATION¶
You can supply a completely custom regex_patterns: Dict[str, str] (type -> pattern)
when calling anonymize_file(). Keys become the emitted entity TYPE (upper-cased)
and are used for placeholder generation (EMAIL_1, IBAN_7, DRIVERS_LICENSE_CA_2, ...).
Only the keys you provide are used; there is no automatic merging with defaults
unless you build your dict from DEFAULT_REGEX_PATTERNS.
The function is intentionally tiny. It only does structural scanning, then runs a cheap checksum when one exists (Luhn, IBAN mod-97, VIN check digit, and a few national-ID checks). Failures are kept and relabeled TYPE_LIKE (for example IBAN_LIKE) so a mistyped number is still hidden. Semantic disambiguation, name coreference, and hard-to-regex PII remain the responsibility of the LLM stage that always follows.
extract_entities_via_regex(text: str, patterns: Dict[str, str]) -> List[EntityDict]
¶
Scans the text for PII using pre-configured regular expressions (RE2 engine).
Matches that fail a registered checksum (see validators.py) are kept and
labeled <TYPE>_LIKE (for example IBAN_LIKE).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
Input text to analyze. |
required |
patterns
|
Dict[str, str]
|
Dictionary mapping entity type strings (e.g. "IBAN", "SSN_US", "CRYPTO_ETH") to RE2-compatible regex pattern strings. |
required |
Returns:
| Type | Description |
|---|---|
List[EntityDict]
|
A list of EntityDict representing identified PII. "type" is always |
List[EntityDict]
|
upper-cased. "base_form" currently equals the matched text (core |
List[EntityDict]
|
consolidation may later promote variations to a longer base form). |
List[EntityDict]
|
Each hit also includes chunk-local |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/regex_ner.py
pdf_anonymizer_core.validators
¶
Cheap, unambiguous checksums for structured regex hits.
The regex stage is structural on purpose (RE2 cannot do Luhn, IBAN mod-97, etc.). After a match, this module checks the extra digit when one exists.
- Check passes: keep the real type (
IBAN). - Check fails: keep the text, but relabel as
IBAN_LIKEso a mistyped number is still hidden. Never drop the hit. - No registered check: accept the type unchanged.
Only attach a check when it is cheap and unambiguous. Do not invent rules for identifiers that have none (most SSNs, many passports, most VAT numbers).
has_checksum(entity_type: str) -> bool
¶
Return True if this type has a registered extra-digit check.
like_type(entity_type: str) -> str
¶
luhn_ok(digits: str) -> bool
¶
Return True if digits (0-9 only) passes the Luhn check.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/validators.py
parent_type(entity_type: str) -> str
¶
IBAN_LIKE -> IBAN; IBAN -> IBAN.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/validators.py
passes_checksum(entity_type: str, text: str) -> bool
¶
Return True if text has no check, or if its check succeeds.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/validators.py
type_matches_filter(entity_type: str, allowed: list[str] | set[str]) -> bool
¶
True if the type is listed, is a _LIKE sibling, or matches a prefix.
A listed DRIVERS_LICENSE matches DRIVERS_LICENSE_US.
A listed DATE matches DATE_ISO.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/validators.py
verhoeff_ok(digits: str) -> bool
¶
Return True if digits (0-9 only) passes the Verhoeff check.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/validators.py
pdf_anonymizer_core.operators
¶
Per-type operators for how a found value is written into the masked file.
Default is replace (typed stand-ins such as PERSON_1). Other operators
change what the reader sees; the mapping still records original → written form
so deanonymize can reverse when the written form is unique.
apply_operator(original: str, entity_type: str, placeholder: str, operator: str, base_form: Optional[str] = None, secret: str = '', encrypt_secret: str = '') -> str
¶
Return the string to write in place of original.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
decrypt_value(token: str, secret: str) -> str
¶
Reverse encrypt_value. Raises ValueError on a bad token or secret.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
encrypt_value(original: str, secret: str) -> str
¶
AES-256-GCM token. Same secret + same text always yields the same token.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
fake_value(original: str, entity_type: str, base_form: str, secret: str = '') -> str
¶
Stable fake in the same shape family. Same base_form always matches.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
generalize_value(original: str, entity_type: str) -> str
¶
Coarser value: year, ZIP3, or age band. Falls back to the original shape.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
mapping_without_encrypt_plaintexts(mapping: Dict[str, str]) -> Dict[str, str]
¶
Drop encrypt tokens so a leaked map does not hold those originals.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
mask_value(original: str, entity_type: str) -> str
¶
Keep a little shape; hide the rest.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
operator_for_type(entity_type: str, operators: Optional[Dict[str, str]]) -> str
¶
Resolve the operator for a type. CREDIT_CARD_LIKE follows CREDIT_CARD.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
parse_operator_specs(specs: Optional[Iterable[str]]) -> Dict[str, str]
¶
Parse TYPE=operator strings. Unknown operators raise ValueError.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
restore_encrypt_tokens(text: str, secret: str) -> str
¶
Replace every ENC1_ token. Unknown or wrong-key tokens stay as-is.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
shift_date_value(original: str, base_form: str) -> str
¶
Shift a date by a stable offset derived from base_form.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/operators.py
pdf_anonymizer_core.spans
¶
Locate and apply non-overlapping replacement intervals.
Entity texts are found in the full document with the same word-boundary
rules as before. Longer intervals win when two hits overlap, so
John Doe is replaced and the inner John is left alone.
apply_spans(full_text: str, spans: Sequence[Span], mapping: Dict[str, str]) -> str
¶
Write replacements from the end of the string so earlier offsets stay valid.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/spans.py
locate_spans(full_text: str, entity_texts: Iterable[str]) -> List[Span]
¶
Find every bounded occurrence of each entity text in full_text.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/spans.py
make_boundary_pattern(text: str) -> str
¶
Word-boundary wrap when the first/last character is alphanumeric.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/spans.py
pick_non_overlapping(spans: Sequence[Span]) -> List[Span]
¶
Keep longest spans first; drop any that overlap an accepted span.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/spans.py
replace_entities(full_text: str, entity_texts: Iterable[str], mapping: Dict[str, str]) -> str
¶
Locate, resolve overlaps, and replace. Empty entity list is a no-op.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/spans.py
pdf_anonymizer_core.gazetteers
¶
Keep-lists and deny-lists (gazetteers).
Keep-list: never replace this phrase, even if regex or the model found it. Deny-list: always replace this phrase, even if both stages missed it.
If a phrase is on both lists, keep wins (it stays visible).
apply_deny_list(text: str, entities: Sequence[Dict[str, str]], phrases: Iterable[str]) -> List[Dict[str, str]]
¶
Add a CUSTOM entity for each deny-list phrase that appears in text.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/gazetteers.py
apply_keep_list(entities: Sequence[Dict[str, str]], phrases: Iterable[str]) -> List[Dict[str, str]]
¶
Drop entities whose text or base form is on the keep-list (case-insensitive).
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/gazetteers.py
load_phrase_list(path: str) -> List[str]
¶
Load one phrase per line. Blank lines and # comments are ignored.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/gazetteers.py
pdf_anonymizer_core.verify
¶
Residual-PII scan for already-anonymized text.
Re-runs the cheap regex pass on the masked document and optionally asks a language model to look again. Hits that are only stand-in labels (PERSON_1, IBAN_LIKE_2) are ignored. The scan reports; it does not rewrite the file.
residual_report_path(anonymized_file_path: str) -> str
¶
data/stats/<stem>.residual_pii.json next to other stats files.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/verify.py
scan_residual_llm(text: str, model_name: str, prompt_template: str = RESIDUAL_LLM_PROMPT, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0) -> List[Dict[str, str]]
¶
Ask a language model for leftover personal details. May return [].
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/verify.py
scan_residual_regex(text: str, regex_patterns: Optional[Dict[str, str]] = None) -> List[Dict[str, str]]
¶
Return regex hits that are not stand-in labels.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/verify.py
verify_anonymized_text(text: str, *, anonymized_file: Optional[str] = None, regex_patterns: Optional[Dict[str, str]] = None, use_llm: bool = False, model_name: Optional[str] = None, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0) -> Dict[str, Any]
¶
Scan masked text. Never rewrites it.
Returns a report dict with regex_hits, optional llm_hits, and counts.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/verify.py
write_residual_report(report: Dict[str, Any], anonymized_file_path: str) -> str
¶
Write the report JSON and return its path.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/verify.py
pdf_anonymizer_core.risk
¶
Linkage-risk report for already-masked text.
A name can be gone and the page can still point to one person: job + company + city in the same paragraph. This module only scores those clumps. It does not change the file.
assess_entity_list(entities: Iterable[Dict[str, Any]]) -> Dict[str, Any]
¶
Score a flat entity list as one window (no positions). Does not rewrite.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/risk.py
assess_linkage_risk(text: str) -> Dict[str, Any]
¶
Score identity-clue clumps in masked text. Does not rewrite it.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/risk.py
placeholder_type(token: str) -> str
¶
PERSON_1.v_2 → PERSON; IBAN_LIKE_1 → IBAN_LIKE.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/risk.py
pdf_anonymizer_core.mapping_crypto
¶
Encrypt and decrypt mapping files (AES-256-GCM + Argon2id).
The mapping file is the key to the original names. With a passphrase the JSON is locked so a leaked file is not an instant deanonymization.
Envelope v2 (current):
- AES-256-GCM authenticated encryption
- Argon2id key derivation (OWASP interactive parameters by default)
- Document SHA-256 and mapping schema version bound as AEAD AAD
- Envelope fields validated before any KDF or decrypt
- Authentication checks use constant-time compares
- Derived keys and plaintext PII live in wipeable buffers
Envelope v1 (legacy, decrypt only): scrypt, no AAD. Still accepted so
existing *.mapping.json.enc files keep working.
Envelope is still JSON so you can tell an encrypted map from a plaintext
one without a special file type. We write *.mapping.json.enc by default.
EnvelopeMeta
dataclass
¶
Validated envelope fields. Built before any KDF or AEAD call.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
decrypt_mapping(payload: Dict[str, Any], passphrase: str, *, source_sha256: str | None = None, schema_version: int | None = None) -> Dict[str, str]
¶
Decrypt an envelope back to placeholder -> original.
Envelope metadata is validated first. If the caller supplies an expected source hash or schema version those values are compared in constant time against the authenticated AAD before the KDF runs. GCM then re-authenticates the same AAD so a swapped or replayed mapping fails the tag check.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
encode_aad(source_sha256: str, schema_version: int, envelope_version: int = VERSION) -> bytes
¶
Canonical AAD bytes bound into AES-GCM.
Newline-delimited, not JSON: callers cannot change the binding by adding spaces or reordering keys.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
encrypt_mapping(mapping: Dict[str, str], passphrase: str, *, source_sha256: str | None = None, schema_version: int | None = None, kdf_params: Mapping[str, int] | None = None) -> Dict[str, Any]
¶
Return a JSON-serializable encrypted envelope.
Extra keyword arguments are optional so existing callers
encrypt_mapping(mapping, passphrase) keep working. When
source_sha256 is omitted the AAD still binds an empty hash so the
field cannot be filled in later without breaking the tag.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
is_encrypted_mapping(payload: Any) -> bool
¶
True if payload looks like our encrypted envelope, not a name map.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
load_mapping_payload(raw_mapping: Any, passphrase: str | None, *, source_sha256: str | None = None, schema_version: int | None = None) -> Dict[str, str]
¶
Load plaintext or encrypted mapping JSON already parsed from disk.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
resolve_mapping_passphrase(explicit: str | None = None) -> str | None
¶
CLI flag wins; otherwise ANONYMIZER_MAPPING_KEY.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
sha256_file(path: str | os.PathLike[str]) -> str
¶
Return the lowercase hex SHA-256 of a file, streamed in 1 MiB chunks.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
validate_envelope(payload: Any) -> EnvelopeMeta
¶
Strict metadata checks. Raises ValueError before any crypto work.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/mapping_crypto.py
Low-Level Components (for advanced use / extension)¶
pdf_anonymizer_core.llm_provider
¶
LLM provider adapters + response caching layer.
This module contains: - LocalLLMCache: thread-safe on-disk caching of LLM responses (keyed by model + prompt hash) to avoid repeated calls during development or re-processing. - LLMProvider abstract base + concrete implementations for Google, Ollama, Hugging Face, OpenRouter, OpenAI, and Anthropic. - configure_cache() and get_provider() factory.
All providers implement a uniform .call(prompt, model_name) that goes
through the cache when enabled.
LLMProvider
¶
Bases: ABC
Abstract base class for LLM providers.
Subclasses must implement _call_raw. The public .call() method (inherited) adds transparent caching when the global cache is enabled.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/llm_provider.py
call(prompt: str, model_name: str, max_output_tokens: Optional[int] = None) -> str
¶
Public entry point used by the anonymizer.
Checks the cache (if enabled) before delegating to the concrete provider.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/llm_provider.py
configure_cache(enabled: bool, cache_dir: str = 'data/cache', cache_file: str = 'llm_responses.json')
¶
Enable or disable (and optionally relocate) the global LLM response cache.
Called automatically by the CLI according to the active AppConfig. You can also call it directly when using the core SDK.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether caching should be active. |
required |
cache_dir
|
str
|
Directory in which llm_responses.json will be stored. |
'data/cache'
|
cache_file
|
str
|
Filename for the JSON cache. |
'llm_responses.json'
|
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/llm_provider.py
get_provider(provider_name: str) -> LLMProvider
¶
Factory function to get a provider instance.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/llm_provider.py
pdf_anonymizer_core.call_llm
¶
LLM calling logic with structured parsing and resilient retries.
Contains: - Pydantic models used to validate LLM JSON responses (EntityModel, IdentificationResult). - classify_error(): decides which exceptions are worth retrying. - identify_entities_with_llm(): the main retrying wrapper that talks to providers.
classify_error(exception: Exception) -> tuple[bool, str]
¶
Classify an exception to determine if it is retryable and get a descriptive label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exception
|
Exception
|
The exception object. |
required |
Returns:
| Type | Description |
|---|---|
tuple[bool, str]
|
A tuple of (is_retryable, error_category). |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/call_llm.py
identify_entities_with_llm(text: str, prompt_template: str, model_name: str, max_retries: int = 3, base_retry_delay: float = 1.0, max_retry_delay: float = 10.0) -> List[dict]
¶
Call an LLM (via the configured provider) to extract PII entities from one chunk.
The call is wrapped with retry logic: - Transient errors (rate limits, 5xx, connection, parsing) are retried. - Auth errors are not retried. - Uses exponential backoff + jitter.
The response is cleaned of markdown fences and validated with Pydantic before returning a list of plain dicts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The chunk of text to analyze. |
required |
prompt_template
|
str
|
Prompt containing a {text} placeholder. |
required |
model_name
|
str
|
Model string (passed through get_provider_and_model_name). |
required |
max_retries
|
int
|
Maximum number of attempts. |
3
|
base_retry_delay
|
float
|
Starting backoff delay (seconds). |
1.0
|
max_retry_delay
|
float
|
Upper bound on backoff delay (seconds). |
10.0
|
Returns:
| Type | Description |
|---|---|
List[dict]
|
List of entity dicts (each with "text", "type", "base_form", |
List[dict]
|
"score" 0.70, and "source" "llm"). Returns [] on unrecoverable |
List[dict]
|
failure after retries. |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/call_llm.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
pdf_anonymizer_core.load_and_extract
¶
Text extraction and semantic chunking for PDF, Markdown, and plain text.
CSV, Excel, and Word files do not use this loader.
Uses pymupdf4llm for high-quality PDF → Markdown conversion (preserves structure useful for LLMs) and langchain text splitters: - MarkdownTextSplitter for .pdf and .md (respects headers/code blocks) - RecursiveCharacterTextSplitter for .txt / fallback
Chunk size and overlap are the primary controls for memory usage and LLM context consumption.
load_and_extract_text_from_file(file_path: str, characters_to_anonymize: int = 100000, chunk_overlap: int = 0, ocr: bool = False) -> Tuple[str, List[str]]
¶
Loads a file and extracts text, returning the full text and chunked text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
The path to the file. |
required |
characters_to_anonymize
|
int
|
Number of characters to process in each chunk. |
100000
|
chunk_overlap
|
int
|
Overlap size between chunks. |
0
|
Returns:
| Type | Description |
|---|---|
Tuple[str, List[str]]
|
Tuple[str, List[str]]: The full text as a string, and a list of chunk strings. |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/load_and_extract.py
load_and_extract_text_from_pdf(file_path: str, characters_to_anonymize: int = 100000, chunk_overlap: int = 0, ocr: bool = False) -> Tuple[str, List[str]]
¶
Loads a PDF file and extracts text from each page, returning the full text and chunked text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str
|
The path to the PDF file. |
required |
characters_to_anonymize
|
int
|
Number of characters to anonymize in one go (chunk size). |
100000
|
chunk_overlap
|
int
|
Overlap size between chunks. |
0
|
Returns:
| Type | Description |
|---|---|
Tuple[str, List[str]]
|
Tuple[str, List[str]]: The full text as a string, and a list of chunk strings. |
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/load_and_extract.py
pdf_anonymizer_core.span_ner.extract_entities_via_ner(text: str, *, model_name: str = DEFAULT_NER_MODEL, labels: Optional[Iterable[str]] = None) -> List[EntityDict]
¶
Return PERSON / ORGANIZATION / LOCATION / ADDRESS / DATE spans.
Does not emit identity clues (INDIRECT). That stays on the LLM path.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/span_ner.py
pdf_anonymizer_core.span_ner.resolve_semantic_stages(*, use_llm: bool, use_ner: Optional[bool], replace_llm_when_ner: bool) -> tuple[bool, bool]
¶
Return (run_ner, run_llm).
use_ner is True / False / None (auto). Auto turns NER on only when
the extra is installed and the language model would have run. Speed and
cost profiles set replace_llm_when_ner so NER replaces the LLM.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/span_ner.py
pdf_anonymizer_core.pdf_ocr.ocr_pdf(path: str, *, language: str = 'eng', dpi: int = 200) -> tuple[str, list[PdfWord]]
¶
OCR every page. Returns (plain_text, words_with_boxes).
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/pdf_ocr.py
pdf_anonymizer_core.pdf_ocr.write_layout_sidecar(dest_path: str, source: str, words: Iterable[PdfWord]) -> str
¶
Write <anonymized-stem>.layout.json next to the anonymized file.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/pdf_ocr.py
pdf_anonymizer_core.pdf_output.write_anonymized_pdf(source_path: str, dest_path: str, orig_to_written: Dict[str, str], entity_texts: Iterable[str], *, redact: bool = False) -> None
¶
Rewrite source_path as a sanitized .pdf.
When redact is false, each hit is replaced with its written stand-in
(reversible with the mapping). When redact is true, the hit becomes a
black box and cannot be restored from the page.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/pdf_output.py
pdf_anonymizer_core.pdf_output.sanitize_pdf(document) -> None
¶
Drop identity-bearing extras on every native-PDF write.
Clears /Info, XMP, embedded files, leftover annotations, optional
content groups, and outline titles are left alone only when they have
no leftover PII we can see. Incremental /Prev history is avoided by
a full rewrite at save time (garbage=4, not incremental).
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/pdf_output.py
Tables (CSV / Excel)¶
pdf_anonymizer_core.tables.load_table(path: str) -> TableDocument
¶
Load a .csv or .xlsx file as a TableDocument.
Raises ValueError for rejected spreadsheet suffixes, a missing
[excel] extra, or a file over the size / cell cap.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/tables.py
pdf_anonymizer_core.tables.save_table(doc: TableDocument, path: str) -> None
¶
Write doc as .csv or .xlsx. Excel formulas are not persisted.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/tables.py
pdf_anonymizer_core.tables.apply_mapping_to_table(doc: TableDocument, orig_to_written: Dict[str, str], entity_texts: Iterable[str]) -> TableDocument
¶
Replace detected entity texts in each cell. Does not use mapping keys.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/tables.py
pdf_anonymizer_core.tables.flatten_table_for_review(doc: TableDocument, *, anonymized: bool = True) -> str
¶
Row-wise flatten for verify / risk / consolidate.
Blank line after the sheet header and after every row, including the last, so risk windows do not glue a header or the next sheet onto a data row.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/tables.py
pdf_anonymizer_core.tables.load_review_text(path: str) -> str
¶
Load text for verify / report. Tables and Word files are flattened.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/tables.py
Word (DOCX)¶
pdf_anonymizer_core.word.load_docx(path: str) -> WordDocument
¶
Load a .docx file as a WordDocument.
Raises ValueError for rejected Word suffixes, a missing [docx]
extra, a file over the size / block cap, or an unreadable package.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/word.py
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
pdf_anonymizer_core.word.save_docx(doc: WordDocument, path: str) -> None
¶
Write the live python-docx package to path.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/word.py
pdf_anonymizer_core.word.apply_mapping_to_docx(doc: WordDocument, orig_to_written: Dict[str, str], entity_texts: Iterable[str]) -> WordDocument
¶
Replace detected entity texts in each block. Does not use mapping keys.
Source code in packages/pdf-anonymizer-core/src/pdf_anonymizer_core/word.py
pdf_anonymizer_core.word.flatten_docx_for_review(doc: WordDocument, *, anonymized: bool = True) -> str
¶
Part-wise flatten for verify / risk / consolidate.
Blank line after the part header and after every block, including the last, so risk windows do not glue a header onto the next paragraph.