Model Classes, Tables and Rows

Here is the model we’re using in the experimental app that @thomas.campbell1 presented at the user group:

from anvil.tables import app_tables
from anvil_reactive.main import reactive_class

from .helpers import LinkedClass, WithLinks, WithView


@reactive_class
class Book(
    WithView, app_tables.book.Row, buffered=True, attrs=True, client_writable=True
):
    table = app_tables.book
    key = "isbn_13"


@reactive_class
class Author(
    WithLinks,
    WithView,
    app_tables.author.Row,
    buffered=True,
    attrs=True,
    client_writable=True,
):
    table = app_tables.author
    key = "name"
    links = [LinkedClass(Book, column="author", on_delete="restrict")]

    def __str__(self):
        return self.name

That’s already starting to look a little unwieldy with just the two mixins that we’ve created thus far and it’s highly likely we’ll create more.

I’d like instead, to be able to create my own base class with all the various options set how I want in one place. Something like:

from anvil.tables import app_tables
from anvil_reactive.main import reactive_class

from .helpers import LinkedClass, WithLinks, WithView


@reactive_class
class MyBaseModel(
    WithLinks,
    WithView,
    app_tables.Row,  # <- This is where things might get tricky, I suspect
    buffered=True,
    attrs=True,
    client_writable=True
):
    pass


class Book(MyBaseModel):
    table = app_tables.book
    key = "isbn_13"


class Author(MyBaseModel):
    table = app_tables.author
    key = "name"
    links = [LinkedClass(Book, column="author", on_delete="restrict")]

    def __str__(self):
        return self.name

Hmm - this could be a job for a metaclass


class MetaRow(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        # adjust the bases here
        return super().__new__(
            cls, name, bases, namespace, attrs=True, client_writable=True, buffered=True
        )

class Author(app_tables.author.Row, metaclass=MetaRow):
    table = app_tables.author
    key = "name"
    links = [LinkedClass(Book, column="author", on_delete="restrict")]

    def __str__(self):
        return self.name


(and looks like we’ll need to fix the linting issue with setting a metaclass)

Ha! I’d thought the same thing but metaclasses are one of things that trigger “if you think this is the solution, there’s a very good chance you’ve missed something simpler” in my head.

1 Like

For what it’s worth, I share that intuition about metaclasses!

It seems like the issue here is specifically the config options (buffered=True, attrs=True, client_writable=True), as the rest can be combined with inheritance. Just spitballing, no promises, but would something like this appeal?

class MyBaseModel(WithLinks, WithView):
    _anvil_model_options_ = {"buffered":True, "attrs":True, "client_writable":True}

class Book(MyBaseModel, app_tables.books.Row):
    # ...
1 Like

That would definitely solve 90% of the issue!

The remaining 10% is the irritation of having to specify the table twice but that’s a much bigger change, I suspect.

Definitely a “sit on your hands, go for a walk, don’t touch the damned keyboard” trigger!

Tell me more about why you’d still want to do this?

1 Like

We have a WithView mixin because we’re using client readable views to populate “index” forms with the lists of existing instances.

To do that, we need to know the table within the model class.

Here’s the code:

2 Likes