Skip to main content

A Zod-like validation library for Python

Project description

zon - Zod-like validator library for Python

Coverage Status

Want to have the validation power of zod but with the ease-of-use of Python?

Enter zon.

zon is a Python library that aims to provide a simple, easy-to-use API for validating data, similar to zod's own API'. In fact, the whole library and its name were inspired by zod: Zod + Python = Zon !!!.

Why

While doing a project for college, we were using both Python and JS/TS in the code-base. We were also using a JSON-Schema document to define a data schema for the data that we would be consuming.

There exists tooling that allows for one schema to generate TS types and from that Zod validator code, but there was nothing of the sort for Python. What's more, none of the validation libraries I found for Python had an API that was as easy to use as Zod's. So, I set out to build it.

Installation

Pip

This package is available on PyPI, so you can install it with pip:

pip install zon

Source

Alternatively, you can clone this repository and install it from source:

git clone https://github.com/Naapperas/zon
cd zon
pip install .

Usage

In its essence, zon behaves much like zod. If you have used zod, you will feel right at home with zon.

[!NOTE]
There are some differences in the public API between zon and zod. Those mostly stem from the fact that Python does not have type inference like Typescript has. There are other slight deviations between zon and zod.

General

Validate

To validate against a schema, use validator.validate()

validator = zon.string()
message = validator.validate("Hello World!") # returns 'Hello World!'

Alternatively, you may use validator.safe_validate(). safe_validate will tell you whether the validation was successful, without throwing an error. Depending on the needs of your project, you can do this to handle exceptions more elegantly.

validator = zon.string()
success, message = validator.safe_validate("Hello World!") # returns (True, 'Hello World!')

Chaining

Most validators can be chained together, just like zod:

validator = zon.string().min(5).max(10).email()

This is equivalent to:

validator = zon.string()
validator = validator.min(5)
validator = validator.max(10)
validator = validator.email()

Basic types

zon features most of zod's basic types:

zon.string()
zon.number()
zon.boolean()
zon.literal()
zon.enum()

zon.record()
zon.element_list()
zon.element_tuple()

zon.union()
zon.intersection()
zon.optional()

zon.never()
zon.anything()

Numbers

validator = zon.number()

validator.gt(5)
validator.gte(5) # alias for .min(5)
validator.lt(5)
validator.lte(5) # alias for .max(5)

validator.int()
validator.float()

validator.positive()
validator.negative()
validator.non_negative()
validator.non_positive()

validator.multiple_of(5) # alias for .step(5)

validator.finite()

Strings

For strings, there are also some extra methods:

validator = zon.string()

validator.min(5)
validator.max(10)
validator.length(5)

validator.email()
validator.url()
validator.uuid()
validator.regex(r"^\d{3}-\d{3}-\d{4}$")
validator.includes("needle")
validator.starts_with("prefix")
validator.ends_with("suffix")
validator.datetime()
validator.ip()

# transformers
validator.trim()
validator.to_lower_case()
validator.to_upper_case()

Datetime

zon accepts the same options as zod for datetime string validation:

validator.datetime({"precision": 3})
validator.datetime({"local": True})
validator.datetime({"offset": True})

[!NOTE] zod uses regex-based validation for datetimes, which must be valid ISO 8601 strings. However, due to an issue with most JavaScript engines' datetime validation, offsets cannot specify only hours, and zod reflects this in their API. While zon could reflect zod's API in this matter, it is best to not constrain users to the problems of another platform, making this one of the aspects where zon deviates from zod.

IP addresses

zon accepts the same options as zod for ip address string validation:

validator.ip({"version": "v4"})
validator.ip({"version": "v6"})

List

Lists are defined by calling the zon.element_list() method, passing as an argument a Zon instance. All elements in this list must be of the same type.

zon.element_list(zon.string())

Like strings, lists also have some extra methods that check the length of the list:

validator = zon.element_list(...)

validator.min(5)
validator.max(10)
validator.length(5)

There is also a method for validating that the list has at least one element, nonempty:

validator.nonempty()

You can get the type of the list's elements by accessing the element property:

zon.element_list(zon.string()).element is ZonString

Union

zon supports unions of types, which are defined by calling the zon.union() method, passing as arguments the Zon instances that are part of the union.

zod.union([zon.string(), zon.number()])

To access the various options, access the options property:

zod.union([zon.string(), zon.number()]).options = [ZonString, ZonNumber]

Record

zon supports validating objects according to a specified schema, using the zon.schema() method. This method takes as an argument a dictionary, where the keys are the keys of the object to be validated and the values are the Zon instances that define the type of each key.

validator = zon.record({
    "name": zon.string(),
    "age": zon.number(),
    "isAwesome": zon.boolean(),
    "friends": zon.element_list(zon.string()),
    "address": zon.record({
        "street": zon.string(),
        "city": zon.string(),
        "country": zon.string(),
    }),
})

Useful methods for ZonRecord instances:

validator.extend({...})
validator.merge(otherZon) == validator.extend(otherZon.shape)
validator.pick({"name": True}) == ZonRecord({"name": ...})
validator.omit({"name": True}) == ZonRecord({"<key that is different from name>": ..., ...})
validator.partial() # makes all attributes optional, shallowly
validator.partial({"name": True}) # makes only "name" optional (in this example)
validator.deepPartial() # makes all attributes optional, recursively
validator.required() # makes all attributes required, shallowly

You can access the shape of objects validated by any ZonRecord instance by accessing the shape property:

shape = validator.shape

If you want to validate only the keys of the shape, use the keyof method:

validator.keyof() == ZonEnum(["name", "age", "isAwesome", "friends", "address"])

Unknown Keys

As zod, zon normally strips unknown keys from records. This, however, can be configured:

validator.strict() # presence of unknown keys makes validation fail
validator.passthrough() # add unknown keys to the resulting record
validator.strip() # the default behavior, strip unknown keys from the resulting record

Catchall

In case you want to validate unknown keys, you can use the catchall method to specify the validator that is used:

validator.catchall(zon.string()) # unknown keys *must* be associated with string values

Tuple

zon supports tuples out-of-the-box, which are fixed-size containers whose elements might not have the same type:

validator = zon.tuple([...])

If you want to access the items of the tuples, use the items property:

validator.items = [...]

Variadic items can be validated using the rest method:

validator.rest(zon.string()) # after the defined items, everything must be a string

Enums

zon supports enumerations, which allow validating that any given data is one of many values:

validator = zon.enum([...])

If you want to access the possible valid values, use the enum property:

validator.enum = [...]

Examples

Example usage of zon can be found in the examples directory.

Documentation

Documentation is still not available, but it will be soon.

Tests

Tests can be found in the tests folder. zon uses pytest for unit testing.

To run the tests, simply run:

pytest test

Coverage can be found on Coveralls.

Contributing

Contribution guidelines can be found in CONTRIBUTING

Past and current contributors can be found in CONTRIBUTORS

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zon-3.0.1.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zon-3.0.1-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

Details for the file zon-3.0.1.tar.gz.

File metadata

  • Download URL: zon-3.0.1.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.13.11

File hashes

Hashes for zon-3.0.1.tar.gz
Algorithm Hash digest
SHA256 3ed297f6177720e9d28c680200d3715dbb2bf91d281caf0f8987d58a4b151704
MD5 1f104cad41c8203490585ab5821d48c1
BLAKE2b-256 7ee1cf2e3561f807a4f3709a886c46651ac509c0e881a6b51392169ab8fff604

See more details on using hashes here.

File details

Details for the file zon-3.0.1-py3-none-any.whl.

File metadata

  • Download URL: zon-3.0.1-py3-none-any.whl
  • Upload date:
  • Size: 14.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.13.11

File hashes

Hashes for zon-3.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6c927bc58209869b8e0eb97fa0a07453d91c6e827a5bdc5c661d383b1bd4ddef
MD5 dfd5e93da332f2d18393320abda7ac45
BLAKE2b-256 70c26c6623260bff6659392e894dd64d2cff86b079cee7170161501c14e736ec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page