[Checkins] SVN: zc.relationship/trunk/ zc.relationship relies on
zc.relation;
zc.relationship index now subclasses zc.relation Catalog
Gary Poster
gary at zope.com
Fri Aug 3 18:20:15 EDT 2007
Log message for revision 78569:
zc.relationship relies on zc.relation; zc.relationship index now subclasses zc.relation Catalog
Changed:
_U zc.relationship/trunk/
U zc.relationship/trunk/setup.py
U zc.relationship/trunk/src/zc/relationship/CHANGES.txt
U zc.relationship/trunk/src/zc/relationship/README.txt
U zc.relationship/trunk/src/zc/relationship/index.py
U zc.relationship/trunk/src/zc/relationship/interfaces.py
-=-
Property changes on: zc.relationship/trunk
___________________________________________________________________
Name: svn:externals
+
Modified: zc.relationship/trunk/setup.py
===================================================================
--- zc.relationship/trunk/setup.py 2007-08-03 22:11:42 UTC (rev 78568)
+++ zc.relationship/trunk/setup.py 2007-08-03 22:20:15 UTC (rev 78569)
@@ -2,7 +2,7 @@
setup(
name="zc.relationship",
- version="2.0dev",
+ version="2.0a1",
packages=find_packages('src'),
include_package_data=True,
package_dir= {'':'src'},
@@ -28,6 +28,7 @@
'zope.app.keyreference',
'zope.location',
'zope.index',
+ 'zc.relation',
'zope.app.testing', # TODO remove this
'zope.app.component', # TODO remove this
Modified: zc.relationship/trunk/src/zc/relationship/CHANGES.txt
===================================================================
--- zc.relationship/trunk/src/zc/relationship/CHANGES.txt 2007-08-03 22:11:42 UTC (rev 78568)
+++ zc.relationship/trunk/src/zc/relationship/CHANGES.txt 2007-08-03 22:20:15 UTC (rev 78569)
@@ -7,10 +7,18 @@
(dev version; supports Zope 3.4/Zope 2.11/ZODB 3.8)
+The 2.x line should be almost completely compatible with the 1.x line.
+The one notable incompatibility does not affect the use of relationship
+containers and is small enough that it will hopefully affect noone. If
+anyone using the 1.x line objects to the incompatibility during the
+alpha release, I will consider making it compatible (which will break
+some parallels in the API).
+
New Requirements
----------------
- ZODB 3.8
+- zc.relation
Incompatibilities with 1.0
--------------------------
@@ -18,12 +26,15 @@
- `findRelationships` will now use the defaultTransitiveQueriesFactory if it
is set.
-- `deactivateSets` is no longer an instantiation option (it was broken because
- of a ZODB bug anyway, as had been described in the documentation).
+- Some instantiation exceptions have different error messages.
Changes in 2.0 alpha
--------------------
+- the relationship index code has been moved out to zc.relation and
+ significantly refactored there. A fully backwards compatible subclass
+ remains in zc.relationship.index
+
- support both 64-bit and 32-bit BTree families
- support specifying indexed values by passing callables rather than
@@ -54,7 +65,7 @@
module. (Note that the significantly lower test coverage of the container
code is unlikely to change without contributions: I use the index
exclusively. See plone.relations for a zc.relationship container with
- very good test coverage.
+ very good test coverage.)
1.1
===
Modified: zc.relationship/trunk/src/zc/relationship/README.txt
===================================================================
--- zc.relationship/trunk/src/zc/relationship/README.txt 2007-08-03 22:11:42 UTC (rev 78568)
+++ zc.relationship/trunk/src/zc/relationship/README.txt 2007-08-03 22:20:15 UTC (rev 78569)
@@ -26,6 +26,10 @@
This current document describes the relationship index. See
container.txt for documentation of the relationship container.
+**PLEASE NOTE: the index in zc.relationship, described below, now exists for
+backwards compatibility. zc.relation.catalog now contains the most recent,
+backward-incompatible version of the index code.**
+
=====
Index
=====
@@ -1902,7 +1906,7 @@
>>> ix.findRelationshipTokens({'supervisor': 'Duane'}, maxDepth=3)
Traceback (most recent call last):
...
- ValueError: if maxDepth != 1, transitiveQueriesFactory must be available
+ ValueError: if maxDepth not in (None, 1), queryFactory must be available
>>> ix.defaultTransitiveQueriesFactory = factory
@@ -1973,7 +1977,7 @@
... # doctest: +ELLIPSIS
Traceback (most recent call last):
...
- ValueError: ('Duplicate in attrs', 'objects', <...Attribute ...>)
+ ValueError: ('name already used', 'objects')
>>> ix = index.Index(
... ({'callable': subjects, 'multiple': True, 'name': 'subjects'},
@@ -1981,10 +1985,11 @@
... {'callable': subjects, 'multiple': True, 'name': 'objects'},
... IContextAwareRelationship['getContext']),
... index.TransposingTransitiveQueriesFactory('subjects', 'objects'))
- ... # doctest: +ELLIPSIS
+ ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
- ValueError: ('Duplicate in attrs', 'objects', <...AttrGetter ...>)
+ ValueError: ('element already indexed',
+ <zc.relationship.README.AttrGetter object at ...>)
>>> ix = index.Index(
... ({'element': IRelationship['objects'], 'multiple': True,
@@ -1993,16 +1998,17 @@
... {'element': IRelationship['objects'], 'multiple': True},
... IContextAwareRelationship['getContext']),
... index.TransposingTransitiveQueriesFactory('subjects', 'objects'))
- ... # doctest: +ELLIPSIS
+ ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
- ValueError: ('Duplicate in attrs', 'objects', <...Attribute ...>)
+ ValueError: ('element already indexed',
+ <zope.interface.interface.Attribute object at ...>)
.. [#neither_or_both] It is not allowed to provide only one or the other of
'load' and 'dump'.
>>> ix = index.Index(
- ... ({'element': IRelationship['objects'], 'multiple': True,
+ ... ({'element': IRelationship['subjects'], 'multiple': True,
... 'name': 'subjects','dump': None},
... IRelationship['relationshiptype'],
... {'element': IRelationship['objects'], 'multiple': True},
Modified: zc.relationship/trunk/src/zc/relationship/index.py
===================================================================
--- zc.relationship/trunk/src/zc/relationship/index.py 2007-08-03 22:11:42 UTC (rev 78568)
+++ zc.relationship/trunk/src/zc/relationship/index.py 2007-08-03 22:20:15 UTC (rev 78569)
@@ -13,20 +13,19 @@
from zc.relationship import interfaces
+import zc.relation.catalog
+
+# N.B.
+# this is now a subclass of the zc.relation.catalog.Catalog. It only exists
+# to provide backwards compatibility. New work should go in zc.relation.
+# Ideally, new code should use the zc.relation code directly.
+
##############################################################################
# the marker that shows that a path is circular
#
-class CircularRelationshipPath(tuple):
- interface.implements(interfaces.ICircularRelationshipPath)
+CircularRelationshipPath = zc.relation.catalog.CircularRelationPath
- def __new__(kls, elements, cycled):
- res = super(CircularRelationshipPath, kls).__new__(kls, elements)
- res.cycled = cycled
- return res
- def __repr__(self):
- return 'cycle%s' % super(CircularRelationshipPath, self).__repr__()
-
##############################################################################
# a common case transitive queries factory
@@ -67,6 +66,14 @@
res.update(static)
yield res
+def factoryWrapper(factory, query, index):
+ cache = {}
+ def getQueries(relchain):
+ if not relchain:
+ return (query,)
+ return factory(relchain, query, index, cache)
+ return getQueries
+
##############################################################################
# a common case intid getter and setter
@@ -91,255 +98,72 @@
('BTree', 'TreeSet', 'Bucket', 'Set',
'intersection', 'multiunion', 'union', 'difference'))
-class Index(persistent.Persistent, zope.app.container.contained.Contained):
- interface.implements(interfaces.IIndex)
+class Index(zc.relation.catalog.Catalog,
+ zope.app.container.contained.Contained):
+ interface.implementsOnly(
+ interfaces.IIndex, interface.implementedBy(persistent.Persistent),
+ interface.implementedBy(zope.app.container.contained.Contained))
- family = BTrees.family32
-
def __init__(self, attrs, defaultTransitiveQueriesFactory=None,
dumpRel=generateToken, loadRel=resolveToken,
- relFamily=None, family=None):
- if family is not None:
- self.family = family
- else:
- family = self.family
- self._name_TO_mapping = family.OO.BTree()
- # held mappings are objtoken to (relcount, relset)
- self._EMPTY_name_TO_relcount_relset = family.OO.BTree()
- self._reltoken_name_TO_objtokenset = family.OO.BTree()
+ relFamily=None, family=None, deactivateSets=False):
+ super(Index, self).__init__(dumpRel, loadRel, relFamily, family)
self.defaultTransitiveQueriesFactory = defaultTransitiveQueriesFactory
- if relFamily is None:
- relFamily = family.IF
- self._relTools = getModuleTools(relFamily)
- self._relTools['load'] = loadRel
- self._relTools['dump'] = dumpRel
- self._relLength = Length.Length()
- self._relTokens = self._relTools['TreeSet']()
- self._attrs = _attrs = {} # this is private, and it's not expected to
- # mutate after this initial setting.
- seen = set()
for data in attrs:
- # see README.txt for description of attrs.
-
if zope.interface.interfaces.IElement.providedBy(data):
data = {'element': data}
- res = getModuleTools(data.get('btree', family.IF))
- res['dump'] = data.get('dump', generateToken)
- res['load'] = data.get('load', resolveToken)
- res['multiple'] = data.get('multiple', False)
- if (res['dump'] is None) ^ (res['load'] is None):
- raise ValueError(
- "either both of 'dump' and 'load' must be None, or "
- "neither")
- # when both load and dump are None, this is a small
- # optimization that can be a large optimization if the returned
- # value is one of the main four options of the selected btree
- # family (BTree, TreeSet, Set, Bucket).
-
- if 'element' in data:
- if 'callable' in data:
+ if 'callable' in data:
+ if 'element' in data:
raise ValueError(
'cannot provide both callable and element')
- res['element'] = val = data['element']
- name = res['attrname'] = val.__name__
- res['interface'] = val.interface
- res['call'] = zope.interface.interfaces.IMethod.providedBy(val)
- elif 'callable' not in data:
+ data['element'] = data.pop('callable')
+ elif 'element' not in data:
raise ValueError('must provide element or callable')
- else:
- # must return iterable or None
- val = res['callable'] = data['callable']
- name = getattr(res['callable'], '__name__', None)
- res['name'] = data.get('name', name)
- if res['name'] is None:
- raise ValueError('no name specified')
- if res['name'] in _attrs or val in seen:
- raise ValueError('Duplicate in attrs', res['name'], val)
- seen.add(val)
- if res['TreeSet'].__name__[0] == 'I':
- Mapping = BTrees.family32.IO.BTree
- elif res['TreeSet'].__name__[0] == 'L':
- Mapping = BTrees.family64.IO.BTree
- else:
- assert res['TreeSet'].__name__.startswith('O')
- Mapping = family.OO.BTree
- self._name_TO_mapping[res['name']] = Mapping()
- # these are objtoken to (relcount, relset)
- _attrs[res['name']] = res
-
+ if 'dump' not in data:
+ data['dump'] = generateToken
+ if 'load' not in data:
+ data['load'] = resolveToken
+ self.addValueIndex(**data)
+ # deactivateSets is now ignored. It was broken before.
- def _getValuesAndTokens(self, rel, data):
- values = None
- if 'interface' in data:
- valueSource = data['interface'](rel, None)
- if valueSource is not None:
- values = getattr(valueSource, data['attrname'])
- if data['call']:
- values = values()
- else:
- values = data['callable'](rel, self)
- if not data['multiple'] and values is not None:
- # None is a marker for no value
- values = (values,)
- optimization = data['dump'] is None and (
- values is None or
- isinstance(values, (
- data['TreeSet'], data['BTree'], data['Bucket'], data['Set'])))
- if not values:
- return None, None, optimization
- elif optimization:
- # this is the optimization story (see _add)
- return values, values, optimization
- else:
- cache = {}
- if data['dump'] is None:
- tokens = data['TreeSet'](values)
- else:
- tokens = data['TreeSet'](
- data['dump'](o, self, cache) for o in values)
- return values, tokens, False
+ # disable zc.relation default query factories, enable zc.relationship
+ addDefaultQueryFactory = iterDefaultQueryFactories = None
+ removeDefaultQueryFactory = None
+ def _getQueryFactory(self, query, queryFactory):
+ res = None
+ if queryFactory is None:
+ queryFactory = self.defaultTransitiveQueriesFactory
+ if queryFactory is not None:
+ res = factoryWrapper(queryFactory, query, self)
+ return queryFactory, res
- def _add(self, relToken, tokens, name, fullTokens):
- self._reltoken_name_TO_objtokenset[(relToken, name)] = fullTokens
- if tokens is None:
- dataset = self._EMPTY_name_TO_relcount_relset
- keys = (name,)
- else:
- dataset = self._name_TO_mapping[name]
- keys = tokens
- for key in keys:
- data = dataset.get(key)
- if data is None:
- data = dataset[key] = (
- Length.Length(), self._relTools['TreeSet']())
- res = data[1].insert(relToken)
- assert res, 'Internal error: relToken existed in data'
- data[0].change(1)
+ # disable search indexes
+ _iterListeners = zc.relation.catalog.Catalog.iterListeners
+ def _getSearchIndexResults(self, name, query, maxDepth, filter,
+ targetQuery, targetFilter, queryFactory):
+ return None
+ addSearchIndex = iterSearchIndexes = removeSearchIndex = None
- def _remove(self, relToken, tokens, name):
- if tokens is None:
- dataset = self._EMPTY_name_TO_relcount_relset
- keys = (name,)
- else:
- dataset = self._name_TO_mapping[name]
- keys = tokens
- for key in keys:
- data = dataset[key]
- data[1].remove(relToken)
- data[0].change(-1)
- if not data[0].value:
- del dataset[key]
- else:
- assert data[0].value > 0
-
- def index(self, rel):
- self.index_doc(self._relTools['dump'](rel, self, {}), rel)
-
- def index_doc(self, relToken, rel):
- if relToken in self._relTokens:
- # reindex
- for data in self._attrs.values():
- values, newTokens, optimization = self._getValuesAndTokens(
- rel, data)
- oldTokens = self._reltoken_name_TO_objtokenset[
- (relToken, data['name'])]
- if newTokens != oldTokens:
- if newTokens is not None and oldTokens is not None:
- added = data['difference'](newTokens, oldTokens)
- removed = data['difference'](oldTokens, newTokens)
- if optimization:
- # the goal of this optimization is to not have to
- # recreate a TreeSet (which can be large and
- # relatively timeconsuming) when only small changes
- # have been made. We ballpark this by saying
- # "if there are only a few removals, do them, and
- # then do an update: it's almost certainly a win
- # over essentially generating a new TreeSet and
- # updating it with *all* values. On the other
- # hand, if there are a lot of removals, it's
- # probably quicker just to make a new one." See
- # timeit/set_creation_vs_removal.py for details.
- # A len is pretty cheap--all the buckets should
- # already be in memory.
- len_removed = len(removed)
- if len_removed < 5:
- recycle = True
- else:
- len_old = len(oldTokens)
- ratio = float(len_old)/len_removed
- recycle = (ratio <= 0.1 or len_old > 500
- and ratio < 0.2)
- if recycle:
- for t in removed:
- oldTokens.remove(t)
- oldTokens.update(added)
- newTokens = oldTokens
- else:
- newTokens = data['TreeSet'](newTokens)
- else:
- removed = oldTokens
- added = newTokens
- if optimization and newTokens is not None:
- newTokens = data['TreeSet'](newTokens)
- self._remove(relToken, removed, data['name'])
- self._add(relToken, added, data['name'], newTokens)
- else:
- # new
- for data in self._attrs.values():
- assert self._reltoken_name_TO_objtokenset.get(
- (relToken, data['name']), self) is self
- values, tokens, optimization = self._getValuesAndTokens(
- rel, data)
- if optimization and tokens is not None:
- tokens = data['TreeSet'](tokens)
- self._add(relToken, tokens, data['name'], tokens)
- self._relTokens.insert(relToken)
- self._relLength.change(1)
-
- def unindex(self, rel):
- self.unindex_doc(self._relTools['dump'](rel, self, {}))
-
- def __contains__(self, rel):
- return self.tokenizeRelationship(rel) in self._relTokens
-
- def unindex_doc(self, relToken):
- if relToken in self._relTokens:
- for data in self._attrs.values():
- tokens = self._reltoken_name_TO_objtokenset.pop(
- (relToken, data['name']))
- self._remove(relToken, tokens, data['name'])
- self._relTokens.remove(relToken)
- self._relLength.change(-1)
-
def documentCount(self):
return self._relLength.value
def wordCount(self):
- return 0 # we don't index words; we could arbitrarily keep track of
- # how many related objects we have, but that would be annoying to get
- # right for very questionable benefit
+ return 0 # we don't index words
- def clear(self):
- for v in self._name_TO_mapping.values():
- v.clear()
- self._EMPTY_name_TO_relcount_relset.clear()
- self._reltoken_name_TO_objtokenset.clear()
- self._relTokens.clear()
- self._relLength.set(0)
-
def apply(self, query):
# there are two kinds of queries: values and relationships.
if len(query) != 1:
raise ValueError('one key in the primary query dictionary')
(searchType, query) = query.items()[0]
if searchType=='relationships':
- if self._relTools['TreeSet'].__name__[:2] not in ('IF', 'LF'):
+ relTools = self.getRelationModuleTools()
+ if relTools['TreeSet'].__name__[:2] not in ('IF', 'LF'):
raise ValueError(
'cannot fulfill `apply` interface because cannot return '
'an (I|L)FBTree-based result')
- res = self._relData(query)
+ res = self.getRelationTokens(query)
if res is None:
- res = self._relTools['TreeSet']()
+ res = relTools['TreeSet']()
return res
elif searchType=='values':
data = self._attrs[query['resultName']]
@@ -347,354 +171,110 @@
raise ValueError(
'cannot fulfill `apply` interface because cannot return '
'an (I|L)FBTree-based result')
+ q = BTrees.family32.OO.Bucket(query.get('query', ()))
+ targetq = BTrees.family32.OO.Bucket(query.get('targetQuery', ()))
+ queryFactory, getQueries = self._getQueryFactory(
+ q, query.get('transitiveQueriesFactory'))
iterable = self._yieldValueTokens(
query['resultName'], *(self._parse(
- query['query'], query.get('maxDepth'),
- query.get('filter'), query.get('targetQuery'),
- query.get('targetFilter'),
- query.get('transitiveQueriesFactory')) +
+ q, query.get('maxDepth'), query.get('filter'), targetq,
+ query.get('targetFilter'), getQueries) +
(True,)))
# IF and LF have multiunion; can demand its presence
return data['multiunion'](tuple(iterable))
else:
raise ValueError('unknown query type', searchType)
- def tokenizeQuery(self, query):
- res = {}
- if getattr(query, 'items', None) is not None:
- query = query.items()
- for k, v in query:
- if k is None:
- v = self._relTools['dump'](v, self, {})
- else:
- data = self._attrs[k]
- if v is not None and data['dump'] is not None:
- v = data['dump'](v, self, {})
- res[k] = v
- return res
+ tokenizeRelationship = zc.relation.catalog.Catalog.tokenizeRelation
- def resolveQuery(self, query):
- res = {}
- if getattr(query, 'items', None) is not None:
- query = query.items()
- for k, v in query:
- if k is None:
- v = self._relTools['load'](v, self, {})
- else:
- data = self._attrs[k]
- if v is not None and data['load'] is not None:
- v = data['load'](v, self, {})
- res[k] = v
- return res
+ resolveRelationshipToken = (
+ zc.relation.catalog.Catalog.resolveRelationToken)
- def tokenizeValues(self, values, name):
- dump = self._attrs[name]['dump']
- if dump is None:
- return values
- cache = {}
- return (dump(v, self, cache) for v in values)
+ tokenizeRelationships = zc.relation.catalog.Catalog.tokenizeRelations
- def resolveValueTokens(self, tokens, name):
- load = self._attrs[name]['load']
- if load is None:
- return tokens
- cache = {}
- return (load(t, self, cache) for t in tokens)
+ resolveRelationshipTokens = (
+ zc.relation.catalog.Catalog.resolveRelationTokens)
- def tokenizeRelationship(self, rel):
- return self._relTools['dump'](rel, self, {})
-
- def resolveRelationshipToken(self, token):
- return self._relTools['load'](token, self, {})
-
- def tokenizeRelationships(self, rels):
- cache = {}
- return (self._relTools['dump'](r, self, cache) for r in rels)
-
- def resolveRelationshipTokens(self, tokens):
- cache = {}
- return (self._relTools['load'](t, self, cache) for t in tokens)
-
def findRelationshipTokenSet(self, query):
- # equivalent to, and used by, non-transitive
- # findRelationshipTokens(query)
+ # equivalent to findRelationshipTokens(query, maxDepth=1)
res = self._relData(query)
if res is None:
res = self._relTools['TreeSet']()
return res
def findValueTokenSet(self, reltoken, name):
- # equivalent to, and used by, non-transitive
- # findValueTokens(name, {None: reltoken})
+ # equivalent to findValueTokens(name, {None: reltoken}, maxDepth=1)
res = self._reltoken_name_TO_objtokenset.get((reltoken, name))
if res is None:
res = self._attrs[name]['TreeSet']()
return res
- def _relData(self, searchTerms):
- data = []
- if getattr(searchTerms, 'items', None) is not None: # quack
- searchTerms = searchTerms.items()
- searchTerms = tuple(searchTerms)
- if not searchTerms:
- return self._relTokens
- rel = None
- for nm, token in searchTerms:
- if nm is None:
- rel = token
- if rel not in self._relTokens:
- return None
- else:
- if token is None:
- relData = self._EMPTY_name_TO_relcount_relset.get(nm)
- else:
- relData = self._name_TO_mapping[nm].get(token)
- if relData is None or relData[0].value == 0:
- return None
- data.append((relData[0].value, relData[1])) # length, set
- # we don't want to sort on the set values!! just the lengths.
- data.sort(key=lambda i: i[0])
- if rel is not None:
- for l, s in data:
- if rel not in s:
- return None
- return self._relTools['TreeSet']((rel,))
- # we had an untested optimization attempt here before. It has
- # been tested now (see timeit/manual_intersection.py), found
- # useless, and removed.
- #
- # we know we have at least one result now. intersect all. Work
- # from smallest to largest, until we're done or we don't have any
- # more results.
- res = data.pop(0)[1]
- while res and data:
- res = self._relTools['intersection'](res, data.pop(0)[1])
- return res
-
- def _parse(self, query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory):
- relData = self._relData(query)
- if maxDepth is not None and (
- not isinstance(maxDepth, (int, long)) or maxDepth < 1):
- raise ValueError('maxDepth must be None or a positive integer')
- if filter is not None:
- filterCache = {}
- def checkFilter(relchain, query):
- return filter(relchain, query, self, filterCache)
- else:
- checkFilter = None
- targetCache = {}
- checkTargetFilter = None
- if targetQuery is not None:
- targetData = self._relData(targetQuery)
- if targetData is None:
- relData = None # shortcut
- else:
- if targetFilter is not None:
- def checkTargetFilter(relchain, query):
- return relchain[-1] in targetData and targetFilter(
- relchain, query, self, targetCache)
- else:
- def checkTargetFilter(relchain, query):
- return relchain[-1] in targetData
- elif targetFilter is not None:
- def checkTargetFilter(relchain, query):
- return targetFilter(relchain, query, self, targetCache)
- getQueries = None
- if transitiveQueriesFactory is None:
- transitiveQueriesFactory = self.defaultTransitiveQueriesFactory
- if transitiveQueriesFactory is None:
- if maxDepth != 1 and maxDepth is not None:
- raise ValueError(
- 'if maxDepth != 1, transitiveQueriesFactory must be '
- 'available')
- else:
- transitiveCache = {}
- def getQueries(relchain, query):
- return transitiveQueriesFactory(
- relchain, query, self, transitiveCache)
- return (query, relData, maxDepth, checkFilter, checkTargetFilter,
- getQueries)
-
def findValueTokens(self, resultName, query=(), maxDepth=None,
filter=None, targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- data = self._attrs.get(resultName)
- if data is None:
- raise ValueError('name not indexed', resultName)
- if (((maxDepth is None and transitiveQueriesFactory is None and
- self.defaultTransitiveQueriesFactory is None)
- or maxDepth==1)
- and filter is None and not targetQuery and targetFilter is None):
- if not query:
- return self._name_TO_mapping[resultName]
- rels = self._relData(query)
- if not rels:
- return data['TreeSet']()
- elif len(rels) == 1:
- return self.findValueTokenSet(iter(rels).next(), resultName)
- else:
- iterable = (
- self._reltoken_name_TO_objtokenset.get((r, resultName))
- for r in rels)
- if data['multiunion'] is not None:
- res = data['multiunion'](tuple(iterable))
- else:
- res = data['TreeSet']()
- for s in iterable:
- res = data['union'](res, s)
- return res
- return self._yieldValueTokens(
- resultName, *self._parse(
- query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory))
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findValueTokens(
+ resultName, query, maxDepth, filter, targetQuery, targetFilter,
+ transitiveQueriesFactory)
def findValues(self, resultName, query=(), maxDepth=None, filter=None,
targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- data = self._attrs.get(resultName)
- if data is None:
- raise ValueError('name not indexed', resultName)
- resolve = data['load']
- res = self.findValueTokens(resultName, query, maxDepth, filter,
- targetQuery, targetFilter,
- transitiveQueriesFactory)
- if resolve is None:
- return res
- else:
- cache = {}
- return (resolve(t, self, cache) for t in res)
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findValues(
+ resultName, query, maxDepth, filter, targetQuery, targetFilter,
+ transitiveQueriesFactory)
- def _yieldValueTokens(
- self, resultName, query, relData, maxDepth, checkFilter,
- checkTargetFilter, getQueries, yieldSets=False):
- relSeen = set()
- objSeen = set()
- for path in self._yieldRelationshipTokenChains(
- query, relData, maxDepth, checkFilter, checkTargetFilter,
- getQueries, findCycles=False):
- relToken = path[-1]
- if relToken not in relSeen:
- relSeen.add(relToken)
- outputSet = self._reltoken_name_TO_objtokenset.get(
- (relToken, resultName))
- if outputSet:
- if yieldSets:
- yield outputSet
- else:
- for token in outputSet:
- if token not in objSeen:
- yield token
- objSeen.add(token)
-
- def findRelationshipTokens(self, query=(), maxDepth=None, filter=None,
- targetQuery=None, targetFilter=None,
- transitiveQueriesFactory=None):
- if (((maxDepth is None and transitiveQueriesFactory is None and
- self.defaultTransitiveQueriesFactory is None)
- or maxDepth==1)
- and filter is None and not targetQuery and targetFilter is None):
- return self.findRelationshipTokenSet(query)
- seen = self._relTools['TreeSet']()
- return (res[-1] for res in self._yieldRelationshipTokenChains(
- *self._parse(query, maxDepth, filter, targetQuery,
- targetFilter, transitiveQueriesFactory) +
- (False,))
- if seen.insert(res[-1]))
-
def findRelationships(self, query=(), maxDepth=None, filter=None,
targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- return self.resolveRelationshipTokens(
- self.findRelationshipTokens(
- query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory))
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findRelations(
+ query, maxDepth, filter, targetQuery, targetFilter,
+ transitiveQueriesFactory)
- def findRelationshipChains(self, query, maxDepth=None, filter=None,
+ def findRelationshipTokens(self, query=(), maxDepth=None, filter=None,
targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- """find relationship tokens that match the searchTerms.
-
- same arguments as findValueTokens except no resultName.
- """
- return self._yieldRelationshipChains(*self._parse(
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findRelationTokens(
query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory))
+ transitiveQueriesFactory)
- def _yieldRelationshipChains(self, query, relData, maxDepth, checkFilter,
- checkTargetFilter, getQueries,
- findCycles=True):
- resolve = self._relTools['load']
- cache = {}
- for p in self._yieldRelationshipTokenChains(
- query, relData, maxDepth, checkFilter, checkTargetFilter,
- getQueries, findCycles):
- t = (resolve(t, self, cache) for t in p)
- if interfaces.ICircularRelationshipPath.providedBy(p):
- res = CircularRelationshipPath(t, p.cycled)
- else:
- res = tuple(t)
- yield res
-
- def findRelationshipTokenChains(self, query, maxDepth=None, filter=None,
+ def findRelationshipTokenChains(self, query=(), maxDepth=None, filter=None,
targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- """find relationship tokens that match the searchTerms.
-
- same arguments as findValueTokens except no resultName.
- """
- return self._yieldRelationshipTokenChains(*self._parse(
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findRelationTokenChains(
query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory))
+ transitiveQueriesFactory)
- def _yieldRelationshipTokenChains(self, query, relData, maxDepth,
- checkFilter, checkTargetFilter,
- getQueries, findCycles=True):
- if not relData:
- raise StopIteration
- stack = [((), iter(relData))]
- while stack:
- tokenChain, relDataIter = stack[0]
- try:
- relToken = relDataIter.next()
- except StopIteration:
- stack.pop(0)
- else:
- tokenChain += (relToken,)
- if checkFilter is not None and not checkFilter(
- tokenChain, query):
- continue
- walkFurther = maxDepth is None or len(tokenChain) < maxDepth
- if getQueries is not None and (walkFurther or findCycles):
- oldInputs = frozenset(tokenChain)
- next = set()
- cycled = []
- for q in getQueries(tokenChain, query):
- relData = self._relData(q)
- if relData:
- intersection = oldInputs.intersection(relData)
- if intersection:
- # it's a cycle
- cycled.append(q)
- elif walkFurther:
- next.update(relData)
- if walkFurther and next:
- stack.append((tokenChain, iter(next)))
- if cycled:
- tokenChain = CircularRelationshipPath(
- tokenChain, cycled)
- if (checkTargetFilter is None or
- checkTargetFilter(tokenChain, query)):
- yield tokenChain
+ def findRelationshipChains(self, query=(), maxDepth=None, filter=None,
+ targetQuery=None, targetFilter=None,
+ transitiveQueriesFactory=None):
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).findRelationChains(
+ query, maxDepth, filter, targetQuery, targetFilter,
+ transitiveQueriesFactory)
- def isLinked(self, query, maxDepth=None, filter=None,
+ def isLinked(self, query=(), maxDepth=None, filter=None,
targetQuery=None, targetFilter=None,
transitiveQueriesFactory=None):
- try:
- self._yieldRelationshipTokenChains(*self._parse(
- query, maxDepth, filter, targetQuery, targetFilter,
- transitiveQueriesFactory)+(False,)).next()
- except StopIteration:
- return False
- else:
- return True
+ # argument names changed slightly
+ if targetQuery is None:
+ targetQuery = ()
+ return super(Index, self).canFind(
+ query, maxDepth, filter, targetQuery, targetFilter,
+ transitiveQueriesFactory)
Modified: zc.relationship/trunk/src/zc/relationship/interfaces.py
===================================================================
--- zc.relationship/trunk/src/zc/relationship/interfaces.py 2007-08-03 22:11:42 UTC (rev 78568)
+++ zc.relationship/trunk/src/zc/relationship/interfaces.py 2007-08-03 22:20:15 UTC (rev 78569)
@@ -18,14 +18,10 @@
from zope import interface
from zope.app.container.interfaces import IReadContainer
import zope.index.interfaces
+import zc.relation.interfaces
-class ICircularRelationshipPath(interface.Interface):
- """A tuple that has a circular relationship in the very final element of
- the path."""
+ICircularRelationshipPath = zc.relation.interfaces.ICircularRelationPath
- cycled = interface.Attribute(
- """a list of the searches needed to continue the cycle""")
-
class ITransitiveQueriesFactory(interface.Interface):
def __call__(relchain, query, index, cache):
"""return iterable of queries to search further from given relchain.
More information about the Checkins
mailing list