Course / The model / Lesson 13

Lesson 13 · The model

The model hosts external metadata

Read attributes of the model from application code and use them to drive an application task.

about 3 minutes · screenplay estimate6 scenesPrerequisites: lesson 12

Development course · Reading edition · API compatibility

Scene 01

Where the attributes are

The model built by AsqueelDb is a tree of objects. db dot table returns the table object. Its column method returns the model object of a column. Every column object has a dictionary called attributes. It holds the arguments of the declaration, after composition and validation. The table object also has model, the model object of the table. model dot columns maps the physical column names to the same column objects, in declaration order. Reading these objects opens no connection.

Example & screen notes

Slide title "Reading the model". Code, on the database built from ExportInvoicing of lesson 12:

db = AsqueelDb(ExportInvoicing)
contact = db.table('invc.contact')

contact.column('email').attributes['export_mode']        # 'masked'
contact.model.columns['email'] is contact.column('email')  # True
list(contact.model.columns)
# ['id', 'customer_id', 'full_name', 'email', 'phone', 'internal_note']

Scene 02

Defaults in the model

The phone column declares neither export argument. Its attributes still contain export_mode equal to plain. When the model is built, a signature default that is not None is written into the attributes. So the application does not repeat the default. export_name has the default None. A None default is not written. Reading it with square brackets would raise KeyError. Use get, with the fallback the application needs.

Example & screen notes

Slide title "What attributes contains". Code:

sorted(contact.column('phone').attributes)
# ['export_mode', 'indexed', 'name', 'notnull', 'unique']
contact.column('phone').attributes['export_mode']        # 'plain'
contact.column('full_name').attributes['export_name']    # 'name'
contact.column('phone').attributes.get('export_name')    # None

Two bullets:

  • A default other than None is written into the model
  • An argument with default None, left out, is absent: read it with .get()

Scene 03

An export driven by the model

This function is application code. asqueel contains nothing like it. It walks the physical columns of a table. It skips the columns marked omit, so internal_note is never queried. It renames the columns that have an export_name. It builds the column list of the query from the model. After the fetch, it masks the columns marked masked. The ids are padded with spaces, because the key columns are character twenty-two. The same function works on any table declared with the export grammar. Changing what is exported means changing the recipe, not the function.

Example & screen notes

Slide title "The application reads, asqueel stores". Code:

def mask(value):
    return None if value is None else value[0] + '***'


def export_rows(db, table_name):
    table = db.table(table_name)
    plan = []
    for name, column in table.model.columns.items():
        mode = column.attributes['export_mode']
        if mode != 'omit':
            plan.append((name, column.attributes.get('export_name', name), mode))
    columns = ', '.join('$' + name for name, _, _ in plan)
    for row in table.query(columns=columns, order_by='$full_name').fetch():
        yield {label: mask(row[name]) if mode == 'masked' else row[name]
               for name, label, mode in plan}

Output of for row in export_rows(db, 'invc.contact'): print(row):

{'id': 'k1                    ', 'customer': 'c1                    ', 'name': 'Ann Lee', 'email': 'a***', 'phone': '555-1'}
{'id': 'k2                    ', 'customer': 'c2                    ', 'name': 'Bo Ng', 'email': 'b***', 'phone': None}

Scene 04

Division of work

asqueel and the application share the work. asqueel accepts only the arguments that the grammar declares. It checks their types and their allowed values. It writes the defaults into the model, after all layers are composed. The application reads the attributes and gives them a meaning. asqueel never executes them. The same model can carry several such vocabularies, one per grammar. An undeclared argument still raises, so a typing mistake in the metadata stops the build.

Example & screen notes

Slide title "Who does what". Table:

asqueel The application
accepts only declared arguments decides what an argument means
checks types and Literal values reads attributes at run time
writes non-None defaults into the model acts: export, audit, documentation, ...
composes layers before building the model —

Scene 05

Standard attributes are read the same way

The standard grammar already has descriptive arguments, such as name_long and caption_field. They work the same way. The database ignores them, and the model keeps them. name_long is also a property. When a column has no name_long, the property returns the short name, or else the column name. That is why the email column returns email. An application reads these attributes exactly like the export attributes of lesson 12.

Example & screen notes

Slide title "Standard semantic attributes". Code:

db.table('invc.invoice').model.name_long                        # 'Invoice'
db.table('invc.invoice').model.attributes['caption_field']      # 'inv_number'
contact.model.name_long                                         # 'Contact'
contact.column('email').name_long                               # 'email'

Scene 06

Recap

The model objects expose every declared attribute, standard or added by a grammar. Defaults other than None are already in the model. asqueel stores and validates, and the application reads and acts. The next part of the course, starting with lesson 14, covers queries.

Example & screen notes

Three bullets:

  • db.table(...).column(name).attributes and db.table(...).model.columns expose the model
  • Non-None defaults are in the model; None defaults are absent
  • asqueel stores and validates; the application reads and acts