Pydantic with Model Classes

Maybe this is totally obvious but, I thought I would show an update to the Model Class demo that uses Pydantic for data validation.

I’ll run through a simple case to update a user’s name here but I’ll link the app clone below.

So, starting in client/Models.py

import anvil.server

class User(app_tables.users.Row, client_updatable=True, buffered=True): 
    ...

As, you can see, so far things are very exciting. I’ll finish defining the model on the server

server/ServerModels.py

from . import Models

from typing import Annotated
from pydantic import BaseModel, StringConstraints


class UserModel(BaseModel):
    name: Annotated[str, StringConstraints(min_length=3, max_length=50)]


class User(Models.User):
    def _do_update(self, updates, from_client):
        print("Saving User Info")
        user = anvil.users.get_user()
        if from_client and user is None:
            raise PermissionError("You must be logged in to delete tasks.")

        if from_client and self != user:
            raise PermissionError(
                "You do not have permission to update other user's info."
            )

        user_info = UserModel(**updates)
        super()._do_update(user_info.model_dump(), from_client)

Here, we define our pydantic data model for updating the user’s name. We are placing a length constraint on the name of between 3 and 50 characters.

Now, we finish off defining the update method for the User’s Model Class. Since we have everything defined on the server we are free to use pydantic for validation. The only hiccup was I need to explicitly call the model_dump(), otherwise, I was getting type errors.

One feature of pydantic that was also really helpful was using the .model_dump(exclude_unset=True). With this you can define optional parameters for a row and send incomplete updates without issues. So, for the Todo item in the demo the pydantic model is:

class TodoModel(BaseModel, arbitrary_types_allowed=True):
    title: Annotated[str, StringConstraints(min_length=3, max_length=50)] = None
    description: Annotated[str, StringConstraints(max_length=255)] = None
    due: Optional[date] = None
    completed: Optional[bool] = None
    owner: app_tables.users.Row | None

Now, you can send partial updates like {"completed": True} without issues.

Here is the updated Models Classes Todo Demo:
Todo Demo

6 Likes