Added convenience function registry.getRelations()
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@846 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
536983c5da
commit
2657b5d243
3 changed files with 187 additions and 155 deletions
|
@ -86,18 +86,18 @@ relations (we derive the classes from Persistent, thus we will be able to
|
|||
reference these objects via IntIds later; the __parent__ and __name__
|
||||
attributes are also needed later when we send an IObjectRemovedEvent event):
|
||||
|
||||
>>> from persistent import Persistent
|
||||
>>> class Person(Persistent):
|
||||
... __name__ = __parent__ = None
|
||||
>>> from persistent import Persistent
|
||||
>>> class Person(Persistent):
|
||||
... __name__ = __parent__ = None
|
||||
|
||||
>>> class City(Persistent):
|
||||
... pass
|
||||
>>> class City(Persistent):
|
||||
... pass
|
||||
|
||||
>>> clark = Person()
|
||||
>>> kirk = Person()
|
||||
>>> audrey = Person()
|
||||
>>> washington = City()
|
||||
>>> newyork = City()
|
||||
>>> clark = Person()
|
||||
>>> kirk = Person()
|
||||
>>> audrey = Person()
|
||||
>>> washington = City()
|
||||
>>> newyork = City()
|
||||
|
||||
The relation we'll use tells us in which city a person lives; this is a dyadic
|
||||
relation as it connects two objects. We also associate the relationship
|
||||
|
@ -107,49 +107,49 @@ with an interface as we will later use this interface for querying relations.
|
|||
Dyadic Relations
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
>>> from cybertools.relation import DyadicRelation
|
||||
>>> class LivesIn(DyadicRelation):
|
||||
... pass
|
||||
>>> from cybertools.relation import DyadicRelation
|
||||
>>> class LivesIn(DyadicRelation):
|
||||
... pass
|
||||
|
||||
We don't directly keep track of relations but use a relations registry for
|
||||
this. The relations registry is usually a local utility; for testing we use
|
||||
a simple dummy implementation:
|
||||
|
||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||
>>> relations = DummyRelationsRegistry()
|
||||
>>> from cybertools.relation.registry import DummyRelationsRegistry
|
||||
>>> relations = DummyRelationsRegistry()
|
||||
|
||||
So we are ready to connect a person and a city using the LivesIn relationship:
|
||||
|
||||
>>> relations.register(LivesIn(clark, washington))
|
||||
>>> relations.register(LivesIn(audrey, newyork))
|
||||
>>> relations.register(LivesIn(kirk, newyork))
|
||||
>>> relations.register(LivesIn(clark, washington))
|
||||
>>> relations.register(LivesIn(audrey, newyork))
|
||||
>>> relations.register(LivesIn(kirk, newyork))
|
||||
|
||||
We can now query the relations registry to find out where clark lives and
|
||||
who lives in New York. For this we use the standard attributes of dyadic
|
||||
relations, first and second:
|
||||
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
1
|
||||
>>> clarkRels[0].second == washington
|
||||
True
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
1
|
||||
>>> clarkRels[0].second == washington
|
||||
True
|
||||
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
2
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
2
|
||||
|
||||
It is also possible to remove a relation from the relation registry:
|
||||
|
||||
>>> relations.unregister(
|
||||
... relations.query(first=audrey, second=newyork)[0]
|
||||
... )
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
1
|
||||
>>> nyRels[0].first == kirk
|
||||
True
|
||||
>>> relations.unregister(
|
||||
... relations.query(first=audrey, second=newyork)[0]
|
||||
... )
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
1
|
||||
>>> nyRels[0].first == kirk
|
||||
True
|
||||
|
||||
|
||||
|
||||
Triadic Relations
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -157,32 +157,32 @@ We now extend our setting using a triadic relationship - triadic relations
|
|||
connect three objects. (If you want to connect more than three objects you
|
||||
may use combinations of triadic and dyadic relations.)
|
||||
|
||||
>>> from cybertools.relation import TriadicRelation
|
||||
>>> class ParentsOf(TriadicRelation):
|
||||
... """ first (father) and second (mother) are the parents of
|
||||
... third (child)."""
|
||||
>>> from cybertools.relation import TriadicRelation
|
||||
>>> class ParentsOf(TriadicRelation):
|
||||
... """ first (father) and second (mother) are the parents of
|
||||
... third (child)."""
|
||||
|
||||
>>> relations.register(ParentsOf(clark, audrey, kirk))
|
||||
>>> relations.register(ParentsOf(clark, audrey, kirk))
|
||||
|
||||
When we search for relations that contain clark as first we get both:
|
||||
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
2
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
2
|
||||
|
||||
So we want to look only for ParentsOf relationships - this should give us
|
||||
all relations for clark's children:
|
||||
|
||||
>>> clarkChildren = relations.query(relationship=ParentsOf,
|
||||
... first=clark)
|
||||
>>> len(clarkChildren)
|
||||
1
|
||||
>>> clarkChildren[0].second == audrey
|
||||
True
|
||||
>>> clarkChildren[0].third == kirk
|
||||
True
|
||||
>>> clarkChildren = relations.query(relationship=ParentsOf,
|
||||
... first=clark)
|
||||
>>> len(clarkChildren)
|
||||
1
|
||||
>>> clarkChildren[0].second == audrey
|
||||
True
|
||||
>>> clarkChildren[0].third == kirk
|
||||
True
|
||||
|
||||
|
||||
|
||||
Setting up and using a RelationsRegistry local utility
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -191,18 +191,18 @@ the relations registry. We also register the relations registry as a
|
|||
utility for demonstration purposes (and to be able to use it later when
|
||||
working with events).
|
||||
|
||||
>>> from cybertools.relation.registry import RelationsRegistry
|
||||
>>> from cybertools.relation.interfaces import IRelationsRegistry
|
||||
>>> from zope.app.testing import ztapi
|
||||
>>> ztapi.provideUtility(IRelationsRegistry, RelationsRegistry())
|
||||
>>> from cybertools.relation.registry import RelationsRegistry
|
||||
>>> from cybertools.relation.interfaces import IRelationsRegistry
|
||||
>>> from zope.app.testing import ztapi
|
||||
>>> ztapi.provideUtility(IRelationsRegistry, RelationsRegistry())
|
||||
|
||||
>>> from zope.app import zapi
|
||||
>>> relations = zapi.getUtility(IRelationsRegistry)
|
||||
>>> from zope.app import zapi
|
||||
>>> relations = zapi.getUtility(IRelationsRegistry)
|
||||
|
||||
In real life the indexes needed will be set up via subscription to
|
||||
IObjectCreatedEvent - here we have to do this explicitly:
|
||||
|
||||
>>> relations.setupIndexes()
|
||||
>>> relations.setupIndexes()
|
||||
|
||||
In order to register relations the objects that are referenced have to be
|
||||
registered with an IntIds (unique ids) utility, so we have to set up such
|
||||
|
@ -210,75 +210,84 @@ an utility (using a stub/dummy implementation for testing purposes) and
|
|||
register the objects with it (in real life this is done automatically
|
||||
when we add an object to a container):
|
||||
|
||||
>>> from cybertools.relation.tests import IntIdsStub
|
||||
>>> from zope.app.intid.interfaces import IIntIds
|
||||
>>> ztapi.provideUtility(IIntIds, IntIdsStub())
|
||||
>>> intids = zapi.getUtility(IIntIds)
|
||||
>>> intids.register(clark)
|
||||
0
|
||||
>>> intids.register(kirk)
|
||||
1
|
||||
>>> intids.register(audrey)
|
||||
2
|
||||
>>> intids.register(washington)
|
||||
3
|
||||
>>> intids.register(newyork)
|
||||
4
|
||||
>>> from cybertools.relation.tests import IntIdsStub
|
||||
>>> from zope.app.intid.interfaces import IIntIds
|
||||
>>> ztapi.provideUtility(IIntIds, IntIdsStub())
|
||||
>>> intids = zapi.getUtility(IIntIds)
|
||||
>>> intids.register(clark)
|
||||
0
|
||||
>>> intids.register(kirk)
|
||||
1
|
||||
>>> intids.register(audrey)
|
||||
2
|
||||
>>> intids.register(washington)
|
||||
3
|
||||
>>> intids.register(newyork)
|
||||
4
|
||||
|
||||
We also have to provide an adapter for the Relation objects that provides
|
||||
the attributes needed for indexing:
|
||||
|
||||
>>> from cybertools.relation.registry import IIndexableRelation
|
||||
>>> from cybertools.relation.registry import IndexableRelationAdapter
|
||||
>>> from cybertools.relation.interfaces import IRelation
|
||||
>>> ztapi.provideAdapter(IRelation, IIndexableRelation,
|
||||
... IndexableRelationAdapter)
|
||||
>>> from cybertools.relation.registry import IIndexableRelation
|
||||
>>> from cybertools.relation.registry import IndexableRelationAdapter
|
||||
>>> from cybertools.relation.interfaces import IRelation
|
||||
>>> ztapi.provideAdapter(IRelation, IIndexableRelation,
|
||||
... IndexableRelationAdapter)
|
||||
|
||||
So we are ready again to register a set of relations with our new relations
|
||||
registry and query it.
|
||||
|
||||
>>> relations.register(LivesIn(clark, washington))
|
||||
>>> relations.register(LivesIn(audrey, newyork))
|
||||
>>> relations.register(LivesIn(kirk, newyork))
|
||||
>>> relations.register(LivesIn(clark, washington))
|
||||
>>> relations.register(LivesIn(audrey, newyork))
|
||||
>>> relations.register(LivesIn(kirk, newyork))
|
||||
|
||||
As we now get back a result set we have to convert the query results to a list
|
||||
if we want to access relations by array index:
|
||||
|
||||
>>> clarkRels = list(relations.query(first=clark))
|
||||
>>> len(clarkRels)
|
||||
1
|
||||
>>> clarkRels[0].second == washington
|
||||
True
|
||||
>>> clarkRels = list(relations.query(first=clark))
|
||||
>>> len(clarkRels)
|
||||
1
|
||||
>>> clarkRels[0].second == washington
|
||||
True
|
||||
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
2
|
||||
>>> nyRels = relations.query(second=newyork)
|
||||
>>> len(nyRels)
|
||||
2
|
||||
|
||||
>>> relations.unregister(
|
||||
... list(relations.query(first=audrey, second=newyork))[0]
|
||||
... )
|
||||
>>> nyRels = list(relations.query(second=newyork))
|
||||
>>> len(nyRels)
|
||||
1
|
||||
>>> nyRels[0].first == kirk
|
||||
True
|
||||
>>> relations.unregister(
|
||||
... list(relations.query(first=audrey, second=newyork))[0]
|
||||
... )
|
||||
>>> nyRels = list(relations.query(second=newyork))
|
||||
>>> len(nyRels)
|
||||
1
|
||||
>>> nyRels[0].first == kirk
|
||||
True
|
||||
|
||||
It should work also for triadic relations:
|
||||
|
||||
>>> relations.register(ParentsOf(clark, audrey, kirk))
|
||||
>>> relations.register(ParentsOf(clark, audrey, kirk))
|
||||
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
2
|
||||
>>> clarkRels = relations.query(first=clark)
|
||||
>>> len(clarkRels)
|
||||
2
|
||||
|
||||
>>> clarkChildren = list(relations.query(relationship=ParentsOf,
|
||||
... first=clark))
|
||||
>>> len(clarkChildren)
|
||||
1
|
||||
>>> clarkChildren[0].second == audrey
|
||||
True
|
||||
>>> clarkChildren[0].third == kirk
|
||||
True
|
||||
>>> clarkChildren = list(relations.query(relationship=ParentsOf,
|
||||
... first=clark))
|
||||
>>> len(clarkChildren)
|
||||
1
|
||||
>>> clarkChildren[0].second == audrey
|
||||
True
|
||||
>>> clarkChildren[0].third == kirk
|
||||
True
|
||||
|
||||
There is also a convenience function that it makes even easier to query
|
||||
a relations registry; it allows to query for more than one relationship:
|
||||
|
||||
>>> from cybertools.relation.registry import getRelations
|
||||
>>> len(getRelations(first=clark))
|
||||
2
|
||||
>>> len(getRelations(first=clark, relationships=[LivesIn]))
|
||||
1
|
||||
|
||||
|
||||
Handling object removal
|
||||
|
@ -291,14 +300,14 @@ provides a simple handler for this event. (In real life all this is
|
|||
done via configure.zcml - see relation/configure.zcml for an example that
|
||||
also provides the default behaviour.)
|
||||
|
||||
>>> from zope.app.container.interfaces import IObjectRemovedEvent
|
||||
>>> from zope.app.container.contained import ObjectRemovedEvent
|
||||
>>> from zope.event import notify
|
||||
>>> from zope.interface import Interface
|
||||
>>> from cybertools.relation.registry import invalidateRelations
|
||||
|
||||
>>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
|
||||
... invalidateRelations)
|
||||
>>> from zope.app.container.interfaces import IObjectRemovedEvent
|
||||
>>> from zope.app.container.contained import ObjectRemovedEvent
|
||||
>>> from zope.event import notify
|
||||
>>> from zope.interface import Interface
|
||||
>>> from cybertools.relation.registry import invalidateRelations
|
||||
|
||||
>>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
|
||||
... invalidateRelations)
|
||||
|
||||
The invalidateRelations handler will query for all relations the object to be
|
||||
removed is involved in and then fire for these relations a
|
||||
|
@ -310,27 +319,27 @@ the registry module. (There might be other handlers that raise an
|
|||
exception thus preventing the removal of objects that take part in certain
|
||||
relations.)
|
||||
|
||||
>>> from cybertools.relation.interfaces import IRelation
|
||||
>>> from cybertools.relation.interfaces import IRelationInvalidatedEvent
|
||||
>>> from cybertools.relation.registry import removeRelation
|
||||
|
||||
>>> ztapi.subscribe([IRelation, IRelationInvalidatedEvent], None,
|
||||
... removeRelation)
|
||||
>>> from cybertools.relation.interfaces import IRelation
|
||||
>>> from cybertools.relation.interfaces import IRelationInvalidatedEvent
|
||||
>>> from cybertools.relation.registry import removeRelation
|
||||
|
||||
>>> ztapi.subscribe([IRelation, IRelationInvalidatedEvent], None,
|
||||
... removeRelation)
|
||||
|
||||
Let's first check if everything is still as before:
|
||||
|
||||
>>> len(relations.query(first=clark))
|
||||
2
|
||||
|
||||
>>> len(relations.query(first=clark))
|
||||
2
|
||||
|
||||
We simulate the removal of kirk by calling notify, so clark hasn't got
|
||||
a son any longer :-(
|
||||
|
||||
>>> notify(ObjectRemovedEvent(kirk))
|
||||
>>> notify(ObjectRemovedEvent(kirk))
|
||||
|
||||
Thus there should only remain one relation containing clark as first:
|
||||
|
||||
>>> len(relations.query(first=clark))
|
||||
1
|
||||
>>> len(relations.query(first=clark))
|
||||
1
|
||||
|
||||
|
||||
Named Predicates
|
||||
|
@ -343,40 +352,40 @@ But often it is desirable to create new relationships on the fly by
|
|||
providing some string as the name of the relationship. This can be done by
|
||||
creating a special relation class that uses named predicates.
|
||||
|
||||
>>> class PredicateRelation(DyadicRelation):
|
||||
... def __init__(self, predicate, first, second):
|
||||
... self.predicate = predicate
|
||||
... self.first = first
|
||||
... self.second = second
|
||||
... def getPredicateName(self):
|
||||
... return self.predicate.getPredicateName()
|
||||
>>> class PredicateRelation(DyadicRelation):
|
||||
... def __init__(self, predicate, first, second):
|
||||
... self.predicate = predicate
|
||||
... self.first = first
|
||||
... self.second = second
|
||||
... def getPredicateName(self):
|
||||
... return self.predicate.getPredicateName()
|
||||
|
||||
We also need a class for the predicate objects that will be used for
|
||||
the constructor of the NamedPredicateRelation class:
|
||||
|
||||
>>> from cybertools.relation.interfaces import IPredicate
|
||||
>>> from zope.interface import implements
|
||||
|
||||
>>> class Predicate(object):
|
||||
... implements(IPredicate)
|
||||
... def __init__(self, name):
|
||||
... self.name = name
|
||||
... def getPredicateName(self):
|
||||
... return self.name
|
||||
>>> from cybertools.relation.interfaces import IPredicate
|
||||
>>> from zope.interface import implements
|
||||
|
||||
>>> class Predicate(object):
|
||||
... implements(IPredicate)
|
||||
... def __init__(self, name):
|
||||
... self.name = name
|
||||
... def getPredicateName(self):
|
||||
... return self.name
|
||||
|
||||
We can now create a predicate with the name '_lives in_' (that may replace
|
||||
our LivesIn relation class from above) and use for registration:
|
||||
|
||||
>>> livesIn = Predicate('_ lives in _')
|
||||
>>> livesIn = Predicate('_ lives in _')
|
||||
|
||||
>>> relations.register(PredicateRelation(livesIn, clark, washington))
|
||||
>>> relations.register(PredicateRelation(livesIn, audrey, newyork))
|
||||
>>> relations.register(PredicateRelation(livesIn, kirk, newyork))
|
||||
>>> relations.register(PredicateRelation(livesIn, clark, washington))
|
||||
>>> relations.register(PredicateRelation(livesIn, audrey, newyork))
|
||||
>>> relations.register(PredicateRelation(livesIn, kirk, newyork))
|
||||
|
||||
The predicate may then be used as the relationship argument when querying
|
||||
the relations registry.
|
||||
|
||||
>>> len(relations.query(relationship=livesIn, second=washington))
|
||||
1
|
||||
>>> len(relations.query(relationship=livesIn, second=newyork))
|
||||
2
|
||||
>>> len(relations.query(relationship=livesIn, second=washington))
|
||||
1
|
||||
>>> len(relations.query(relationship=livesIn, second=newyork))
|
||||
2
|
||||
|
|
|
@ -59,5 +59,4 @@ class TriadicRelation(Relation):
|
|||
self.first = first
|
||||
self.second = second
|
||||
self.third = third
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -129,6 +129,30 @@ class IndexableRelationAdapter(object):
|
|||
return value
|
||||
|
||||
|
||||
# convenience function:
|
||||
|
||||
def getRelations(first=None, second=None, third=None, relationships=None):
|
||||
""" Return a sequence of relations matching the query specified by the
|
||||
parameters.
|
||||
|
||||
The relationships parameter expects a sequence of relationships
|
||||
(relation classes or predicate objects).
|
||||
"""
|
||||
registry = zapi.getUtility(IRelationsRegistry)
|
||||
query = {}
|
||||
if first: query['first'] = first
|
||||
if second: query['second'] = second
|
||||
if third: query['third'] = third
|
||||
if not relationships:
|
||||
return registry.query(**query)
|
||||
else:
|
||||
result = set()
|
||||
for r in relationships:
|
||||
query['relationship'] = r
|
||||
result.update(registry.query(**query))
|
||||
return result
|
||||
|
||||
|
||||
# events and handlers
|
||||
|
||||
class RelationInvalidatedEvent(ObjectEvent):
|
||||
|
|
Loading…
Add table
Reference in a new issue