diff --git a/relation/README.txt b/relation/README.txt index 9737e56..7105cfc 100644 --- a/relation/README.txt +++ b/relation/README.txt @@ -87,11 +87,15 @@ 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 + >>> from zope.interface import implements + >>> from cybertools.relation.interfaces import IRelatable + >>> class Person(Persistent): ... __name__ = __parent__ = None + ... implements(IRelatable) >>> class City(Persistent): - ... pass + ... implements(IRelatable) >>> clark = Person() >>> kirk = Person() @@ -138,6 +142,12 @@ relations, first and second: >>> len(nyRels) 2 +We can also query the registry using an example relation: + + >>> clarkRels = relations.query(example=LivesIn(clark, None)) + >>> len(clarkRels) + 1 + It is also possible to remove a relation from the relation registry: >>> relations.unregister( @@ -254,6 +264,12 @@ if we want to access relations by array index: >>> len(nyRels) 2 +We can also query the registry using an example relation: + + >>> clarkRels = relations.query(example=LivesIn(clark, None)) + >>> len(clarkRels) + 1 + >>> relations.unregister( ... list(relations.query(first=audrey, second=newyork))[0] ... ) @@ -355,10 +371,12 @@ creating a special relation class that uses named predicates. >>> class PredicateRelation(DyadicRelation): ... def __init__(self, predicate, first, second): ... self.predicate = predicate + ... predicate.forClass = self.__class__ ... self.first = first ... self.second = second ... def getPredicateName(self): - ... return self.predicate.getPredicateName() + ... baseName = super(PredicateRelation, self).getPredicateName() + ... return '.'.join((baseName, self.predicate.name)) We also need a class for the predicate objects that will be used for the constructor of the NamedPredicateRelation class: @@ -370,7 +388,10 @@ the constructor of the NamedPredicateRelation class: ... implements(IPredicate) ... def __init__(self, name): ... self.name = name + ... self.forClass = None ... def getPredicateName(self): + ... if self.forClass is not None: + ... return self.forClass(self, None, None).getPredicateName() ... return self.name We can now create a predicate with the name '_lives in_' (that may replace diff --git a/relation/__init__.py b/relation/__init__.py index 9d94eca..1860a7b 100644 --- a/relation/__init__.py +++ b/relation/__init__.py @@ -29,6 +29,7 @@ from persistent import Persistent from zope.interface import implements from interfaces import IPredicate from interfaces import IRelation, IDyadicRelation, ITriadicRelation +from interfaces import IRelatable class Relation(Persistent): @@ -40,6 +41,12 @@ class Relation(Persistent): def validate(self, registry=None): return True + + def checkRelatable(self, *objects): + for obj in objects: + if obj is not None and not IRelatable.providedBy(obj): + raise(ValueError, 'Objects to be used in relations ' + 'must provide the IRelatable interface.') class DyadicRelation(Relation): @@ -49,6 +56,7 @@ class DyadicRelation(Relation): def __init__(self, first, second): self.first = first self.second = second + self.checkRelatable(first, second) class TriadicRelation(Relation): @@ -59,4 +67,5 @@ class TriadicRelation(Relation): self.first = first self.second = second self.third = third + self.checkRelatable(first, second, third) diff --git a/relation/interfaces.py b/relation/interfaces.py index d2703d6..0abcbd4 100644 --- a/relation/interfaces.py +++ b/relation/interfaces.py @@ -71,6 +71,23 @@ class ITriadicRelation(IDyadicRelation): third = Attribute('Third object that belongs to the relation.') +# this is just a conceptual try - thinking about storing +# relations as attributes... +class IAttributeRelation(IDyadicRelation): + """ A type of relation that will be stored in attributes of the + objects that take part in the relation. You have to use a + relation registry that provides IAttributeRelationRegistry + for registering/managing relations of this type. + """ + + attributeNameFirst = Attribute('Name of the attribute in which the ' + 'relation will be stored on the `first` object. ' + 'Typically a class attribute.') + attributeNameSecond = Attribute('Name of the attribute in which the ' + 'relation will be stored on the `second` object.' + 'Typically a class attribute.') + + class IPredicate(Interface): """ A predicate signifies a relationship. This may be implemented directly as a relation class, or the relation object may @@ -90,6 +107,15 @@ class IRelationInvalidatedEvent(IObjectEvent): """ +# marker interfaces + +class IRelatable(Interface): + """ Marker interface for objects that may have relations associated + with them. Should be checked by IRelationRegistry.register() + and event handlers. + """ + + # relation registry interfaces class IRelationRegistryUpdate(Interface): @@ -105,15 +131,12 @@ class IRelationRegistryUpdate(Interface): """ Remove the relation given from this registry. """ -#BBB -#IRelationsRegistryUpdate = IRelationRegistryUpdate - class IRelationRegistryQuery(Interface): """ Interface for querying a relation registry. """ - def query(relation=None, **kw): + def query(example=None, **kw): """ Return a sequence of relations that fulfill the criteria given. You may provide a relation object as an example that specifies the @@ -125,8 +148,6 @@ class IRelationRegistryQuery(Interface): rr.queryRelations(first=someObject, second=anotherObject, relationship=SomeRelationClass) """ -#BBB -#IRelationsRegistryQuery = IRelationRegistryQuery class IRelationRegistry(IRelationRegistryUpdate, IRelationRegistryQuery): @@ -134,6 +155,4 @@ class IRelationRegistry(IRelationRegistryUpdate, IRelationRegistryQuery): implemented as a local utility . """ -#BBB -#IRelationsRegistry = IRelationRegistry diff --git a/relation/registry.py b/relation/registry.py index d4228f6..2ff04e6 100644 --- a/relation/registry.py +++ b/relation/registry.py @@ -57,23 +57,27 @@ class DummyRelationRegistry(object): def query(self, example=None, **kw): result = [] + criteria = {} + if example is not None: + for attr in ('first', 'second', 'third',): + value = getattr(example, attr, None) + if value is not None: + criteria[attr] = value + criteria['relationship'] = example + criteria.update(kw) for r in self.relations: hit = True - for k in kw: - #if ((k == 'relationship' and r.__class__ != kw[k]) + for k in criteria: if ((k == 'relationship' - and r.getPredicateName() != kw[k].getPredicateName()) + and r.getPredicateName() != criteria[k].getPredicateName()) or (k != 'relationship' - and (not hasattr(r, k) or getattr(r, k) != kw[k]))): + and (not hasattr(r, k) or getattr(r, k) != criteria[k]))): hit = False break if hit: result.append(r) return result -#BBB -#DummyRelationsRegistry = DummyRelationRegistry - class RelationRegistry(Catalog): """ Local utility for registering (cataloguing) and searching relations. @@ -97,14 +101,26 @@ class RelationRegistry(Catalog): notify(RelationInvalidatedEvent(relation)) def query(self, example=None, **kw): + intIds = zapi.getUtility(IIntIds) + criteria = {} + if example is not None: + for attr in ('first', 'second', 'third',): + value = getattr(example, attr, None) + if value is not None: + criteria[attr] = intIds.getId(value) + pn = example.getPredicateName() + if pn: + criteria['relationship'] = pn for k in kw: + # overwrite example fields with explicit values if k == 'relationship': - quString = kw[k].getPredicateName() + criteria[k] = kw[k].getPredicateName() else: - quString = zapi.getUtility(IIntIds).getId(kw[k]) + criteria[k] = intIds.getId(kw[k]) + for k in criteria: # set min, max - kw[k] = (quString, quString) - return self.searchResults(**kw) + criteria[k] = (criteria[k], criteria[k]) + return self.searchResults(**criteria) #BBB #RelationsRegistry = RelationRegistry @@ -207,9 +223,13 @@ def invalidateRelations(context, event): """ Handles IObjectRemoved event: unregisters all relations the object to be removed is involved in. """ + # TODO: check marker interface of object: + # if not IRelatable.providedBy(event.object): + # return relations = [] - registry = zapi.queryUtility(IRelationRegistry) - if registry is not None: + #registry = zapi.queryUtility(IRelationRegistry) + registries = zapi.getAllUtilitiesRegisteredFor(IRelationRegistry) + for registry in registries: for attr in ('first', 'second', 'third'): relations = registry.query(**{attr: context}) for relation in relations: