validators: one function per value, with no schema to define
Python Data Validation for Humans™.
At a glance
- What is it?
- validators is an MIT-licensed Python package that validates a single value with a single call, `validators.email('[email protected]')`, instead of defining a schema or a form. The judgement is that the zero-dependency design and the function-per-type surface are the reason it survives in dependency-light codebases, and that the 0.x version line and the per-call cost are what you pay for the convenience.
- Who is it for?
- validators fits a Python developer who needs to check a value here and there, in a script, a data pipeline, a webhook handler or a form field, and who does not want to introduce a schema library to do it. It does not fit a service that needs a declarative contract shared between a backend and a frontend, and it does not fit a codebase that already standardised on a validation library with model integration.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The whole design is the absence of a schema
The README opens by naming the problem the library exists to solve, and the framing is unusually direct.
Python has all kinds of data validation tools, but every one of them seems to require defining a schema or form. The author wanted to create a simple validation library where validating a simple value does not require defining a form or a schema.
The result is a function per value:
>>> import validators
>>>
>>> validators.email('[email protected]')
TrueThat is the entire interface for the common case. A validator returns a boolean for a single value, and you compose it into whatever control flow you already have. There is no model class, no declarative document, and no serialisation step to keep in step with your data structures.
The practical effect is on the dependency graph. The package metadata declares `dependencies = []`, so a project that adds validators adds nothing to its transitive dependency tree. For a library that other people install transitively, that is the difference between being adopted widely and being avoided, and it is the reason this approach has survived while richer schema libraries have not always managed it.
The package is `validators` on PyPI, the author listed in the metadata is Konsta Vesterinen, the licence is MIT with the text in `LICENSE.txt`, and the repository classifies itself as `Development Status :: 4 - Beta` with the operating system as OS Independent and the implementation as CPython only.
The dependency list is empty, and the one extra is deliberate
The empty dependency list is the headline architectural fact, and the one optional dependency shows how carefully the boundary is drawn.
The `crypto-eth-addresses` extra pulls in `eth-hash[pycryptodome]>=0.7.0`, and nothing else is required. So the Ethereum address validators, which need to compute a checksum or a hash of the address, are opt-in and bring their own crypto stack, while the rest of the library stays dependency-free.
That is a good pattern. The alternative would be to always depend on a hashing library for the sake of a handful of validators, or to reimplement the address checks by hand, and both are worse. A user who never touches crypto addresses never pays for it, and a user who does gets the right implementation rather than an approximation.
The Python floor is the other constraint to note. `requires-python = ">=3.11"`, and the classifiers list 3.11, 3.12, 3.13 and 3.14. The README also carries a note that Python 3.11 reaches EOL in October 2027, which is the kind of reminder that tells you the maintainer is tracking the upstream support timeline.
The build system is setuptools through `setuptools.build_meta`, with the version marked dynamic, and there is a `uv.lock` at the repository root, so the project is developed with uv even though the build backend is setuptools. The `package/` directory, `src/`, `tests/`, `docs/`, `mkdocs.yml` and a `MANIFEST.in` complete the layout, along with `CHANGES.md`, `CONTRIBUTING.md` and a `SECURITY.md`.
The release gap says more than the version number
The version history has a shape worth understanding before you depend on this.
The listed releases are 0.34.0 on 2024-09-03, 0.35.0 on 2025-05-01 and 0.36.0 on 2026-10-10. So the last three releases span about two years, and the gap between 0.35.0 and 0.36.0 was about seventeen months.
The last push to the repository was on 2026-10-11, the day after the 0.36.0 release, and the repository is not archived. So the code is being worked on, and the release that just shipped is close to the tip of the tree.
The version numbering is the part to plan around. A 0.x version carries no compatibility guarantee under semantic versioning, so a minor bump can change the API. This library's surface has moved across major versions, and the 0.35.0 to 0.36.0 gap being that long is consistent with a release that consolidates a batch of changes rather than a routine increment.
For a consumer, the practical advice is to pin the version you test against and read `CHANGES.md` before moving. The project publishes a changelog at `CHANGES.md` in the repository, and the package metadata links to it directly as the Changelog URL, so the record is public and versioned.
The continuous integration setup suggests a maintained project rather than an abandoned one. There is a PyCQA badge for the pytest workflow and a separate SAST badge for a static analysis workflow, and the documentation is built and deployed through GitHub Pages in its own workflow.
Documentation lives on a fork because Pages access is a contributor problem
There is a small administrative story in the README that explains where the documentation lives, and it is worth understanding because it affects where you look for the API reference.
The documentation is published through GitHub Pages on the maintainer's fork, at nandgator.github.io/pyvalidators, because the maintainer has only contributor access to this repository and cannot enable Pages for it.
So the canonical repository is `python-validators/validators` and the rendered documentation is served from `nandgator/pyvalidators`. The README also carries a commented-out line with the original documentation URL, `python-validators.github.io/validators/`, and a note that the original docs URL will be restored once properly versioned docs are ready.
The Resources list in the README points at the documentation, the bugtracker at the repository's issues, the security file and the code. There is a separate `SECURITY.md` in the repository root.
For a user this arrangement has one practical consequence. The version of the documentation you are reading is not necessarily tied to the version of the package you have installed, and the repository is on the `master` branch as the default. If a validator's behaviour differs between what the docs describe and what you get from PyPI, the pinned package version and `CHANGES.md` are the reliable sources, not the live documentation site.
The project also appears on PyPI with both a version badge and a downloads badge, and the repository has a `SECURITY.md` and a `CONTRIBUTING.md`, so there is a public process for reporting problems even though the maintainer relationship is described in contributor terms rather than owner terms.
Where the single-function model strains
The design has a clear boundary, and it is worth being explicit about where the boundary is.
The first limit is that a validator is a boolean and nothing more. When a value fails, the library says no, and it does not tell you why. If a user typed an email address incorrectly, your application still has to work out whether it was a missing at sign, a missing domain, or a stray space, and it will have to write that logic. The rich error messages that a schema-based library gives you for free are not part of this model, because the model has no place to put them.
The second is that the function-per-type approach does not compose. A field that is both optional and must look like an email is a condition and a type, and expressing that with this library means writing the condition yourself around the validator call, which is the code the library was trying to remove.
The third is cost. Each call is a function call, so validating a list of a thousand records is a thousand calls with a thousand regex evaluations underneath, where a schema-driven validator can compile a structure once and validate many records against it. For a one-off check that does not matter at all. For a bulk import it does, and the project gives you no way to amortise the pattern.
The fourth is that the 0.x version line means you should not expect the API to be stable across upgrades, which is a real ongoing cost for a library you depend on in many places.
None of these are flaws so much as the price of the model. The alternative, a schema-based library, buys you error messages, composition and compiled validation at the cost of a dependency, a declarative document, and the obligation to keep that document in step with your data. For a single field in a script, the trade is obviously good. For a service with a shared contract, it is not.
Editorial conclusion
validators fits a Python developer who needs to check a value here and there, in a script, a data pipeline, a webhook handler or a form field, and who does not want to introduce a schema library to do it. It does not fit a service that needs a declarative contract shared between a backend and a frontend, and it does not fit a codebase that already standardised on a validation library with model integration. Verify first that the validator you need exists in the version you install, since the API surface has moved across major versions, that the package's Python floor matches your runtime, because it requires Python 3.11 or newer, and that the optional extra you need is installed separately, since the crypto Ethereum address validators only work with the `crypto-eth-addresses` extra and `eth-hash[pycryptodome]`.
Frequently asked questions
How do I install and use the validators package?
Install it with `pip install validators`, then import it and call the validator for the value you have. For an email address, `validators.email('[email protected]')` returns `True`. The package has no runtime dependencies and requires Python 3.11 or newer.
Does the validators package need a schema or a form?
No, and that is the design goal stated in the README. Every validator in the library is a function that takes a value and returns a boolean, so validating a single value needs no schema definition and no form.
How do I validate Ethereum addresses with the validators package?
Those validators are optional and live behind the `crypto-eth-addresses` extra, which pulls in `eth-hash[pycryptodome]>=0.7.0`. Everything else in the library works with the empty dependency list and needs no extra installed.
Is the validators package stable across versions?
It is on a 0.x version line, with 0.34.0, 0.35.0 and 0.36.0 released over roughly two years, so a minor bump can change the API. The project publishes a changelog at `CHANGES.md`, and the repository classifies itself as `Development Status :: 4 - Beta`.
Where is the documentation for the validators package published?
On GitHub Pages through the maintainer's fork at nandgator.github.io/pyvalidators, because the maintainer has only contributor access to the main repository and cannot enable Pages for it. The original documentation URL is commented out in the README with a note that it will be restored once properly versioned docs are ready.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/python-validators-validators)