[Zope-dev] RFC: RelationAware class for relations between
objects
Jeremy Hylton
jeremy@zope.com
28 Apr 2003 13:56:56 -0400
Good summary of the current discussion, Shane. At the end, you say that
we shouldn't assume you know what you're talking about. My version of
that is that I assume I don't know what I'm talking about yet.
> Jeremy posted some code that started to look like the right way to
> create relations in ZODB.
>
> http://mail.zope.org/pipermail/zope3-dev/2003-April/006720.html
>
> Here are the important features that made it interesting:
>
> - You describe relations in the same place you write classes. The great
> thing about an object-oriented database is that you can get so much done
> just by writing classes. But in the current ZODB, as soon as you need
> flexible relationships, you have to move into a totally different
> sphere, such as creating a ZCatalog or some kind of relationship
> service. It shouldn't be that way. Python is expressive enough.
This was the primary goal for me. The implementation of a relationship
may be complicated, but I think the client code should be kept as simple
as possible.
The joy of coding Python should be in seeing short, concise, readable
classes that express a lot of action in a small amount of clear code -
not in reams of trivial code that bores the reader to death.
-- Guido van Rossum
> - Descriptors elegantly provide custom views on relations. In the
> example, "zope3.developers" looks like a set of Developer objects.
Descriptors can do anything! I blogged a little about this on Friday:
http://www.python.org/~jeremy/weblog/
> - All the implementation details, such as use of BTrees, was moved away
> from the application code. To me, this means that the default relation
> implementation could be substituted depending on the capabilities of the
> storage method. Ape, for example, could implement relational queries by
> translating to SQL.
I'm not quite sure what all the implementation details are. Can you say
more about how you would implement relations in Ape?
The simple relationship manager I wrote uses a dictionary. I can see
wanting some other data structure when the objects aren't hashable in a
useful. And I can see using some BTree data structure when the
individual relationships involve many objects. A relational database
model seems quite different, because the database stores all the
relationships for instances of those classes, rather than a single set
of objects.
> Unfortunately, I didn't like Jeremy's revisions quite as much. The
> revised version creates two Relation objects instead of one. Maybe I
> just don't understand it yet, but it doesn't fit my brain. ;-)
Perhaps some rationale is in order.
A descriptor lives in a class dictionary, so it needs to be declared in
the class statement rather than on the instance. It's possible to add
the descriptor after the class is created, but I really don't want to do
that. I like that the attribute name gets declared as a relationship in
the class statement.
The chief difference between the vapor version and the implemented
version is that the vapor version had a single Relation object that was
bound to both instances and the implemented version was two Relation
descriptors that get joined together. The latest CVS version looks like
this:
class SoftwareProject(object):
developers = Relation()
def __init__(self, name):
self.name = name
class Developer(object):
projects = Relation()
def __init__(self, name):
self.name = name
join(many(SoftwareProject.developers),
many(Developer.projects))
Does this version of the API look any better?
> I prefer
> the notion of having two views on a single relation. I also feel that
> having a many2many function might be oversimplifying, since I came up
> with the need for a "many2many2many" function over the weekend. That
> would be wrong!
I'm not actually sure if we need two different descriptors. I guess we
can have one descriptor that dispatches based on the class it was bound
to. The separate descriptors may be a result of keeping the
implementation simple at the expense of the clients. Regardless of how
it's spelled, though, there is a bit of necesary complexity that comes
from doing this in class statements. The Relation objects need to be
created before the classes are created. There needs to be a call that
tells the Relation about all the classes that participate in the
Relation. (Maybe it could be done when the classes are created via a
custom metaclass, but that seems to messy.)
Can you post a simple example of many2many2many? It would surely be
simpler to spell with the join() function above.
> We need to make sure the interface fits an existing,
> well-researched model for relationships. I only know about relational
> tables, topic maps, and RDF.
I don't know much about any of these. From what little I know of RDF,
it seems an example to avoid for this work. I've never heard of "topic
maps."
I know that the ODMG object database standard has binary relationships,
that is relationships between pairs of objects. I don't really
understand how an object database extends to relationships among many
objects, since a pointer just points to one thing. I'd be quite
interested to see how a 3-way relationship worked in ZODB.
> Max M: your example is useful and probably more manageable than
> ZCatalogs. But I think it would be more useful if it provided an easy
> way to create relations in the code itself. You only have a comment
> that says the relation already exists. Jeremy's example creates the
> relation if it doesn't already exist, although it's only a basic
> relation. You example would also be enhanced by the use of descriptors.
Not sure if I follow this example completely -- assuming you mean the
example code with deposit() and withdraw() methods. I assume the
particular example of moving an object would use the object hub in
Zope3. But it sounds like this relationship is a transient one; the use
case being addressed is just preserving a link when an object changes
location.
Jeremy