Is there anything like a Model SimpleObjectColumn?

What I’m trying to do:
I hope to extend the Model Row concept to include structure-valued columns, so that it’s objects (classes) all the way down.

This could be really helpful for complex objects such as Insurance Products, which are composed of many subobjects.

Hi @p.colbert,

Model classes extend the row object for a particular Data Table. So if you have a Data Table that has a Simple Object column, you can then add some attributes to the model class that do something with the data in that column.

Does that help, or am I misunderstanding what you are trying to do?

It’s this part:

Wrapping a row in a class-based object does not automatically do the same for the values in its columns. That would be overkill for most scalar-valued columns (number, date, datetime, str, …). However, it’s already been done for link columns. But not for simpleObject columns. These remain raw Python data, even when they follow a strict structure.

If these have a consistent structure from row to row, then it may make sense to treat them not as plain lists or dicts, but as instances of developer-defined classes – exactly as we are encouraged to do with their containing Rows.

In short, why stop at the Row level? I have complex data in some of my simpleObjects. Repeating structures within repeating structures. In some, I have dicts and lists 8 or 9 levels deep! So where it makes sense, why should I not apply my classes all the way down?

Currently, I can ask for a Row, and Anvil’s runtime will deliver an Object, of a class I define. It often makes sense to do that with a simpleObject column, too. Objects within objects, as deep as they may go. But how to define such an arrangement, in the first place, in a way that works with the Model Row pattern, or even works with an unadorned Row, is not clear.

You can create a class that implements the descriptor protocol and define your linked class instantiation in there.

I’ve done this for a client and it works fine but the code is not mine to share here.

That makes good sense. And I’ve considered it for single-level simpleObject values.

I’ll have to consider what it might look like for multiple levels of classes, where it may be harder to keep the row’s single simpleObject value and each corresponding deeply-nested class instance in sync.

In practice, there’s a further complication. simpleObject columns are often used to accommodate structures that change over time. It’s easier to accommodate evolution in these columns, than elsewhere in the row, where it’s

  1. a manual change, that
  2. immediately affects all users of that table.

Thus, version 1.0 of an App may store values in one arrangement, while Version 1.1 stores values in a slightly different arrangement. Format version numbers are often stored in the simpleObject, so that code can tell when a format-upgrade should be applied.

Handling this sort of situation, one might need an object factory, that can choose the correct class for the value, based on the format-number.

Edit: I’ve just checked. The Python type() of a simpleObject value is just a native Python type: None, str, dict, list, … This suggests that a subclass of dict might work, in the same way that a subclass of anvil.tables.v2._row.Row is used as a Model Row.

When you say “classes all the way down,” it reminds me of how I’ve built some of my apps, for example, one that manages trucks, versions, crates, and so on, all structured through nested classes. That makes me wonder: is using Model classes really worth the effort here?

Maybe I’m missing something in your question, or I don’t fully grasp the benefits of Model classes. But if I understand both correctly, I think sticking with good old manual serialize/deserialize might be the better route.

I haven’t used Model classes myself (I haven’t created any new apps since they were introduced), but my impression is they handle a lot of functionality out of the box. My concern is that, as your use case drifts away from what’s included in those built-in features, you may find yourself putting in more effort just to keep using them, when the traditional approach would be simpler and faster.

I use simple object columns for all data that doesn’t need to be searchable. Every table has a few searchable fields and one or two simple object columns holding the main data. For instance, a simplified Trucks table might have three columns: two searchable ones, project_number and truck_number, and one simple object column, versions. That column contains a list of versions, each a dictionary with the full version content. When users make changes and save, a new version gets appended. This way I can track who changed what and when, for any truck. I’ve seen trucks with 0.5MB of data in that versions column.

My Truck class manages a list of Version instances, each Version handles its metadata and content, the content holds lists of Crate instances, each Crate contains lists of Panel or Misc instances. Classes all the way down.

Each class knows how to serialize and deserialize itself into and from a simple object. It gets more involved in some cases, for example, if crate number X is unchanged between two versions, instead of repeating it, the system stores a reference to the crate in the earlier version. So the serializer/deserializer takes care of compressing and expanding the data as needed.

I really don’t see how Model classes would help in a scenario like this.

Where, in this example, would a Model class make things easier?

I was just playing around with Model Classes and found you can modify the __getitem__ magic method. If you do that, you can make the simple object columns return your custom classes, and if you use __setitem__, for changing the values.

def __getitem__(self, key):
        if key == "simple-object-column":
            return """Custom Class for Simple Objects"""
            

        return super().__getitem__(key)