In the Display
class, we used the information about the type of relation stored by the MetaRelation
metaclass. This is necessary because in recognizing that there is more than one type of relation, we need some way to indicate that when we define a new relation and act upon that information when creating a new class. Look at the following example code:
class A(Entity): pass class B(Entity): pass class AhasmanyB(Relation): a=A b=B
Here we express the relation between A
and B
to be one-to-many. If we would like to express the notion that an instance of A
may refer only to a single B
instance, we need some way to indicate that in the definition. One way of doing so is by reversing the assignments in the class definition of the variable:
class AreferstoasingleB(Relation): a=B b=A
The MetaRelation
metaclass we defined earlier could act on such a definition as we arranged for the class dictionary of the relation being defined to be an OrderedDict
, so in principle, we can act on the order of the definitions.
A slightly more explicit way of defining this is often clearer, so instead we opt for a relation_type
attribute that can be assigned a string with the type of the relation. For example:
class AhasmanyB(Relation): a=A b=B relation_type='1:N' class AreferstoasingleB(Relation): a=A b=B relation_type='N:1'
If we leave out the relation_type
, a one-to-many relation is assumed.
Let's have a look at the changes and additions to MetaRelation
needed to implement those semantics. We need two changes. The first is in the definition of the bridge table we use to administer the relation. We need an additional unique
constraint here to enforce that in a one-to-many relation, the IDs in the column referring to the many side of the equation are unique.
This may sound counterintuitive, but let's say we have the following cars: a Volvo, a Renault, a Ford, and a Nissan. There are also two owners, John and Jill. Jill owns the Volvo and the Renault, and John the other cars. The tables might look like this:
Car | |
---|---|
ID |
make |
1 |
Volvo |
2 |
Renault |
3 |
Ford |
4 |
Nissan |
Owner | |
---|---|
ID |
name |
1 |
Jill |
2 |
John |
The table that reflects the ownership of the cars might look like this:
Ownership | |
---|---|
Car |
owner |
1 |
1 |
2 |
1 |
3 |
2 |
4 |
2 |
We see that while a single owner may have many cars, it is the numbers in the Car
column that are unique because of this relation.
In order to define a table with those additional uniqueness constraints and to make the information about the type of relation available in the classes that form both halves of a relation, we have to adapt the final part of the __new__()
method in the MetaRelation
metaclass:
Chapter9/entity.py
if relationdefinition or '_meta' in classdict: a = classdict['a'] b = classdict['b'] r = '1:N'0 if 'relation_type' in classdict: r = classdict['relation_type'] if not r in ('N:1','1:N'): raise KeyError("unknown relation_ type %s"%r) classdict['relation_type'] = r if not issubclass(a,AbstractEntity) : raise TypeError('a not an AbstractEntity') if not issubclass(a,AbstractEntity) : raise TypeError('b not an AbstractEntity') runique = ' ,unique(%s_id)'%a.__name__ if r == '1:N' : runique = ' ,unique(%s_id)'%b.__name__ sql = 'create table if not exists %(rel)s ( %(a)s_id references %(a)s on delete cascade, %(b)s_id references %(b)s on delete cascade, unique(%(a)s_id,%(b)s_id)%(ru)s)'%{'rel':classname,'a':a.__name__,'b':b.__name__,'ru':runique} conn = sqlite.connect(classdict['_database']) conn.execute(sql) setattr(a,'get'+b.__name__,lambda self:getclass(self,b, classname)) setattr(a,'get',get) setattr(b,'get'+a.__name__,lambda self:getclass(self,a, classname)) setattr(b,'get',get) setattr(a,'add'+b.__name__,lambda self,entity:addclass(self, entity,b, classname)) setattr(a,'add',add) setattr(b,'add'+a.__name__,lambda self,entity:addclass(self, entity,a, classname)) setattr(b,'add',add) reltypes = getattr(a,'reltype',{}) reltypes[b.__name__]=r setattr(a,'reltype',reltypes) reltypes = getattr(b,'reltype',{}) reltypes[a.__name__]={'1:N':'N:1','N:N':'N:N','N:1':'1:N'}[r] setattr(b,'reltype',reltypes) relclasses = getattr(a,'relclass',{}) relclasses[b.__name__]=b setattr(a,'relclass',relclasses) relclasses = getattr(b,'relclass',{}) relclasses[a.__name__]=a setattr(b,'relclass',relclasses) joins = getattr(a,'joins',{}) joins[b.__name__]=classname setattr(a,'joins',joins) joins = getattr(b,'joins',{}) joins[a.__name__]=classname setattr(b,'joins',joins) return type.__new__(metaclass,classname,baseclasses,classdict)
The highlighted lines are the ones we added. The first set makes sure there is a relation_type
attribute defined and if not, creates one with a default'1:N'
value.
The second set of highlighted lines determines which column in the bridge table should receive an additional unique constraint and constructs the SQL query to create the table.
The final block of highlighted lines adds class attributes to both classes in the relation. All those attributes are dictionaries indexed by the name of an entity. The reltype
attribute holds the type of the relation, so in a Car
entity, we might obtain the type of relation with an Owner
like this:
Car.reltype('Owner')
Which, if defined as in our previous example, will yield'N:1'
(one or more cars may have a single owner).
Likewise, we can get information about the same relation from the perspective of the owner:
Owner.reltype('Car')
Which will yield the inverse,'1:N'
(an owner may have more than one car).
18.221.66.185