# -*- coding: utf-8 -*-
import inspect
class NeedOtherComponent(Exception):
def __init__(self, componentName):
super(NeedOtherComponent, self).__init__('need component %s'%componentName)
self.neededComponentName = componentName
def getNestedItem(obj, item):
try:
prefix, suffix = item.split('.', 1)
except ValueError:
return obj[item]
return getNestedItem(obj[prefix], suffix)
def listExtend(a, b):
17 ↛ 19line 17 didn't jump to line 19, because the condition on line 17 was never false if not isinstance(a, list):
a = [a]
19 ↛ 21line 19 didn't jump to line 21, because the condition on line 19 was never false if not isinstance(b, list):
b = [b]
return a + b
class ProductGroupQuery(object):
transitivityTable = {('$eq' , '$eq' ): '$eq' ,
('$eq' , '$lt' ): '$lt' ,
('$eq' , '$lte'): '$lte',
('$eq' , '$gt' ): '$gt' ,
('$eq' , '$gte'): '$gte',
('$lt' , '$lt' ): '$lt' ,
('$lte', '$lt' ): '$lt' ,
('$lte', '$lte'): '$lte',
('$gt' , '$gt' ): '$gt' ,
('$gte', '$gt' ): '$gt' ,
('$gte', '$gte'): '$gte',
}
symmetryTable = {'$eq' : '$eq' ,
'$ne' : '$ne' ,
'$lt' : '$gt' ,
'$gt' : '$lt' ,
'$lte': '$gte',
'$gte': '$lte',
}
combinationTable = {'$gt': max,
'$gte': max,
'$lt': min,
'$lte': min,
'$all': listExtend}
foldTable = {'$all': lambda x: None if isinstance(x, list) else x}
def __init__(self):
self.products = {}
self._finished = False
self._relationTree = {}
self._enclosings = {}
self._sort = []
@property
def finished(self):
return self._finished
def addRelation(self, first, rel, second, isSym=False):
self._finished = False
changed = False
if not isSym:
try:
symRel = self.symmetryTable[rel]
except KeyError:
pass
else:
changed = self.addRelation(second, symRel, first, True)
try:
f = self._relationTree[first]
except KeyError:
f = {}
self._relationTree[first] = f
try:
fr = f[rel]
except KeyError:
fr = set()
f[rel] = fr
if second not in fr:
fr.add(second)
return True
return changed
def addEnclosing(self, center, before, after):
center = center.productQuery._name__
84 ↛ 88line 84 didn't jump to line 88, because the condition on line 84 was never false if not center in self._enclosings:
l = []
self._enclosings[center] = l
else:
l = self._enclosings[center]
l.append((before, after))
def addSort(self, key, direction):
self._sort.append((key, direction))
@property
def relations(self):
for first, first_rels in self._relationTree.items():
for rel, others in first_rels.items():
for second in others:
yield first, rel, second
def finish(self):
#construct dependent relations (e.g. transitivity)
changed = True
while changed:
changed = False
for first, rel1, second in list(self.relations): #list operator copies the result so we can alter the underlying data inside the loop
for rel2, others2 in list(self._relationTree.get(second, {}).items()):
try:
rel3 = self.transitivityTable[rel1, rel2]
except KeyError:
continue
for third in list(others2):
if repr(third) != repr(first): # != on ProductAttribute is reserved for user code!
if self.addRelation(first, rel3, third):
changed = True
self._finished = True
def getQuery(self, primaryProduct, otherProducts={}):
if primaryProduct in self._enclosings:
for before, after in self._enclosings[primaryProduct]:
bn, an = [x.productQuery._name__ for x in (before, after)]
if bn in otherProducts and an not in otherProducts:
raise NeedOtherComponent(an)
if an in otherProducts and bn not in otherProducts:
raise NeedOtherComponent(bn)
if not self.finished:
self.finish()
query = {}
for first, rel, second in self.relations:
if not isinstance(first, ProductAttribute) or first.productQuery._name__ != primaryProduct:
continue
if isinstance(second, ProductAttribute):
#this is a dependent relation
if second.productQuery._name__ in otherProducts:
try:
secondValue = getNestedItem(otherProducts[second.productQuery._name__], second.name)
except KeyError:
raise ValueError('missing "%s" for product "%s"'%(second.name, second.productQuery._name__))
for k,v in rel2mongo(first.name, rel, secondValue).items():
self.joinIntoQuery(query, k, v)
else:
for k,v in rel2mongo(first.name, rel, second).items():
self.joinIntoQuery(query, k, v)
# -- fold too expressive queries
for k, v in list(query.items()):
if isinstance(v, dict) and len(v) == 1:
op = v.keys()[0]
if op in self.foldTable:
foldRule = self.foldTable[op]
foldRes = foldRule(v[op])
if foldRes is not None:
query[k] = foldRes
# -- compute sortings
sort = self._sort[:]
limit = None
for center, enclosings in self._enclosings.items():
if center in otherProducts:
for before, after in enclosings:
if primaryProduct == before.productQuery._name__:
sort.append((before.name, -1))
limit = 1
elif primaryProduct == after.productQuery._name__:
sort.append((after.name, 1))
limit = 1
for before, after in enclosings:
if primaryProduct == before.productQuery._name__ and after.productQuery._name__ in otherProducts:
sort.append((before.name, -1))
limit = 1
elif primaryProduct == after.productQuery._name__ and before.productQuery._name__ in otherProducts:
sort.append((after.name, 1))
limit = 1
sort2 = []
sortd = {}
for k,d in sort:
try:
dold = sortd[k]
except KeyError:
sortd[k] = d
sort2.append((k,d))
else:
assert dold == d
if len(sort2) > 0 or limit is not None:
extra = {}
182 ↛ 184line 182 didn't jump to line 184, because the condition on line 182 was never false if len(sort2) > 0:
extra['sortBy'] = sort2
if limit is not None:
extra['limit'] = limit
return (query, extra)
return query
def joinIntoQuery(self, query, k, v):
try:
existingQuery = query[k]
except KeyError:
query[k] = v
else:
if not (isinstance(existingQuery, dict) and isinstance(v, dict)):
196 ↛ 199line 196 didn't jump to line 199, because the condition on line 196 was never false if existingQuery == v:
return
else:
raise ValueError('mixing equality with inequalities is not supported for "%s"!'%k)
for k2,v2 in v.items():
if k2 in existingQuery:
existingQuery[k2] = self.combinationTable[k2](v2, existingQuery[k2])
else:
existingQuery[k2] = v2
def __getitem__(self, name):
try:
return self.products[name]
except KeyError:
p = ProductQuery(name, self)
self.products[name] = p
return p
class ProductQuery(object):
def __init__(self, name, queryGroup):
self._attributes__ = {}
self._name__ = name
self._queryGroup__ = queryGroup
def __getitem__(self, name):
try:
return self._attributes__[name]
except KeyError:
a = ProductAttribute(name, self)
self._attributes__[name] = a
return a
def __getattr__(self, name):
227 ↛ 229line 227 didn't jump to line 229, because the condition on line 227 was never false if name[0] != '_':
return self[name]
raise AttributeError
class ProductAttribute(object):
def __init__(self, name, productQuery):
self.name = name
self.productQuery = productQuery
self.queryGroup = productQuery._queryGroup__
def __eq__(self, other):
self.queryGroup.addRelation(self, '$eq', other)
def __ne__(self, other):
self.queryGroup.addRelation(self, '$ne', other)
def __lt__(self, other):
self.queryGroup.addRelation(self, '$lt', other)
def __le__(self, other):
self.queryGroup.addRelation(self, '$lte', other)
def __gt__(self, other):
self.queryGroup.addRelation(self, '$gt', other)
def __ge__(self, other):
self.queryGroup.addRelation(self, '$gte', other)
def enclosedBy(self, a, b):
self.queryGroup.addRelation(self, '$gt', a)
self.queryGroup.addRelation(self, '$lt', b)
self.queryGroup.addEnclosing(self, a, b)
def sort(self, direction=1):
self.queryGroup.addSort(self.name, direction)
def isIn(self, other):
self.queryGroup.addRelation(self, '$in', other)
self.queryGroup.addRelation(other, '$all', self, True)
def notIn(self, other):
self.queryGroup.addRelation(self, '$nin', other)
self.queryGroup.addRelation(other, '$ne', self, True)
def __getattr__(self, key):
return self.productQuery['%s.%s'%(self.name, key)]
def __repr__(self):
return '<attr %s:%s>'%(self.productQuery._name__, self.name)
def rel2mongo(first, rel, second):
if rel == '$eq':
return {first: second}
else:
return {first: {rel: second}}
def processQuery(queryfunction):
"""
Assigns a ProductQuery to every argument of queryfunction and returns the
components and associated ProductGroupQuery after computing queryfunction
"""
components = inspect.getargspec(queryfunction).args
if inspect.ismethod(queryfunction):
components = components[1:]
pg = ProductGroupQuery()
products = [pg[c] for c in components]
queryfunction(*products)
return components, pg
if __name__ == '__main__':
pg = ProductGroupQuery()
raw = pg['raw']
cal = pg['cal']
raw['type'] == 'raw_image'
raw['stopTime'] <= cal['validUntil']
print pg.getQuery('raw')
print pg.getQuery('raw',{'cal': {'validUntil': 6}})
print pg.getQuery('cal')
print pg.getQuery('cal',{'raw': {'stopTime': 5}})
|