Zod: client and server side validation

We’ve just released a new module in anvil-labs called zod
It’s very much inspired by the popular TypeScript equivalent zod.dev

It’s not tied to the UI so can be integrated with existing UI solutions
And since it’s not tied to the UI you can use the same validation on the server and client!

Documentation


Quick Start:

>>> from anvil_labs import zod as z
>>> email_schema = z.string().strip().email()
>>> email_schema.parse("foo@bar.com")
# "foo@bar.com"
>>> email_schema.parse("  foo@bar.com ")
# "foo@bar.com"
>>> email_schema.parse("foo@")
# ZodError: "Invalid email" on line 1
>>> result = email_schema.safe_parse("foo@")
>>> result
# ParseResult(success=False, data=None, error=ZodError('Invalid email'))
>>> result.success
# False

Object schema

>>> from anvil_labs import zod as z
>>> user_schema = z.typed_dict({"name": z.string().min(3).max(50).transform(str.capitalize)})
>>> user_schema.parse({ "name": None })
# ZodError: Expected string, received none
>>> user_schema.parse({ "name": "m" })
# ZodError: String must contain at least 3 character(s)
>>> user_schema.parse({ "name": "meredydd" })
# { "name": "Meredydd" }


Example App

  • I’ve tried to showcase some of the features in the above clone link
    (but there are plenty more)
  • You’ll see that the same schema is used on both the client and the server :raised_hands:
    (No need for different validation logic)

Live App: https://zod-validation.anvil.app


Other considerations
Since there’s no documentation yet the autocompletions should help somewhat :crossed_fingers:
But for general questions on how to use it there’s always the anvil-labs discussion page

anvilistas/anvil-labs · Discussions · GitHub

To use it now, add anvil-labs as a third party dependency with the code:
5YU7BBT6T5O7ZNOX

Enjoy :smile:


Related to:

Yet another validator and formatter

Form Validation with Anvil


edit:
Original post used z.object instead of z.typed_dict

(Partial) documentation now available:
https://anvil-labs.readthedocs.io/en/latest/guides/modules/zod.html

12 Likes

This is proof positive that a well-designed and -documented schema pays for itself in spades.

I can easily see a generalized schema definition document being distilled into schemas for REST, for Zod, for a user-interface builder, etc.

On skimming Zod’s documentation, it appears that Zod should be able to produce reusable parts of schemas. E.g.,

from anvil_labs import zod as z

human_age_years_t = z.integer().nonnegative().max(127)

joint_ages_t = z.object({
    'primary': human_age_years_t,
    'spouse': human_age_years_t,
})

Which would make sense. After all, if your app has dozens of “age” fields, why spell out the type every time, when, by giving it a name, you not only avoid duplication, but you actually document what the type is used for? What it means in the real world?

On the other hand, I’ve never seen any Zod examples like the above. Which suggests that you must spell it out every time.

Must we?

3 Likes

Yes that will absolutely work. A schema is just a set of instructions for converting input to output.

Worth noting that the schema won’t mutate the data. i.e. When you give the object schema a dictionary to parse it will return a new dictionary (assuming the parse was successful).

3 Likes

In that case, I would greatly prefer writing
def fn(x: human_age_years_t): ...
to
def fn(x: int): ...

2 Likes

I’m no expert on TypeScript, JavaScript, or Zod, so please bear with me. What’s the distinction between record, object, and mapping? I assume that each has a different set of use cases, else why offer more than one.

i guess ignore mapping for now. We’re just working on the api to be more pythonic.

as it currently stands:

z.object

z.object is like Python’s typing.TypedDict

typing — Support for type hints — Python 3.12.3 documentation

a dictionary, with specific keys and each key has a specific type.

We will likely change the name to z.typed_dict (Keeping z.object around for backwards compatibility)

z.record

This is like Python’s typing.Mapping a dictionary with a key type and value type. Think a dictionary used as a cache, where the keys are all integers, say, and the values are all the same type.

typing — Support for type hints — Python 3.12.3 documentation

We will change this to z.mapping to match Python typing and keep z.record around for backward compatibility.


I’ll let update this thread when those names have been added.

If interested further see this PR where we’re discussing the naming

zod: try out pythonic names by s-cork · Pull Request #121 · anvilistas/anvil-labs · GitHub

and

Zod Naming · anvilistas/anvil-labs · Discussion #120 · GitHub


edit:

And just like that we’ve made those changes and added partial documentation - I’ve adjusted the first post and the clone link to use the new api:

https://anvil-labs.readthedocs.io/en/latest/guides/modules/zod.html

1 Like

I’m a very simple boy, if indeed a 55 year old balding, fattening male can be described as a boy based entirely on his sense of humour.

But I don’t understand what zod would be used for. Could someone please explain, preferably as if you were talking to a 10 year old idiot?

Is it a best practise thing, or is it one of those “if you have to ask it’s not for you” things?

2 Likes

Off Topic
@david.wylie
You. Are. Hilarious.

Thanks for the giggles.

2 Likes

It’s a validation tool. In the example @stucork gave at the top of this thread, he has a string which should be a valid email address.

He first defines the zod schema for a valid email address:

from anvil_labs import zod as z
email_schema = z.string().strip().email()

and then uses that to check three strings:

email_schema.parse("foo@bar.com")

which returns foo@bar.com because it passes the validation

email_schema.parse("   foo@bar.com   ")

which also returns foo@bar.com because the scheme strips the whitespace before running the check, and,

email_schema.parse("foo@")

which raises an error because the string isn’t a valid email address.

There are lots of other ways to create a schema described in the docs. This one is a simple string validation schema.

Also, because it’s not tied to anvil components, it can be used anywhere - both client and server side.

4 Likes

For example, to check inputs from an API. It’s impractical to check them manually, so it has to be done in code, to prevent the dreaded “garbage-in, garbage-out” syndrome.

Yes, we could all write our own sanity-check functions to do the same job. Zod, however, provides a cohesive framework, which helps simplify the code, make it more readable, and make it easier to combine validators to build bigger and better ones.

The scalability is especially important when, as in my case, I have data files with literally hundreds of distinct, highly-structured inputs…

What I especially like is that Zod follows solid design principles, which makes the whole thing much easier to think about – and to design for.

I like to use the “domain” concept from data design. A Domain is literally a mathematical set, a set of values. Since programs are mathematical procedures, in reasoning about program behavior, it helps to be able to pin down the Domain of each input. The more precisely we do that, the better we can reason.

Zod makes Domains real, in our programs. Real objects that we can name, query, reuse, build with, and make them do work for us.

1 Like

Instead of the original Zod’s TypeScriptyness. Yes!

Took a few hours out to study JavaScript and TypeScript, and the TypeScript version now makes a lot more sense. Many Anvil users, of course, came to Anvil to get away from all of that!

A Pythonic version is absolutely the way to go here. I look forward to seeing how it adapts to Python. I’ll probably join the discussion. There’s a good, mathematically-solid foundation for this work, easy to reason about. Keeping that simplicity is, I hope, a design/implementation goal.

2 Likes

OK, rereading it I think I get it now. Different day, different eyes.