Generating Elegance
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?