diff --git a/tracking/README.txt b/tracking/README.txt index 59c1885..7da8ca6 100644 --- a/tracking/README.txt +++ b/tracking/README.txt @@ -14,7 +14,7 @@ working with the current run of a task.) >>> tracks = TrackingStorage() >>> tracks.saveUserTrack('a001', 0, 'u1', {'somekey': 'somevalue'}) '0000001' - >>> t1 = tracks.getUserTracks('a001', 0, 'u1') + >>> t1 = list(tracks.getUserTracks('a001', 0, 'u1')) >>> len(t1) 1 >>> t1[0].data @@ -30,18 +30,18 @@ We can query the tracking storage using the tracks' metadata. These are mapped to btree indexes, so we get fast access to the resulting track data. - >>> tracks.query(taskId='a001') + >>> list(tracks.query(taskId='a001')) [] >>> tracks.saveUserTrack('a002', 0, 'u1', {'somekey': 'anothervalue'}) '0000002' - >>> result = tracks.query(userName='u1') + >>> result = list(tracks.query(userName='u1')) >>> len(result) 2 By supplying a list we can also search for more than one value in one query. - >>> result = tracks.query(taskId=('a001', 'a002')) + >>> result = list(tracks.query(taskId=('a001', 'a002'))) >>> len(result) 2 @@ -70,7 +70,7 @@ for a given set of keys. >>> tracks.saveUserTrack('a001', 0, 'u2', {'somekey': 'user2'}, update=True) '0000004' - >>> t4 = tracks.getUserTracks('a001', 0, 'u2') + >>> t4 = list(tracks.getUserTracks('a001', 0, 'u2')) >>> [t.data for t in t4] [{'somekey': 'user2'}] diff --git a/tracking/btree.py b/tracking/btree.py index 93af357..98d6440 100644 --- a/tracking/btree.py +++ b/tracking/btree.py @@ -217,6 +217,7 @@ class TrackingStorage(BTreeContainer): self.taskUsers[taskId].update([userName]) def reindexTracks(self): + # TODO: clear indexes for trackId in self: trackNum = int(trackId) self.indexTrack(trackNum, self[trackId]) @@ -254,7 +255,8 @@ class TrackingStorage(BTreeContainer): start, end = value result = self.intersect(result, self.indexes['timeStamp'].apply((start, end))) - return result and [self[self.idFromNum(r)] for r in result] or set() + #return result and [self[self.idFromNum(r)] for r in result] or set() + return result and (self[self.idFromNum(r)] for r in result) or set() def intersect(self, r1, r2): return r1 is None and r2 or intersection(r1, r2) diff --git a/tracking/notify/README.txt b/tracking/notify/README.txt index 3ecfdbb..5befa67 100644 --- a/tracking/notify/README.txt +++ b/tracking/notify/README.txt @@ -20,7 +20,7 @@ Storing and Retrieving Notifications >>> manager.notify('obj01', 'user01', 'object_changed') - >>> ntf01 = manager.query(userName='user01')[0] + >>> ntf01 = list(manager.query(userName='user01'))[0] >>> ntf01 diff --git a/util/config.py b/util/config.py index 190e71f..df4b94a 100644 --- a/util/config.py +++ b/util/config.py @@ -40,10 +40,11 @@ class Configurator(dict): self.filename = kw.get('filename') def __getitem__(self, key): - item = getattr(self, key, _not_found) - if item is _not_found: - item = ConfigSection(key) - setattr(self, key, item) + return getattr(self, key) + + def __getattr__(self, key): + item = ConfigSection(key) + setattr(self, key, item) return item def load(self, p=None, filename=None): @@ -138,6 +139,6 @@ class ConfigSection(list): for name, value in self.__dict__.items(): if isinstance(value, ConfigSection): value.collect('%s.%s' % (ident, name), result) - elif name != '__name__' and isinstance(value, (str, int)): - result.append('%s.%s = %s' % (ident, name, repr(value))) + elif name != '__name__' and isinstance(value, (str, int, list, tuple)): + result.append('%s.%s = %r' % (ident, name, value)) diff --git a/util/jeep.py b/util/jeep.py index 4583c5f..e369a0a 100644 --- a/util/jeep.py +++ b/util/jeep.py @@ -110,11 +110,15 @@ class Jeep(object): key = getattr(obj, '__name__', getattr(obj, 'name', _notfound)) if key is _notfound: raise AttributeError("No name attribute present") - if key in self: + if key in self.keys(): raise ValueError("Object already present") self._sequence.insert(idx, key) object.__setattr__(self, key, obj) + def update(self, mapping): + for key, value in mapping.items(): + self[key] = value + def pop(self, key=-1): value = self[key] if type(key) in (int, long): diff --git a/util/jeep.txt b/util/jeep.txt index 2fec3e2..61fdf80 100644 --- a/util/jeep.txt +++ b/util/jeep.txt @@ -89,10 +89,14 @@ More Dictionary Methods >>> len(jeep) 4 - >>> del jeep['fourth'] + >>> jeep.update(dict(first='another first value', fifth='5th value')) >>> len(jeep) - 3 - >>> del jeep['fourth'] + 5 + + >>> del jeep['fifth'] + >>> len(jeep) + 4 + >>> del jeep['fifth'] Traceback (most recent call last): ... ValueError: ...not in list @@ -103,6 +107,8 @@ More Methods and Operators >>> 'third' in jeep True + >>> jeep.pop() + 'new fourth value' >>> jeep.pop() 'third value' >>> len(jeep) diff --git a/util/namespace.py b/util/namespace.py index 07920d4..c203a29 100644 --- a/util/namespace.py +++ b/util/namespace.py @@ -26,6 +26,8 @@ $Id$ import traceback +from cybertools.util.jeep import Jeep + _not_found = object() @@ -60,29 +62,108 @@ class BaseNamespace(dict): return result -class Symbol(object): +class Element(object): - def __init__(self, namespace, name): + posArgs = ('name',) + + def __init__(self, namespace, name, collection=None, parent=None): self.namespace = namespace self.name = name + self.collection = collection + self.parent = parent + self.subElements = Jeep() + + def __call__(self, *args, **kw): + elem = self.__class__(self.namespace, '', parent=self) + for idx, v in enumerate(args): + if idx < len(self.posArgs): + setattr(elem, self.posArgs[idx], v) + for k, v in kw.items(): + setattr(elem, k, v) + if not elem.name: + elem.name = self.name + if self.collection is not None: + self.collection.append(elem) + return elem + + def __getitem__(self, key): + if isinstance(key, (list, tuple)): + return tuple(self[k] for k in key) + elif isinstance(key, Element): + self.subElements.append(key) + return key + elif isinstance(key, (int, long, basestring)): + return self.subElements[key] + else: + print '*** Error', key def __str__(self): return self.name def __repr__(self): - return "" % self.name + return "" % self.name class AutoNamespace(BaseNamespace): - symbolFactory = Symbol + elementFactory = Element def __getitem__(self, key): result = self.get(key, _not_found) if result is _not_found: result = getattr(self, key, _not_found) if result is _not_found: - sym = Symbol(self, key) - self[key] = sym - return sym + elem = Element(self, key) + self[key] = elem + return elem return result + + +class Executor(object): + + def __init__(self, namespace): + self.namespace = namespace + + def execute(self, text): + error = '' + try: + exec text in self.namespace + except: + error = traceback.format_exc() + return error + + +class Evaluator(Executor): + + def __init__(self, namespace, allowExec=False): + self.namespace = namespace + self.allowExec = allowExec + + def evaluate(self, text): + """ Evaluate the text as a Python expression given and return the + result. If the text is not an expression try to execute it + as a statement (resulting in a None value). + + The return value is a tuple with the evaluation result and + an error traceback if there was an error. + """ + if self.allowExec: + return self.evalutateOrExecute(text) + result = None + error = '' + try: + result = eval(text, self.namespace) + except: + error = traceback.format_exc() + return result, error + + def evalutateOrExecute(self, text): + result = None + error = '' + try: + result = eval(text, self.namespace) + except SyntaxError: + error = self.execute(text) + except: + error = traceback.format_exc() + return result, error diff --git a/util/namespace.txt b/util/namespace.txt index 9528a4b..7934e76 100644 --- a/util/namespace.txt +++ b/util/namespace.txt @@ -50,9 +50,35 @@ namespace provides a secure restricted execution environment. ... NameError: open +Elements and sub-elements +------------------------- -A Namespace Automatically Generating Symbols -============================================ + >>> code = """ + ... topic('zope3', title='Zope 3')[ + ... annotation(author='jim')] + ... topic('python')[ + ... annotation(author='guido'), + ... child('zope3')] + ... """ + + >>> from cybertools.util.jeep import Jeep + >>> topics = Jeep() + >>> symbols = namespace.BaseNamespace() + >>> symbols['topic'] = namespace.Element(symbols, 'topic', topics) + >>> symbols['annotation'] = namespace.Element(symbols, 'annotation') + >>> symbols['child'] = namespace.Element(symbols, 'child') + + >>> exec code in symbols + + >>> dict(topics) + {'python': , 'zope3': } + + >>> dict(topics['python'].subElements) + {'zope3': , 'annotation': } + + +A Namespace Automatically Generating Elements +============================================= >>> auto = namespace.AutoNamespace() @@ -60,4 +86,50 @@ A Namespace Automatically Generating Symbols something >>> auto.something - + + + +Execution of Python Code in a Namespace +======================================= + +To simplify some standard use cases for working with namespaces the +module provides two classes for execution of Python code with appropriate +simple error handling. + +Evaluation of Python expressions +-------------------------------- + +When evaluating an expression we always get a pair with the resulting +value and an - hopefully empty - error string. + + >>> ev = namespace.Evaluator(auto) + >>> ev.evaluate('25 * 25') + (625, '') + + >>> ev.evaluate('30/0') + (None, 'Traceback...ZeroDivisionError...') + +Trying to execute a statement leads to a syntax error. + + >>> ev.evaluate('print something_else') + (None, 'Traceback...SyntaxError...') + +But we can explicitly allow the execution of statements. The result +of executing a statement is None. + + >>> ev = namespace.Evaluator(auto, allowExec=True) + >>> ev.evaluate('print something_else') + something_else + (None, '') + >>> ev.evaluate('25 * 25') + (625, '') + +Execution of Statements +----------------------- + + >>> ex = namespace.Executor(auto) + >>> ex.execute('number = 25') + '' + + >>> ex.execute('30/0') + 'Traceback...ZeroDivisionError...'