TagStyle#
- class scikitplot.cleanprompt.TagStyle(prefix='[', suffix=']', separator='-', uppercase_kind=True, style='placeholder', surrogates=None, surrogate_set=None)[source]#
The placeholder grammar.
- Parameters:
- prefixstr, default=’[’
Opening delimiter.
- suffixstr, default=’]’
Closing delimiter.
- separatorstr, default=’-’
Separator between the category and the ordinal.
- uppercase_kindbool, default=True
Whether to upper-case the category in the rendered label.
- style{‘placeholder’, ‘surrogate’}, default=’placeholder’
What a removed value is replaced with.
placeholderrenders a bracket label;surrogatesubstitutes an invented but ordinary- looking value for the kinds that have one, falling back to a label for the rest. Seescikitplot.cleanprompt._surrogates.This belongs to the grammar rather than to the wider policy because it changes how a stand-in is spelled, and restoration checks the grammar digest before reading a vault. A vault written in one style therefore cannot be silently read as the other.
- surrogatesstr, optional
The identity (
name@version#digest16) of the custom surrogate set whose names this grammar issues. Filled in fromsurrogate_setwhen that is given; recorded in a vault, sodecodeneeds no set file. Requiresstyle='surrogate'.- surrogate_setSurrogateSet, optional
The set itself (
_surrogate_sets), needed to issue stand-ins. Not compared, not serialised: restoring reads stand-ins from the vault and needs onlysurrogates.
- Raises:
- PolicyError
If any part is empty, or if the suffix could begin inside a label and make the grammar ambiguous.
- Parameters:
Notes
Developer notes. A non-empty
suffixis mandatory, and this is load-bearing rather than cosmetic. With a closing delimiter,[EMAIL-1]is not a prefix of[EMAIL-11], so a naive scan cannot confuse the two. Remove the suffix and ordinals above nine begin to corrupt each other. The upstream project happened to be safe here; making the suffix mandatory turns that accident into a guarantee, andtest__engine.py::test_ordinal_ten_does_not_collidekeeps it honest.Examples
>>> TagStyle().render("email", 3) '[EMAIL-3]' >>> TagStyle(prefix="<<", suffix=">>", separator="_").render("email", 3) '<<EMAIL_3>>'
- property fingerprint: str#
Stable 16-character digest of this grammar.
Notes
Developer notes. This is deliberately narrower than
RedactionPolicy.fingerprint, and the two answer different questions.Restoration cares about exactly one thing: were these labels written in the grammar I am about to read them with? It does not care which kinds were detected, what the limits were, or how overlaps were arbitrated — none of those can change how
[EMAIL-1]is spelled. Checking the full policy digest at restoration time would reject a perfectly valid vault merely because the caller had narrowedkinds, which is a false alarm on the common path.The full policy digest remains on
RedactionResult, where it serves its own purpose: reproducing a pass exactly.- Type:
- lenient_pattern()[source]#
Return a recogniser that also matches labels a model has rewritten.
- Returns:
- re.Pattern
A superset of
pattern, with the samekindandordinalgroups.
- Return type:
Notes
Developer notes — the channel is not lossless.
Every other invariant in this submodule holds across parts it controls. Restoration does not: between redaction and restoration the text passes through a language model, which rewrites tokens. Measured against real replies, a label comes back in these shapes:
[EMAIL-1] as issued [email-1] lower-cased [EMAIL_1] separator changed [EMAIL 1] separator changed to a space [EMAIL<U+2011>1] ASCII hyphen replaced with a Unicode dash \[EMAIL-1\] brackets escaped for Markdown [EMAIL\_1] separator escaped for Markdown [EMAIL-<NL>1] wrapped across a line
The table is written literally: every row above is the exact text a recogniser has to accept, except for two characters a source file cannot show inline –
<U+2011>is one non-breaking hyphen and<NL>is one line break.CP-044exists because an earlier version of this table was written in doubled escapes and was therefore easier to check against itself than against the pattern.Exact matching restores the first and misses the rest, which is how a reply arrives reporting “0 restored” while plainly full of placeholders.
The escaped separator is the composition of two shapes already in that list rather than a new one: a model writing
[EMAIL_1]inside Markdown escapes the underscore, because an unescaped one would open emphasis. It is admitted for the same reason the escaped brackets are, and it was found byCP-044— by checking the list above against the implementation rather than against itself.Admitting it widens the match set by exactly the escaped spellings and widens the prose match set by nothing, because the surrounding structure — delimiter, letter-initial category, digits, delimiter — is untouched. That was measured rather than reasoned about.
This pattern accepts that bounded set — case, the separator family, internal whitespace, and backslash escapes — and nothing else. It does not accept a missing delimiter or an invented category, because those cannot be told apart from ordinary prose. The category must start with a letter, so a footnote marker like
[1]is never a candidate.Matching is only half of it. A lenient match is acted on only when the label it normalises to is in the vault, and the caller reports every one it acted on; see
restore.Examples
>>> style = TagStyle() >>> bool(style.lenient_pattern().search("see [email_1] there")) True >>> bool(style.lenient_pattern().search(r"see [email\_1] there")) True >>> bool(style.lenient_pattern().search("see [1] there")) False >>> style.lenient_pattern().search(r"a\\[EMAIL-1]").group() '[EMAIL-1]'
- normalize(candidate)[source]#
Return the canonical label that a rewritten one was meant to be.
- Parameters:
- candidatestr
Text matched by
lenient_pattern.
- Returns:
- str or None
The label as this grammar renders it, or
Nonewhen the text is not a rewritten label at all.
- Parameters:
candidate (str)
- Return type:
str | None
Notes
Developer notes. The canonical label is rebuilt from the
kindandordinalgroups rather than repaired in place, so whatever a model did to the separator and the delimiters cannot reach the result. That is why admitting a new rewrite shape tolenient_patternneeds no change here.Examples
>>> TagStyle().normalize("[email_1]") '[EMAIL-1]' >>> TagStyle().normalize(r"[email\_1]") '[EMAIL-1]' >>> TagStyle().normalize("not a label") is None True
- pattern()[source]#
Return the compiled recogniser for labels of this grammar.
- Returns:
- re.Pattern
A pattern with two named groups,
kindandordinal, matching any label this style can render.
- Return type:
Notes
Developer notes. Restoration scans with this pattern and then does a dictionary lookup, instead of replacing each known label in turn. That is a single left-to-right pass whose cost is independent of the number of entries, and — more importantly — it cannot let one label’s replacement text be re-matched as another label.
- render(kind, ordinal)[source]#
Render one placeholder label.
- Parameters:
- kindstr
Category, for example
"email".- ordinalint
1-based index within the category.
- Returns:
- str
The label, including delimiters.
- Raises:
- PolicyError
If
kindis empty, contains a character reserved by the placeholder grammar, orordinalis not positive.
- Parameters:
- Return type:
Gallery examples#
Cleanprompt Advanced: Invariants, Encryption, and the Lossy Model Hop
Cleanprompt Moderate: Policies, Entities, and the Vault’s Lifecycle
Cleanprompt from Python: Single Calls and Edge Cases