[ZODB-Dev] Re: [Zope3-dev] PROPOSAL: ZODB Relationships
Roché Compaan
roche at upfrontsystems.co.za
Sun May 11 20:30:18 EDT 2003
I played around a bit and have some proof of concept code honouring the
important things in the proposal as well as what MOF brings to the
table, hopefully. We need to beat the code it into shape though ;-) I
didn't define interfaces now but will when the code settles. I split up
multiplicity into the parameters lower- and upperBound so that we can
give them defaults. Questions I have are nested in the code:
# We should provide an interface for storage types and users must be
# able to pass in their own storage types. Jeremy mentioned this.
# I just use OOBTree and PersistentList for proof of concept
from BTrees.OOBTree import OOBTree
from ZODB.PersistentList import PersistentList
class AssociationError(Exception):
pass
# Or is their a built-in Exception better suited?
class Association:
def __init__(self, name, fromEnd, toEnd):
self.name = name
self.fromEnd = fromEnd
self.toEnd = toEnd
# If the AssociationEnd doesn't know its Association then it is
# not possible for the Reference to find the Association. It
# feels funny to just set it on the AssociationEnds, but we
# create AssociationEnd instances before the Association
self.fromEnd.association = self
self.toEnd.association = self
# We set 'otherEnd' when isNavigable is true but I don't really
# use it. Code to illustrate use cases would help.
if self.fromEnd.isNavigable:
self.fromEnd.otherEnd = toEnd
if self.toEnd.isNavigable:
self.toEnd.otherEnd = fromEnd
def add(self, fromObj, toObj):
self.fromEnd._add(fromObj, toObj)
self.toEnd._add(toObj, fromObj)
def delete(self, fromObj, toObj):
self.fromEnd._delete(fromObj, toObj)
self.toEnd._delete(toObj, fromObj)
def __getattr__(self, name):
if name == self.fromEnd.name:
return self.fromEnd
elif name == self.toEnd.name:
return self.toEnd
class AssociationEnd:
def __init__(self, name, lowerBound=1, upperBound=None, navigable=True):
self.name = name
self.lowerBound = lowerBound
self.upperBound = upperBound
self.isNavigable = navigable
self.otherEnd = None
# We maintain both directions of the mapping. Not sure yet but
# maybe we can get away with just a '_fromMap'
self._fromMap = OOBTree()
self._toMap = OOBTree()
def _addToMap(self, map, key, value):
if not map.has_key(key):
if (map == self._fromMap and
self.upperBound and len(map) == self.upperBound):
raise AssociationError, 'Upper bound exceeded'
map[key] = PersistentList()
map[key].append(value)
def _deleteFromMap(self, map, key, value):
if map.has_key(key) and value in map[key]:
map[key].remove(value)
if len(map[key]) == 0:
del map[key]
def _add(self, fromObj, toObj):
self._addToMap(self._fromMap, fromObj, toObj)
self._addToMap(self._toMap, toObj, fromObj)
def _delete(self, fromObj, toObj):
self._deleteFromMap(self._fromMap, fromObj, toObj)
self._deleteFromMap(self._toMap, toObj, fromObj)
def get(self, toObj):
return self._toMap.get(toObj, [])
class Reference(object):
def __init__(self, associationEnd):
self.associationEnd = associationEnd
def __get__(self, obj, cls=None):
references = self.associationEnd.get(obj)
if self.associationEnd.upperBound == 1:
return references[0]
else:
return references
def __set__(self, obj, value):
if self.associationEnd.upperBound == 1:
value = [value]
# remove existing associations
association = self.associationEnd.association
current_values = self.associationEnd.get(obj)
for toObj in current_values:
association.delete(obj, toObj)
# add the new ones
for toObj in value:
association.add(obj, toObj)
########################
# But does it work? Let's test.
teacher = AssociationEnd('teacher', upperBound=1)
courses = AssociationEnd('courses')
teaches = Association('teaches', teacher, courses)
class Teacher(object):
courses = Reference(teaches.courses)
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
class Course(object):
teacher = Reference(teaches.teacher)
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
monty = Teacher('monty')
brian = Teacher('brian')
zope101 = Course('zope101')
python101 = Course('python101')
# Testing associations
teaches.add(monty, zope101)
teaches.add(monty, python101)
assert teaches.teacher.get(zope101) == [monty]
assert teaches.courses.get(monty) == [zope101, python101]
teaches.delete(monty, zope101)
assert teaches.teacher.get(zope101) == []
assert teaches.courses.get(monty) == [python101]
# this should raise an AssociationError
try:
teaches.add(brian, [zope101])
except AssociationError:
print 'Exception raised'
# Testing references
assert monty.courses == [python101]
monty.courses = [zope101, python101]
python101.teacher == brian
# Check if the association was updated
assert teaches.courses.get(monty) == [zope101, python101]
monty.courses = [zope101]
assert zope101.teacher == monty
--
Roché Compaan
Upfront Systems http://www.upfrontsystems.co.za
More information about the ZODB-Dev
mailing list