FastPII Docs
Guides

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:

StrategyMethodOutput
AnonymizeStrategyengine.anonymize()Replaces PII with [REDACTED]
RedactStrategyengine.redact()Replaces PII with type labels like [EMAIL]
MaskStrategyengine.mask()Replaces PII with * preserving span length
RemoveStrategyengine.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 asterisks

AnonymizeStrategy 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]

On this page