Yams is a pythonic way to describe an entity-relationship model. It is used at the core of the CubicWeb semantic web framework in order to automate lots of things, including the generation and validation of forms. Although we have been using the MVC design pattern to write user interfaces with Qt and Gtk before we started CubicWeb, we never got to reuse Yams. I am on my way to fix this.

Here is the simplest possible example that generates a user interface (using dialog and python-dialog) to input data described by a Yams data model.

First, let's write a function that builds the data model:

def mk_datamodel():
    from yams.buildobjs import EntityType, RelationDefinition, Int, String
    from yams.reader import build_schema_from_namespace

    class Question(EntityType):
        number = Int()
        text = String()

    class Form(EntityType):
        title = String()

    class in_form(RelationDefinition):
        subject = 'Question'
        object = 'Form'
        cardinality = '*1'

    return build_schema_from_namespace(vars().items())

Here is what you get using graphviz or xdot to display the schema of that data model with:

import os
from yams import schema2dot

datamodel = mk_datamodel()
schema2dot.schema2dot(datamodel, '/tmp/toto.dot')
os.system('xdot /tmp/toto.dot')

To make a step in the direction of genericity, let's add a class that abstracts the dialog API:

class InterfaceDialog:
    """Dialog-based Interface"""
    def __init__(self, dlg):
        self.dlg = dlg

    def input_list(self, invite, options) :
        assert len(options) != 0, str(invite)
        choice = self.dlg.radiolist(invite, list=options, selected=1)
        if choice is not None:
            return choice.lower()
            raise Exception('operation cancelled')

    def input_string(self, invite, default):
        return self.dlg.inputbox(invite, init=default).decode(sys.stdin.encoding)

And now let's put everything together:

datamodel = mk_datamodel()

import dialog
ui = InterfaceDialog(dialog.Dialog())
ui.dlg.setBackgroundTitle('Dialog Interface with Yams')

objs = []
for entitydef in datamodel.entities():
    if entitydef.final:
    obj = {}
    for attr in entitydef.attribute_definitions():
        if attr[1].type in ('String','Int'):
            obj[str(attr[0])] = ui.input_string('%s.%s' % (entitydef,attr[0]), '')
    except Exception, exc:

print objs

The result is a program that will prompt the user for the title of a form and the text/number of a question, then enforce the type constraints and display the inconsistencies.

The above is very simple and does very little, but if you read the documentation of Yams and if you think about generating the UI with Gtk or Qt instead of dialog, or if you have used the form mechanism of CubicWeb, you'll understand that this proof of concept opens a door to a lot of possibilities.

I will come back to this topic in a later article and give an example of integrating the above with pigg, a simple MVC library for Gtk, to make the programming of user-interfaces even more declarative and bug-free.

blog entry of