There was a moment on Friday that sums up why I like to teach. As part of preparing for PyCon'08, David Wolever is tidying up DrProject's database schema. One task is to rationalize the handling of enumerations such as ticket states: at present, these are variously represented as:
  • string columns in the database;
  • integer columns that index into tables that store the corresponding human-readable string; and
  • integer columns that are translated into strings in the code.
The cleaned-up version is to have one table that stores all enumerated values:
  • whatfor (string): name of enumeration
  • name (string): name of enumeration elements
  • value (string): integer value of that element (for sorting and cross-referencing)
which gives:
whatfor name value
'ticket_type' 'bug' 0
'ticket_type' 'enhancement' 1
'ticket_type' 'task' 2
'user_state' 'pending_confirmation' 0
'user_state' 'registered' 1
... ... ...
Using SQLAlchemy and Elixir, this schema is:
class Enum(Entity):

    has_field('whatfor', Unicode(), primary_key = True)

    has_field('name', Unicode(), primary_key = True)

    has_field('value', Integer())

    using_options(tablename='enums', order_by=['value'])class Ticket(Entity):

    belongs_to('status', of_kind='Enum')
The problem is, we also want to define default values for the fields whatfor and name when declaring those columns. Normally, this would be done like this:
belongs_to(

    'status',

    of_kind='Enum',

    column_kwargs = {'default': lambda 'some_default'}

)
But that doesn't work in this case, because there are two fields in the primary key of Enum (and they both get assigned 'some_value'). In the long run, there doesn't seem to be any reason why we shouldn't be able to specify per-column settings when the primary key spans multiple fields, like this:
belongs_to(

    'status',

    of_kind='Enum',

    per_column_kwargs = {

        'whatfor' : { 'default' : 'foo'},

        'value'   : { 'default' : 'bar'}

    }

)
But that doesn't work yet, so David came up with this:
def enum_default(whatfor, value):

    def _enum_generator(whatfor, value):

        yield whatfor

        yield value
    return _enum_generator(whatfor, value).next

belongs_to(

    'status',

    of_kind='Enum',

    column_kwargs = {

        'default': enum_default('foo', 'bar')

    }

)
It took me a moment to figure it out, but when I did, I thought it was actually rather elegant. I wonder what I'll learn from my students next week?