import sys class Environ(object): """ Manages stack based globals. A simple example: >>> from environ import ctx >>> def f(): ... return ctx['x'] >>> >>> ctx['x'] = 7 >>> print f() 7 In this example note that f() was not explicitly passed the value for 'x' in order to return it. Instead the stack was probed for the correct value for 'x'. f() could also have decided to change the value as well... >>> from environ import ctx >>> def f(): ... print ctx['x'] ... ctx['x'] = 5 ... g() ... >>> def g(): ... print ctx['x'] ... >>> ctx['x'] = 7 >>> f() 7 5 >>> print ctx['x'] 7 In this case, note how ctx['x'] was changed only for the benefit of g. Environ values can also be dynamic, for example: >>> from environ import ctx >>> ctx['x'] = "HELLO" >>> >>> # Establish y based on x. >>> @ctx.function ... def y(ctx): ... return ctx['x'] + "!" ... >>> ctx['y'] 'HELLO!' Additionally functions can choose whether to examine further up the stack or back at the top environ. >>> from environ import ctx >>> ctx['x'] = 'HELLO' >>> ctx['y'] = 'WORLD' >>> >>> def f(): ... # Get x from a upper level on the stack. ... @ctx.function ... def x(ctx): ... # Notice recursion here... ... return ctx['x'] + '!' ... print ctx['x'] ... print ctx['y'] >>> f() HELLO! WORLD >>> ctx['x'] 'HELLO' >>> >>> # Make y dependent on CURRENT x. >>> @ctx.function ... def y(ctx): ... return ctx['x'] ... >>> f() HELLO! HELLO >>> >>> # Make y dependent on TOP x. >>> @ctx.function ... def y(ctx): ... return ctx.top['x'] ... >>> f() HELLO! HELLO! Environ objects can also be used with other dictionary like objects to combine environmental elements into a canonical global-like arena: >>> from environ import ctx >>> import os >>> ignore = ctx.addSource(os.environ) >>> ctx['PATH'] is not None True """ def __init__(self, frame, top=None, name=None): if isinstance(frame, int): frame = sys._getframe(frame + 1) elif isinstance(frame, basestring): fname = '!' + frame frame = sys._getframe(1) while frame is not None: if frame.f_locals.get(fname): break else: frame = frame.f_back else: raise ValueError, "No frame marked with %s." % fname self.__dict__['__frame__'] = frame self.__dict__['top'] = top self.__dict__['__name__'] = name def mark(self, fname): """ Marks a environ for use by other environ within the stack framework. >>> from environ import ctx >>> ctx.mark('TEST') >>> globals()['!TEST'] True """ sys._getframe(1).f_locals['!' + fname] = True def bind(self, frame): """ Binds (or seeks) another environ relative to this stack frame. >>> from environ import ctx >>> import sys >>> ctx.mark('TEST') >>> f_locals = sys._getframe(0).f_locals >>> ctx.bind(0).__frame__.f_locals is f_locals True >>> ctx.bind('TEST').__frame__.f_locals is f_locals True """ if isinstance(frame, int): frame += 1 return Environ(frame, self) def addSource(self, src, frameno=0): """ Adds a dictionary-like object to the list of external sources used to determine the values for this context. >>> from environ import ctx >>> ignore = ctx.addSource(dict(x=5)) >>> ctx['x'] 5 A typical use is with the os.environ dictionary: >>> from environ import ctx >>> import os >>> ignore = ctx.addSource(os.environ) """ vars = sys._getframe(frameno + 1).f_locals vars['##'] = vars.get('##', ()) + (src,) return src def get(self, name, default=None): searcher = iter(self.search(name)) for frame, obj in searcher: if isinstance(obj, self.DynamicVariable): return obj.fn(Environ(frame, self.top or self, name)) else: return obj else: return default def __getitem__(self, name): obj = self.get(name, KeyError) if obj is KeyError or obj is ValueError: raise KeyError, name else: return obj def search(self, name): frame = self.__frame__ or sys._getframe(1) if name == self.__name__: # Skip top frame if the same name as we were working with. frame = frame.f_back key = '#' + name while frame is not None: obj = frame.f_locals.get(key, KeyError) if obj is KeyError: for ext in frame.f_locals.get('##', ()): obj = ext.get(name, KeyError) if obj is not KeyError: yield frame, obj frame = frame.f_back else: yield frame, obj def put(self, name, value, frame=0): (self.__frame__ or sys._getframe(frame + 1)) \ .f_locals['#' + name] = value def __setitem__(self, name, value): (self.__frame__ or sys._getframe(1)).f_locals['#' + name] = value def __delitem__(self, name): self.put(name, ValueError, 1) def inherit(self, name): """ Similar to del ctx[name], except that the name except that the value at this context is simply discarded (though returned) instead of being left with a marker to consider it deleted. >>> from environ import ctx >>> def f(): ... ctx['x'] = 'overridden' ... print ctx['x'] ... del ctx['x'] ... try: ... print ctx['x'] ... except KeyError: ... print "KEY ERROR" ... ctx.inherit('x') ... print ctx['x'] ... >>> ctx['x'] = 'HELLO' >>> f() overridden KEY ERROR HELLO """ return (self.__frame__ or sys._getframe(1)) \ .f_locals.pop('#' + name, None) def function(self, fn, name=None): if name is None: name = fn.__name__ self.put(name, self.DynamicVariable(fn), 1) return fn class DynamicVariable(object): __slots__ = ['fn'] def __init__(self, fn): self.fn = fn ctx = Environ(None)