Transformation Engine
Strategy-based PII transformation with built-in and custom strategies.
Transformation Engine
The TransformationEngine applies transformation strategies to detection results. Each strategy defines how an individual finding is replaced in the output text.
Built-in strategies
FastPII provides four built-in strategies:
| Strategy | Method | Output |
|---|---|---|
AnonymizeStrategy | engine.anonymize() | Replaces PII with [REDACTED] |
RedactStrategy | engine.redact() | Replaces PII with type labels like [EMAIL] |
MaskStrategy | engine.mask() | Replaces PII with * preserving span length |
RemoveStrategy | engine.remove() | Deletes PII entirely |
Convenience methods
The FastPII engine provides convenience methods that detect and transform in one call:
from fastpii import FastPII, DEFAULT_PRIORITY
from fastpii.countries.cz import CzechPack
engine = FastPII(priority=DEFAULT_PRIORITY)
engine.register(CzechPack())
text = "Email: jan.novak@example.cz, RČ: 8001011238"
engine.anonymize(text) # Email: [REDACTED], RČ: [REDACTED]
engine.redact(text) # Email: [EMAIL], RČ: [RODNE_CISLO]
engine.mask(text) # Email: *******************, RČ: **********
engine.remove(text) # Email: , RČ:Direct strategy usage
Use TransformationEngine.apply() when you need fine-grained control:
from fastpii.core.transform import TransformationEngine, AnonymizeStrategy, RedactStrategy, MaskStrategy, RemoveStrategy
result = engine.detect(text)
# Apply each strategy
anonymized = TransformationEngine.apply(result, AnonymizeStrategy(replacement="<PII>"))
redacted = TransformationEngine.apply(result, RedactStrategy())
masked = TransformationEngine.apply(result, MaskStrategy())
removed = TransformationEngine.apply(result, RemoveStrategy())Custom strategy
Create your own strategy by implementing the TransformationStrategy protocol:
from fastpii.core.transform import TransformationStrategy
from fastpii.models import Finding
class HashStrategy:
"""Replace PII with its SHA-256 hash prefix."""
def replace(self, finding: Finding, text: str) -> str:
import hashlib
return hashlib.sha256(finding.value.encode()).hexdigest()[:8]
result = engine.detect(text)
transformed = TransformationEngine.apply(result, HashStrategy())TransformationStrategy protocol
from typing import Protocol, runtime_checkable
from fastpii.models import Finding
@runtime_checkable
class TransformationStrategy(Protocol):
def replace(self, finding: Finding, text: str) -> str:
"""Return the replacement string for the given finding.
Args:
finding: The detected PII finding with start, end, type, etc.
text: The original input text (for context if needed).
Returns:
The string to insert at text[start:end].
"""
...The TransformationEngine processes findings in reverse order (by start position) so that replacements do not shift subsequent character positions.
How masking uses span length
MaskStrategy uses finding.end - finding.start for the mask length, not len(finding.value). This ensures correct masking when detectors normalize values (e.g., phone numbers with formatting removed):
# If a detector finds "+420 123 456 789" at position 10-27
# But normalizes to "420123456789" internally
# MaskStrategy still masks positions 10-27 with asterisksAnonymizeStrategy with custom replacement
from fastpii.core.transform import AnonymizeStrategy
result = engine.detect(text)
# Default replacement
anonymized = TransformationEngine.apply(result, AnonymizeStrategy())
# [REDACTED]
# Custom replacement
custom = TransformationEngine.apply(result, AnonymizeStrategy(replacement="<PII>"))
# <PII>
# Unique placeholder per type
type_tagged = TransformationEngine.apply(result, AnonymizeStrategy(replacement="[REMOVED]"))
# [REMOVED]