anvilistas / anvil-orm

A library for [Anvil Applications](https://anvil.works) that provides ORM-like functionality.
https://anvil-model.readthedocs.io/en/latest/
MIT License
12 stars 4 forks source link

Add validation #12

Open meatballs opened 3 years ago

meatballs commented 3 years ago

Similar to how this is done by attrs and/or pydantic.

s-cork commented 3 years ago

I really need to push meredydd to ok the class annotations in skulpt!

meatballs commented 3 years ago

API for when validation occurs...

How about on_validation and on_validation attributes which can be set to callable?

s-cork commented 3 years ago

good question I don't know enough about the library so ignore any code that is totally ignorant

# from the docs
@model_type
class Book:
    title = Attribute()
    published_on = Attribute()
    author = Relationship(cls="Author")

fluent_python = Book.search(title="Fluent Python")[0]
fluent_python.title = "Fluent Python (Clear, Concise, and Effective Programming)"
fluent_python.save()

Could be - almost straight form the pydantic code

@model_type
class Book:
    title = Attribute()
    published_on = Attribute()
    author = Relationship(cls="Author")

    @validate('title')
    def author_must_contain_space(v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

And the model could have some sort of validate method

fluent_python = Book.search(title="Fluent Python")[0]
fluent_python.title = "Fluent Python (Clear, Concise, and Effective Programming)"
try:
    fluent_python.validate() # loops over all the validators
    fluent_python.validate(key='title') # loops over all validators registered for title
except anvil_orm.ValidationError as e:
    print(e)
else:
    fluent_python.save()

The @validate would just append a method to a default_dict(list) whose key is the attribute

calling .validate() would catch ValueErrors, AssertionErrors and TypeErrors and eventually raise a ValidationError after all validators were checked in a similar way to pydantic.

It could also potentially be some sort of context manager

with anvil_orm.validate(fluent_python) as v:
    if v is not None:
        pass
    else:
        print(v.errors) # there are errors
        print(v.errors['title'])

just jamming so ignore what doesn't make sense.

meatballs commented 3 years ago

My only reservation about raising errors is what then has to happen on a bound component using write back. Catching those errors becomes tricky.

Instead, I was wondering about either triggering an event or publishing a message. I opted for a generic callback so that the user can do whatever they like.

I like the decorators, though!

s-cork commented 3 years ago

For a writeback in my head it would be something like

def on_validate(self, **event_args):
    try:
        self.item.validate()
   except ValidationError as e:
       self.author_error.text = e.errors['author']

And then you just have event_bindings for methods like lost_focus set to on_validate.

But again just throwing pasta

meatballs commented 3 years ago

Having some time to have a crack at this properly would be nice!