Closed s-cork closed 1 year ago
Looking very nice!
@meatballs - I'm pretty close on this one and I think it's turning into something quite nice
A typical form might look like this
# schemas.py
from anvil_labs import zod as z
new_user_schema = z.object(
{
"email": z.string().strip().email(),
"name": z.string().min(5),
"age": z.integer().lt(100).ge(18),
}
)
# Form1
from ..schemas import new_user_schema
class Form1(Form1Template):
def submit_button_click(self, **event_args):
try:
new_user = new_user_schema.parse(
{"email": self.email_input.text, "name": self.name_input.text, "age": self.age_input.text}
)
anvil.server.call("add_user", new_user)
except z.ParseError as e:
print(e)
self.email_err_lbl.text = "; ".join(e.errors("email"))
self.name_err_lbl.text = "; ".joint(e.errors("name"))
self.age_err_lbl.text = "; ".join(e.errors("age"))
else:
... # clear the form
# Server1
from . schemas import new_user_schema
@anvil.server.callable
def add_new_user(user_def):
user_def = new_user_schema.parse(user_def) # might fail here
...
Let me know what you think and what questions you have I like that it's not tied to the UI:
I wonder how you might use it for classes because there's currently no support for attribute schemas
I figured you might do something like:
def validate(schema):
def wrapper(cls):
def inner(*args, **kws):
self = cls(*args, **kws)
schema.parse(self.__dict__)
return self
return inner
return wrapper
schema = z.object({'foo': z.enum([1, 2, 3])})
@validate(schema)
class Foo:
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
Foo(foo=1, bar=None) # fine
Foo(foo='a', bar=None) # ZodError: Expected 1 | 2 | 3, received string at ['foo']
but maybe there's a better way
I've been keeping an eye on this and it looks excellent. I also particularly like how it's independent of the UI.
My only question is how do I add a new test? Say, for example, I had a regex that a string needs to match. How would do that?
Two options The string specific version is just
z.string().regex(re.compile(...), "expected a ...")
But there's also the generic
z.string().refine(str.isalpha, "expected a ...")
Refine takes a callable that should return True/False. Plus an optional message.
Opinions on msg
vs message
?
zod js uses message
but I've used msg
.
I'm tempted to go with message
.
Aha! It was refine that I'd missed! Nice.
I'd go with message. Clearer for non native English speakers.
minus docs I think this is ready to have a look at (I might do those another time) I added tests adjusted from zod js library
Tried to use it and one use case for form validation is a bit awkward: See this discussion:
https://github.com/colinhacks/zod/issues/310#issuecomment-794533682
.optional()
is really supposed to be used in an zod object to say I don't mind if that key, value pair is missing
(since python doesn't have a concept of undefined
we use MISSING
to represent this)
But when used on a solitary z.string()
schema, optional is really quite meaningless
like
schema = z.string().email().optional()
try:
schema.parse(self.input_1.text)
except z.ParseError as e:
print(e) # will fail for empty string
Currently we'd have to do something like:
schema = z.string().email().or_(z.literal(""))
# or
schema = z.union([z.litera(""), z.string().email()])
I can see a few other ways around this for us
we can add kwargs to optional()
, like allow_empty=True
But i'm not sure about this.
we could do what this library does https://www.remix-validated-form.io/zod-form-data/api-reference#text
schema = z.text(z.string().email().optional())
They also have an api for working with text that should be numeric
schema = z.numeric(z.number().optional())
answers on a post card...
Ok - I think we can worry about optional in the documentation
Here's an example of it working quite nicely https://anvil.works/build#clone:BXEGXJKXFVCEXEB4=4W3Q7L2PIH3SZARF4K3KW5BI|XCOXK7RMKUZ4GRXD=PTRRADDWEUMQQ2T3HGKA6NDN|C6ZZPAPN4YYF5NVJ= (it'll copy my local version of anvil-labs with the fixes from #112) I made a vanilla and an atomic version just for fun atomic is so satisfying once it's done - took me a while to remember how it all fits together though!
I also played around with stefano's validation library, and adding a method to the validation class with_schema
can basically be used in place of all the helper methods.
WIP: based on source code from https://zod.dev/
I'm quite excited about this one so thought I'd draft PR the proof of concept
TODO: