Skip to content

Latest commit

 

History

History
116 lines (87 loc) · 3.36 KB

File metadata and controls

116 lines (87 loc) · 3.36 KB
layout default
title Validators
nav_order 3

Validators guide

A validator is any object with a validate(value) method that returns None on success and raises ValidationError on failure. That is the entire contract — user-defined validators are first-class.

from vcoder import Validator, MinLength, ValidationError

MinLength(5).validate("hello")     # ok
MinLength(5).validate("hi")        # raises ValidationError

Composition

Validator(*children) composes validators and runs them in order, stopping at the first failure. Because a composite is a validator, they nest freely.

from vcoder import Validator, MinLength, MaxLength, Regex, NoControlCharacters

password_rules = Validator(
    MinLength(12),
    MaxLength(128),
    Regex(r".*[A-Z].*", full=True),
    NoControlCharacters(),
)
password_rules.is_valid("Sup3rSecretPassphrase")   # True/False

Plain callables work too — anything callable(value) that raises ValidationError:

def no_at_sign(value):
    if "@" in value:
        raise ValidationError("no @ allowed")

Validator(no_at_sign).validate("hello")

Built-in validators

Validator Checks
Min(n) / Max(n) / Range(lo, hi) Ordered comparison bounds
MinLength(n) / MaxLength(n) / Length(min, max, exact=) len(value) bounds
Regex(pattern, flags=0, full=True) Regular-expression match
NoControlCharacters() Rejects control characters
Printable() Every character printable
Ascii() Pure ASCII
NoWhitespace() No whitespace characters
Unicode(allow=, deny=) Restrict Unicode categories
Entropy(min_bits, estimator=) Minimum estimated strength
Unique() All elements distinct
Blacklist(set, case_insensitive=) Value not in a forbidden set
Whitelist(set, case_insensitive=) (alias OneOf) Value in an allowed set
Predicate(func, message) Arbitrary boolean predicate

Using validators with prompts

Pass any validator to a prompt via validators=:

from vcoder import vc, Regex, NoControlCharacters

slug = vc.text(
    "Slug: ",
    validators=[Regex(r"[a-z0-9-]+"), NoControlCharacters()],
)

Built-in prompt options (min, max, min_length, pattern, ...) are just shortcuts that add the corresponding validators for you.

Writing your own

Subclass Validator and override validate:

from vcoder import Validator, ValidationError

class Even(Validator):
    def validate(self, value):
        if value % 2:
            raise ValidationError("must be even", value=value)

vc.int("Even number: ", validators=[Even()])

A note on error messages and secrets

ValidationError stores the offending value on the exception object, but its string form (used for reprompting and appearing in logs/tracebacks) never includes the value. This keeps secrets out of your logs even when a password fails validation. Keep this contract in your own validators: put context in the message, put the raw value in value=.

Entropy and strength

Entropy(min_bits) uses a simple brute-force estimate by default. It is good for catching obviously weak strings, not for precise strength scoring. To use a better estimator (e.g. zxcvbn), pass estimator=:

from vcoder import Entropy

# def zxcvbn_bits(s: str) -> float: ...
Entropy(60, estimator=zxcvbn_bits)