validators review
validators 0.35.0 is a set of typed Python functions for checking individual values without defining a form or object schema. It covers email, URL, hostname, domain, IP, UUID, hashes, country and currency codes, cron expressions, card numbers, lengths, ranges, and regional identifiers. Success is the literal `True`; failure is a false-valued `ValidationError` containing the validator and its arguments, unless raising mode is requested. Version 0.35.0 drops Python 3.8, adds Russian INN and Mir card checks, lets callers define accepted URL schemes, recognizes `.onion`, and fixes cases in email, URI, fragment, and DOI handling.
validators 0.35.0 installed in 0.2 seconds and used 1 MB with 1 dependency and 0 audit findings in our sandbox, while its typed import worked in 0.16 seconds. Install it for isolated format checks; choose a schema library for structured inputs, and never treat syntax acceptance as proof that an address, account, or resource exists.
We installed it
| Install | ✓ · 0.2s | 1 package on disk · 1 MB |
| Import | ✓ | import validators in 0.16s · pure Python · py.typed · requires Python >=3.9 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does validators install cleanly?
Yes. In a fresh container with an empty cache, pip install validators finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.
What does validators need to run?
Python >=3.9, and nothing compiled: it is pure Python. In our run import validators succeeded in 0.16s, and the package ships py.typed for type checkers.
validators or pydantic: which should you use?
pydantic: Use Pydantic for typed models, nested data, coercion, cross-field rules, and structured error locations. validators 0.35.0 installed in 0.2 seconds and used 1 MB with 1 dependency and 0 audit findings in our sandbox, while its typed import worked in 0.16 seconds.
When should you not use validators?
Nested request models need coercion, field paths, cross-field rules, and collected errors. validators deliberately checks one value at a time.
Use it if
- A few boundary values need readable format checks without introducing a model or form system.
- Failure metadata should remain available while ordinary `if` checks stay concise.
- URL, host, domain, or IP policy needs explicit flags for scheme, private addresses, TLDs, query parsing, or local names.
- The application already owns input normalization, public error messages, and any real-world verification beyond syntax.
- Nested request models need coercion, field paths, cross-field rules, and collected errors. validators deliberately checks one value at a time.
- An email inbox, hostname, card, or account must be proven to exist. These functions check syntax and known number rules; they do not establish delivery, DNS ownership, or authorization.
- Existing code compares results with `is False`. Invalid input returns a `ValidationError` object whose truth value is false, not the Boolean singleton.
- Validation policy must be identical across several services without shared wrapper code. Options such as `consider_tld`, `private`, `strict_query`, and custom schemes materially change acceptance.
- A production-stable classifier is mandatory. PyPI still labels 0.35.0 Beta, and the README points to a separately owned temporary documentation site while versioned docs are rebuilt.
Setup reality
We installed validators 0.35.0 in a fresh Python 3.12 Bookworm sandbox. pip completed in 0.2 seconds, left 1 package using 1 MB, and installed 1 direct dependency. import validators worked in 0.16 seconds, and pip-audit found 0 known vulnerabilities. The package is pure Python, requires Python 3.9 or newer, ships a py.typed marker, and declares the MIT license.
No account or config file is required. The first integration decision is result handling: valid input returns True, while invalid input returns a false-valued ValidationError. Boolean checks work, but result is False does not, and the failure object is unsuitable as a raw JSON response. Keep its function and arguments for logs, then map it to your own stable error shape.
Pass r_ve=True to a decorated validator when failure should raise. The process-wide RAISE_VALIDATION_ERROR=True environment switch changes every wrapped validator, including calls inside other dependencies, so local raising is easier to reason about. validators checks input as supplied; trim whitespace, select a Unicode policy, and store canonical email, host, URL, or identifier forms yourself.
Version 0.35.0 accepts Python 3.9 even though that runtime reached end of life in October 2025. URL policy also needs explicit choices for schemes, private IP literals, local hosts, TLD checks, fragments, ports, and strict query parsing. A hostname that later resolves to a private address still needs SSRF defenses, and a card passing Luhn or a brand prefix does not reduce payment-data compliance obligations.
Patterns
Check one email string validate-email
import validators
result = validators.email('someone@example.com')
if result:
accept_address()
else:
reject_address(result.func.__name__)The check covers format only. Failure is a false-valued `ValidationError`, and no DNS or mailbox lookup occurs.
Raise for invalid input raise-error
import validators
from validators import ValidationError
try:
validators.email('wrong@@', r_ve=True)
except ValidationError as error:
print(error.func.__name__, error.value)`r_ve=True` is handled by the common validator decorator. It changes this call without changing every validator in the process.
Accept only HTTP and HTTPS restrict-url-scheme
import validators
def web_scheme(scheme: str) -> bool:
return scheme in {'http', 'https'}
result = validators.url(
'https://example.com/docs?q=python',
validate_scheme=web_scheme,
)Custom scheme validation was added in 0.35.0. The default URL policy accepts schemes beyond HTTP and HTTPS.
Reject a private IP literal in a URL reject-private-ip-url
import validators
result = validators.url('http://192.168.1.10/admin', private=False)
assert not result`private=False` checks an IP literal in the URL. A public hostname can still resolve to a private address later, so this alone does not stop SSRF.
Check a domain against known TLDs require-known-tld
import validators
assert validators.domain('example.com', consider_tld=True) is True
assert validators.domain('service.onion', consider_tld=True) is TrueVersion 0.35.0 recognizes `.onion` in its TLD handling. Internal suffixes may need `consider_tld=False` or separate policy.
Require a private IPv4 value validate-private-ipv4
import validators
assert validators.ipv4('10.2.3.4', private=True) is True
assert not validators.ipv4('8.8.8.8', private=True)The `private` option groups categories according to the implementation. Confirm loopback, link-local, and broadcast expectations for your security rule.
Accept a UUID object or string validate-uuid
from uuid import UUID
import validators
value = '2bc1c94f-0deb-43e9-92a1-4775189ec9f8'
assert validators.uuid(value) is True
assert validators.uuid(UUID(value)) is TrueThe function accepts UUID instances and parseable UUID strings. Check the source or a separate rule when one UUID version is mandatory.
Check a lowercase ASCII slug validate-slug
import validators
assert validators.slug('release-notes-2026') is True
assert not validators.slug('Release Notes')`slug()` validates lowercase ASCII words joined by single hyphens. It does not normalize text or prevent collisions.
Bound a display-name length check-length
import validators
assert validators.length('display name', min_val=3, max_val=40) is True`length()` uses Python `len()`, which counts Unicode code points rather than encoded bytes or user-perceived grapheme clusters.
Validate an inclusive datetime floor check-range
from datetime import datetime, timezone
import validators
result = validators.between(
datetime.now(timezone.utc),
min_val=datetime(2026, 1, 1, tzinfo=timezone.utc),
)Bounds are inclusive. Incomparable values or mixed naive and aware datetimes become a failure object unless raising mode is enabled.
Check a Mir card number format validate-mir-card
import validators
assert validators.mir('2200123456789019') is True
assert not validators.mir('4242424242424242')`mir()` was added in 0.35.0 and checks the Mir prefix, 16-digit length, and Luhn result. It does not prove the account exists.
Check a Russian tax identifier validate-russian-inn
import validators
assert validators.ru_inn('500100732259') is True
assert validators.ru_inn('7830002293') is True`ru_inn()` was added and exposed at package level in 0.35.0. It accepts valid 10-digit company and 12-digit individual control-number forms.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| pydantic | PyPI | Use Pydantic for typed models, nested data, coercion, cross-field rules, and structured error locations. |
| email-validator | PyPI | Use email-validator when normalization and optional DNS deliverability checks are central. |
| rfc3986 | PyPI | Use rfc3986 when URI components need RFC-focused parsing, normalization, and inspection. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

