[Zope3-checkins] CVS: Zope3/src/zodb/query - relation.py:1.1.2.1
Jeremy Hylton
jeremy@zope.com
Fri, 25 Apr 2003 19:00:02 -0400
Update of /cvs-repository/Zope3/src/zodb/query
In directory cvs.zope.org:/tmp/cvs-serv4854/query
Added Files:
Tag: jeremy-query-branch
relation.py
Log Message:
Add rough prototype of inter-object relationships.
=== Added File Zope3/src/zodb/query/relation.py ===
"""Automatic relationship management
A relationship associates two classes. Each class in the relationship
has an attribute that is associated with the other class. The
relationship can be one-to-one, one-to-many, or many-to-one.
A relationship is created by creating Relation descriptors in the two
associated classes. After the classes are created, the Relation
properties are connected to each other.
The relationship is used just like any other attribute, except that
the Relation objects collaborate to provide referential integrity. If
one side of the relationship is modified, the other side is, too.
A single-valued relationship is represented as a simple object
reference. A many-valued relationship is represented as a container.
"""
# XXX The current implementation only handles many-to-many relationships.
from UserDict import UserDict
class Relation(object):
"""Descriptor for instance variable that is one end of relationship."""
# Implementation details:
# Most of the work is deferred to either the RelationDict or the
# RelationManager. (XXX Maybe the manager isn't necessary and all
# its logic could be in the relation.)
# A descriptor bound to the name foo will create an _r_foo attribute
# for each instance, when the _r_ attribute provides the low-level
# object.
def __init__(self):
self.clsinit = False
self.manager = RelationManager()
def __get__(self, obj, cls=None):
if obj is None:
# The manager needs to know what class contains the
# descriptor. I think the only way to know is to wait
# until the descriptor is first used.
if not self.clsinit:
self.manager.setclass(cls)
self.clsinit = True
return self.manager
else:
return self.manager.get(obj)
class RelationDict(UserDict):
# Dict may not be appropriate for all relationships. Sometimes
# a list may be needed. Probably want to let the user decide.
def __init__(self, obj, manager):
self.obj = obj
self.manager = manager
UserDict.__init__(self)
def add(self, obj):
self[obj] = 1
other = getattr(obj, self.manager.name)
other[self.obj] = 1
class RelationManager:
def __init__(self):
self.cls = None
self.other = None
self.many = None
self.name = None
self.attr = None
def __repr__(self):
return "<relation %s-%s to %s-%s.%s>" % (
self.many and "N" or "1",
self.cls.__name__,
self.other.many and "N" or "1",
self.other.cls.__name__,
self.name)
def setclass(self, cls):
self.cls = cls
def register(self, other, many):
# Called by many2many below to establish connection
# between two Relation objects.
self.other = other
self.name = find_attribute_name(other)
self.many = many
self.attr = "_r_" + self.name
def get(self, obj):
# Get the relationship stored from the object.
rel = getattr(obj, self.attr, None)
if rel is None:
rel = RelationDict(obj, self)
setattr(obj, self.attr, rel)
return rel
def find_attribute_name(r):
# XXX We actually have to find the objects in the class dicts
# to figure out what name they are using
for name in r.cls.__dict__:
v = r.cls.__dict__[name]
if isinstance(v, Relation):
if r == v.manager:
return name
def many2many(r1, r2):
"""Connection to Relations in a many-to-many relationship."""
r1.register(r2, True)
r2.register(r1, True)
class SoftwareProject(object):
developers = Relation()
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
class Developer(object):
projects = Relation()
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.name)
many2many(SoftwareProject.developers, Developer.projects)
if __name__ == "__main__":
zope3 = SoftwareProject("Zope3")
jim = Developer("Jim Fulton")
stevea = Developer("Steve Alexander")
zope3.developers.add(jim)
zope3.developers.add(stevea)
assert stevea in zope3.developers
assert zope3 in stevea.projects