Quickstart¶
Programmatic API¶
Use the Entity and Action constants — your IDE autocompletes them and a typo
is caught at edit time. They are interchangeable with the string forms
(Entity.EMAIL == "EMAIL").
from wardcat import Wardcat, Entity, Action
guard = (
Wardcat(salt="my-secret-salt")
.add_entity(Entity.CREDIT_CARD, Action.HASH)
.add_entity(Entity.EMAIL, Action.WARN)
.add_entity(Entity.TC_ID, Action.HASH)
)
result = guard.scan("Customer: Ali Veli, TC: 12345678950, card: 4111 1111 1111 1111")
print(result.sanitized_text)
for v in result.violations:
print(f"[{v.action}] {v.entity_type}: {v.original!r}")
Enable many at once with add_entities() (a list, a {name: action} mapping, or
a {name: {action, layers}} mapping), or turn on everything then prune:
from wardcat import Wardcat, Entity
guard = (
Wardcat(salt="s")
.add_entity(Entity.ALL, action="hash") # everything on, hashed
.remove_entity(Entity.ORG) # …except organisation names
)
Predefined groups pair naturally with add_entities():
from wardcat import Wardcat, turkish_entities
guard = Wardcat(salt="s")
guard.add_entities(turkish_entities(), action="hash") # a whole group, one action
guard.add_entities(["EMAIL", "CREDIT_CARD", "IBAN"], action="redact")
guard.add_entities({ # per-entity actions and layers
"CREDIT_CARD": "hash",
"EMAIL": {"action": "mask"},
"SPECIAL_CATEGORY": {"action": "redact", "layers": ["llm"]},
})
The groups — core_entities, financial_entities, turkish_entities,
european_entities, uk_entities, us_entities, network_entities,
identity_entities, all_entities — are importable from wardcat. Every
entity, with its default action, is on the entity types
page.
Declarative API (YAML)¶
salt: "" # read from env in production
entities:
CREDIT_CARD: { enabled: true, action: hash }
EMAIL: { enabled: true, action: warn }
TC_ID: { enabled: true, action: hash }
The result¶
scan() returns a ScanResult:
result = guard.scan(text)
result.sanitized_text # anonymized output — safe to forward to an LLM
result.violations # list[Violation] — each with entity_type, original, action, confidence
result.is_clean # True if nothing was found
result.warnings # non-empty if a layer could not run (degraded scan)
result.redacted() # PII-free dict — safe for logs / API responses
Raw PII
result.original_text and violations[].original contain raw PII. Use
result.redacted() for logs and API responses.
Batch¶
results = guard.scan_batch(["ali@example.com", "Card: 4111 1111 1111 1111", "Clean."])
for r in results:
print(r.is_clean, len(r.violations))
Examples¶
Runnable scripts in examples/:
| File | Shows |
|---|---|
demo.py |
Programmatic + YAML APIs |
batch_and_async.py |
scan_batch and the async API (regex-only, no services) |
llm_hybrid.py |
regex + NER + LLM with ensemble adjudication (needs Ollama) |
all_layers.py |
all three layers on one guard, with adjudication (needs Ollama + a SpaCy model) |
reversible_roundtrip.py |
Action.TOKENIZE out, restore() back — regex-only, stubbed model, runs offline |
asgi_middleware.py |
Copy-paste ASGI middleware (FastAPI/Starlette) that scans request bodies — wardcat ships no web-framework code; this is a self-contained example |
Next: enable the detection layers you need.