Bug fixes for state initialization and registry wildcard lookups.
========================== ``peak.context`` Internals ========================== >>> from peak.context import State, lookup, setting, registry, wildcard >>> from peak import context Thread testing utility:: >>> def run_in_thread(func): ... from threading import Thread ... t = Thread(target = func) ... t.start() ... t.join() State Objects ============= Separate, ongoing states exist for each thread, with the default state for each thread being a child of ``State.root``:: >>> State.get() <...State object at ...> >>> State.get() is State.get() True >>> State.parent is State.root True >>> my_state = State.get() >>> def test_other_thread(): ... print my_state is State.get() ... print State.parent is State.root >>> run_in_thread(test_other_thread) False True States allow setting and getting rules for values:: >>> s = State() >>> v = setting(lambda value=42: value) >>> s[v] = 2 >>> s[v] = 3 But rules can't be changed once they've been used/read:: >>> s[v] 3 >>> s[v] = 3 >>> s[v] = 4 Traceback (most recent call last): ... InputConflict: (<lambda>, 3, 4) ``new()`` and ``empty()`` return new child and root states, respectively:: >>> context.new() is not context.new() True >>> context.new().parent is State.get() True >>> context.empty() is not context.empty() True >>> context.empty().parent is State.root True >>> isinstance(context.new(), State) True >>> isinstance(context.empty(), State) True Settings ======== "Value" settings can be created from functions returning default values:: >>> def a_setting(value=42): return value >>> a_setting = setting(a_setting) If a rule isn't established for a setting in a state, its default value is used:: >>> s[a_setting] 42 And of course it can't be changed except to the same value afterward:: >>> s[a_setting] = 42 >>> s[a_setting] = 43 Traceback (most recent call last): ... InputConflict: (...a_setting..., 42, 43) Child States and Setting Inheritance ==================================== States can have child states, that inherit their rules' settings:: >>> parent = State() >>> child = parent.child() >>> child.parent is parent True >>> parent[a_setting] = 99 >>> child[a_setting] 99 States can be swapped in and out of current-ness:: >>> original = State.get() >>> old = parent.swap() >>> old is original True >>> State.get() is parent True >>> old.swap() is parent True >>> State.get() is old True Calling a setting returns its value in the current state:: >>> a_setting() 42 >>> old = child.swap() >>> a_setting() 99 Scoped States (using __enter__ and __exit__) ============================================ States have ``__enter__`` and ``__exit__`` methods so they can be used as context managers for "with" statements. Once these methods are used, the state can no longer be used for any other purpose. A strict nesting of states being entered or exited is also enforced; a state may have no more than one entered state nested underneath it. (This nesting is dynamic and has nothing to do with parent/child relationships between the states.) Many possible error conditions are checked. For example, you can't __exit__ a state that hasn't been entered yet:: >>> s = State() >>> old = State.get() >>> s.__exit__(None, None, None) Traceback (most recent call last): ... ScopeError: State hasn't been entered yet >>> State.get() is old True Enter an inactive state that already has a nested state under it:: >>> s.__enter__() is s True >>> s is State.get() True >>> old.__enter__() Traceback (most recent call last): ... ScopeError: State already has an active child Re-enter a previously-entered state::: >>> s.__enter__() Traceback (most recent call last): ... ScopeError: Can't re-enter a previously-entered state Exit a state that still has an active nested state:: >>> sub = s.child() >>> sub.__enter__() is sub True >>> s.__exit__(None, None, None) Traceback (most recent call last): ... ScopeError: Nested state(s) haven't exited yet Or enter a state when the current state already has a nested state:: >>> s.swap() is sub True >>> sub2 = State() >>> sub2.__enter__() Traceback (most recent call last): ... ScopeError: Current state already has an active child >>> sub.swap() is s True And note that a state's contents are deleted when it's exited:: >>> def dummy(): pass >>> def on_delete(wr): ... print "dummy deleted" >>> import weakref >>> aRef = weakref.ref(dummy, on_delete) >>> dlist = [dummy] >>> def aKey(expr=lambda:None): return expr() >>> aKey = setting(aKey) >>> sub[aKey] = dlist.pop >>> lookup(aKey) is dummy True >>> dlist [] >>> del dummy >>> sub.__exit__(None, None, None) dummy deleted You can register functions that will be called back when a state is exited, but not if the state has already exited:: >>> def atexit(typ, val, tb): ... print "exiting", typ, val, tb >>> sub.on_exit(atexit) Traceback (most recent call last): ... ScopeError: State already exited Or hasn't been entered yet:: >>> State.get() is s True >>> sub2.on_exit(atexit) Traceback (most recent call last): ... ScopeError: State hasn't been entered yet But once it has been entered, you can register the same function as many times as you like, but it's only run once (XXX run-once guarantee isn't thread-safe yet):: >>> sub2.__enter__() is sub2 True >>> sub2.on_exit(atexit) >>> sub2.on_exit(atexit) # register twice, only run once And the callback is run at exit time, passing the same values:: >>> sub2.__exit__(None, None, None) exiting None None None >>> old.swap() is s True Of course, you can't exit a state that isn't the current state:: >>> s.__exit__(None, None, None) Traceback (most recent call last): ... ScopeError: Can't exit a non-current state And exit callbacks are not allowed to perform any dynamic lookups, since the state has already been cleared.:: >>> s.swap() is old True >>> def try_a_lookup(typ, val, tb): ... lookup(aKey) # this will fail, because scope is closed >>> def print_error(typ, val, tb): ... import traceback ... print '-->', ''.join(traceback.format_exception(typ, val, tb)), '<--' >>> s.on_exit(try_a_lookup) >>> s.on_exit(print_error) >>> s.__exit__(None, None, None) --> Traceback (most recent call last): ... DynamicRuleError: ('default rule or exit function tried to read dynamic state', ...aKey...) <-- But the exit operation will be succeed, anyway, no matter how many errors take place during the callbacks:: >>> State.get() is old True And once exited, you can't exit again, of course:: >>> s.__exit__(None, None, None) Traceback (most recent call last): ... ScopeError: State already exited Nor can you ever make that state active again, either by switching to it or re-entering it:: >>> s.swap() Traceback (most recent call last): ... ScopeError: Can't switch to an exited state >>> State.get() is old True >>> s.__enter__() Traceback (most recent call last): ... ScopeError: Can't re-enter a previously-entered state Nor can you enter a state that is already the current state:: >>> State.get() is old True >>> old.__enter__() Traceback (most recent call last): ... ScopeError: State is already current There is a root state, that is the parent of every empty state:: >>> State().parent is State.root True But it cannot be switched to, entered, or exited:: >>> State.root.swap() Traceback (most recent call last): ... NotImplementedError: Can't switch to the root state >>> State.root.__enter__() Traceback (most recent call last): ... NotImplementedError: Can't enter the root state >>> State.root.__exit__(None, None, None) Traceback (most recent call last): ... NotImplementedError: Can't exit the root state Expressions and ``lookup`` ========================= "Expression" settings can be created from functions returning values:: >>> def a_param(expr=lambda:42): return expr() >>> a_param = setting(a_param) >>> a_param() 42 >>> old = parent.child().swap() >>> State[a_param] = object >>> a_param() <object object at ...> >>> o1 = a_param() >>> o2 = a_param() >>> o1 is o2 True The ``lookup()`` function returns the current value for any key, either static or dynamic:: >>> lookup(a_param) is a_param() True >>> lookup(a_setting) is a_setting() True >>> new = old.swap() >>> lookup(a_setting), lookup(a_param) (99, 42) Rule Purity =========== Looking up non-rule values during default calculation of a rule is forbidden:: >>> def s2(suffix, value=None): ... return a_param() >>> s2 = registry(s2) >>> lookup(s2) Traceback (most recent call last): ... DynamicRuleError: ('default rule or exit function tried to read dynamic state', ...a_param...) As is changing the active state:: >>> def s3(suffix, value=None): ... return parent.swap() >>> s3 = registry(s3) >>> lookup(s3) Traceback (most recent call last): ... DynamicRuleError: default rule or exit function tried to change states >>> a_param() 42 Value Sharing ============= Computed values should rise to the highest part of the state where they can be determined to still be based on identical rules:: >>> root = State() >>> s1 = root.child() >>> s2 = s1.child() >>> s3 = s2.child() >>> s4 = s3.child() >>> def lookupIn(state, key): ... old = state.swap() ... try: return lookup(key) ... finally: old.swap() >>> f1 = lambda expr=None:object() >>> f2 = object # similar result, but unequal factory >>> k1 = setting(f1) >>> s3[k1] = f2 Looking up k1 in s4 propagates the value to s3 and its children, because the rule is established there:: >>> lookupIn(s4, k1) is lookupIn(s3, k1) is lookupIn(s3.child(), k1) True But since s3 has a different rule from s2, the value doesn't propagate any further up:: >>> lookupIn(s3, k1) is lookupIn(s2, k1) # different factories False Instead, s2 shares its value with s1 and root:: >>> lookupIn(s2, k1) is lookupIn(s1, k1) is lookupIn(root, k1) # same ones True A value can only be shared as high as the lowest of its (recursive) dependencies. So, if we define a new expression whose factory at the root is based on a lookup of the previous dynamic expression:: >>> f2 = lambda expr=object: expr() >>> k2 = setting(f2) >>> root[k2] = lambda: [k1()] Then its propagation scope should be the same as those of k1:: >>> lookupIn(s3, k2) is lookupIn(s2, k2) False >>> lookupIn(s4, k2) is lookupIn(s3, k2) is lookupIn(s3.child(), k2) True >>> lookupIn(s4, k2) is not lookupIn(s2, k2) True And a dynamic dependency on k2 should follow the same rules too:: >>> k9 = setting(lambda expr=None: k2()) >>> lookupIn(s3, k9) is lookupIn(s2, k9) False The cases shown so far only show dynamic dependency on an already-known value. We also need to verify dynamic dependency on an as-yet-unknown value:: >>> k3 = setting(f2) >>> k4 = setting(f2) >>> s2[k3] = lambda: object() >>> root[k4] = lambda: [k3()] >>> lookupIn(s3, k4) is lookupIn(s2, k4) is not lookupIn(s1, k4) True And dynamic dependency on a static value:: >>> k5 = setting(lambda value=42: value) >>> k6 = setting(f2) >>> s2[k5] = 99 >>> root[k6] = lambda: k5() >>> lookupIn(s3, k6) is lookupIn(s2, k6) is not lookupIn(s1, k6) True Finally, we need to prove that values are never propagated to the root state, since they would then be universal and un-removable:: >>> def k9(expr=object): ... print "calculating" ... return expr() >>> k9 = setting(k9) >>> lookupIn(root, k9) calculating <object object ...> >>> lookupIn(s2, k9) is lookupIn(s1, k9) calculating True >>> lookupIn(State(), k9) is lookupIn(root, k9) calculating False Services ======== >>> class S1(context.Service): pass >>> class S2(context.Service): context.replaces(S1) >>> S1.get is S2.get True >>> S1.get() is S2.get() True >>> s1 = S1.get() >>> type(s1) <class 'S1'> >>> s2_ = S2.new() >>> s2 = s2_.__enter__() >>> s2 is S1.get() is S2.get() True >>> s3_ = S2.new() # verify that this forces a *new* instance to be made >>> s3 = s3_.__enter__() >>> s3 is S1.get() is S2.get() is not s2 True >>> s3_.__exit__(None,None,None) >>> s2 is S1.get() is S2.get() True >>> s2_.__exit__(None,None,None) >>> s1 is S1.get() True The ``__default__()`` classmethod of a service is called to create its default instance in a given context:: >>> class S3(context.Service): ... def __default__(cls): ... print "creating default instance of", cls ... return cls() ... __default__ = classmethod(__default__) >>> s3 = S3.get() creating default instance of <class 'S3'> ``context.replaces()`` can only be called inside a ``context.Service`` subclass definition, and only once:: >>> context.replaces(S1) Traceback (most recent call last): ... SyntaxError: Class decorators may only be used inside a class statement >>> class X: ... context.replaces(S1) Traceback (most recent call last): ... TypeError: context.replaces() can only be used in a context.Service subclass >>> class X(context.Service): # doctest: +NORMALIZE_WHITESPACE ... context.replaces(S1) ... context.replaces(S3) Traceback (most recent call last): ... ValueError: replaces() must be used only once per class; there is already a value for ``get``: <bound method ...lookup of <class 'S1'>> Services should be subclassable and super() should be usable in spite of all the singleton-izing magic, for both instance and class methods:: >>> class Base(context.Service): ... def test(self, other=None): ... print "hello from Base" ... if other is not None and other is not self: ... print "insanity!" ... t2 = classmethod(test) >>> class Sub(Base): ... def test(self): ... print "hello from sub" ... super(Sub, self).test(self) ... def t2(cls): ... print "hello from sub cm" ... super(Sub, cls).t2() ... t2 = classmethod(t2) >>> Base.get() is Sub.get() False >>> Sub.test() hello from sub hello from Base >>> Sub.t2() hello from sub cm hello from Base Service attributes should be settable and deletable, mapped to the instance:: >>> class Dummy(context.Service): ... foo = 42 >>> Dummy.foo 42 >>> Dummy.foo = 99 >>> Dummy().foo 42 >>> Dummy.get().foo 99 >>> del Dummy.foo >>> Dummy.foo 42 >>> Dummy.get().foo 42 Service classes should be able to have non-string keys in their class dictionary (so that ``ClassAddOns`` from the "AddOns" package work):: >>> class Dummy(context.Service): ... locals()[42]=99 Settings ======== Settings must be defined with one parameter:: >>> def val(value=42): ... """some docstring""" ... return value * 2 >>> val = setting(val) >>> help(val) Help on function val...: <BLANKLINE> val() some docstring <BLANKLINE> which must be named ``expr`` or ``value``:: >>> setting(lambda:42) Traceback (most recent call last): ... TypeError: setting function must have exactly 1 argument(s) >>> setting(lambda x,y: 42) Traceback (most recent call last): ... TypeError: setting function must have exactly 1 argument(s) >>> setting(lambda x: 42) Traceback (most recent call last): ... TypeError: setting function argument 1 must be named 'value' or 'expr' and have a default value:: >>> setting(lambda value: 42) Traceback (most recent call last): ... TypeError: setting function must have a default value for last argument A setting's "input value" is set by State.__setitem__, and is readable with State.__getitem__:: >>> s = State() >>> s[val] = 99 >>> s[val] 99 And its default input value in the root state is the function's default value:: >>> State.root[val] 42 Calling a setting returns its output value in the current state (which is determined by calling the original function on the input value):: >>> val() 84 Configuration Support ===================== A setting's input value in the current state can be set using ``<<=``:: >>> old = State().swap() >>> val <<= 99 >>> val <<= 22 >>> val <<= 77 >>> State[val] 77 >>> val() 154 >>> val <<= 77 >>> val <<= 22 Traceback (most recent call last): ... InputConflict: (val, 77, 22) Likewise for services:: >>> d1 = Dummy() >>> d2 = Dummy() >>> Dummy <<= lambda: d1 >>> Dummy <<= lambda: d2 >>> Dummy.get() is d2 True >>> Dummy <<= lambda: d1 Traceback (most recent call last): ... InputConflict: (<class 'Dummy'>, <...<lambda>...>, <...<lambda>...>) Done with test:: >>> s = old.swap() A setting can turn a configuration string into a value suitable for its own use, using the ``%`` operator. If a setting's parameter is ``expr``, configuration strings and values are treated as zero-argument callables, to be invoked by the setting's body:: >>> setting(lambda expr=None: expr()) % "42" 'lambda: 42' If a setting's parameter is ``value``, configuration strings and values are used directly:: >>> val % "42" '42' For services, configuration strings are always treated as expressions:: >>> Dummy % "42" 'lambda: 42' Registries ========== Registries must be defined with two parameters: ``suffix`` and either ``value`` or ``expr``:: >>> registry(lambda x: 42) Traceback (most recent call last): ... TypeError: registry function must have exactly 2 argument(s) >>> registry(lambda x, y: 42) Traceback (most recent call last): ... TypeError: registry function argument 1 must be named 'suffix' >>> registry(lambda suffix, y: 42) Traceback (most recent call last): ... TypeError: registry function argument 2 must be named 'value' or 'expr' >>> registry(lambda suffix, expr: 42) Traceback (most recent call last): ... TypeError: registry function must have a default value for last argument >>> def dummy(suffix, value=42): ... """another docstring""" ... if suffix: ... print "looking up", suffix ... return suffix ... return value >>> dummy = registry(dummy) Registry objects format strings according to their second argument, much like settings:: >>> dummy % "xyz" 'xyz' >>> registry(lambda suffix, expr=99: 27) % "xyz" 'lambda: xyz' Registries can have lookups done on them to retrieve sub-registries by name:: >>> dummy['foo'] dummy.foo And these sub-registries are cached:: >>> foo = dummy['foo'] >>> foo is dummy['foo'] True ...but immutable:: >>> dummy['foo'] = foo # same value, ok >>> dummy['foo'] = 99 # different value, not ok! Traceback (most recent call last): ... TypeError: Registries are read-only And are also available as attributes:: >>> foo is dummy.foo True >>> foo.bar is dummy.foo['bar'] True >>> dummy.foo = foo # same value, ok >>> dummy.foo = 99 # different value, not ok! Traceback (most recent call last): ... TypeError: Registries are read-only You can iterate over a registry's keys, and test for membership:: >>> list(foo) ['bar'] >>> 'bar' in foo True >>> 'baz' in foo False Dotted names are treated as though they were nested access:: >>> dummy['foo.bar'] is foo.bar True >>> 'foo.bar' in dummy True Wildcards ========= Wildcards are special objects used with registries:: >>> wildcard(dummy) dummy.* >>> type(dummy['*']) <class 'peak.context.wildcard'> Wildcards are effectively settings that prefix config strings with ``lambda suffix:``:: >>> wildcard(dummy) % "blah" 'lambda suffix: blah' and don't transform their input values:: >>> wildcard(dummy).__apply__(dummy, 99) 99 When a non-root registry's fallback is called, it looks up its parent registry's wildcard input, and calls it to compute the input for that key:: >>> def trace(suffix): ... print "tracing", `suffix` >>> foo['*'] <<= trace >>> foo.bar.__fallback__(None, foo.bar) tracing 'bar' And when a wildcard's fallback is called, it returns a callable that can be passed a suffix to compute a setting's value:: >>> foo.bar['*'].__fallback__(None, foo.bar.baz)('x') tracing 'bar.x' >>> star = foo.bar['*'].__fallback__ >>> star(None, foo.bar.baz) == star(None, foo.bar.baz) True >>> foo.bar.baz.__fallback__(None, foo.bar.baz) tracing 'bar.baz' When attached to a non-root registry, their fallback is to look up their parent wildcard:: >>> foo['spam.*'].__fallback__(None, foo.bar)('x') tracing 'spam.x' The fallback for a wildcard attached to a root registry, however, is ``None``:: >>> print dummy['*'].__fallback__(None, foo) None This causes the original target of the lookup to fall back to state-level inheritance, or the original function:: >>> dummy.__fallback__(None, foo.bar) looking up foo.bar 'foo.bar' Registries can be called, passing in a string key and optionally a default value to return in case the key isn't found:: >>> help(dummy) Help on function dummy...: <BLANKLINE> dummy(key, default=None) another docstring <BLANKLINE> >>> dummy('xyz.abc', 88) 88 >>> dummy('foo') looking up foo 'foo' >>> foo.spam <<= 91 >>> dummy('foo.spam') looking up foo.spam 91 >>> lookup(dummy) 42 >>> lookup(foo['really.really.long.name.needing.multiple.rules']) tracing 'really.really.long.name.needing.multiple.rules' looking up foo.really.really.long.name.needing.multiple.rules >>> sorted(foo) ['bar', 'really', 'spam'] Scopes ====== Subclasses of context.Scope can have a ``manage`` method called on their scoped expressions, and an ``atexit`` method that is called upon scope exit, as long as ``manage`` has been called without error at least once:: >>> class MyScope(context.Scope): ... def manage(self, ob): ... print "managing", ob ... return ob ... def atexit(self, typ, val, tb): ... super(MyScope, self).atexit(typ, val, tb) ... print "exiting", typ, val, tb And are not active unless explicitly created by entering their .new():: >>> MyScope.get() Traceback (most recent call last): ... RuntimeError: No MyScope is currently active >>> old = State.get() >>> new = MyScope.new() >>> s = new.__enter__() The state you'll be in is a child of the original state:: >>> State.parent is old True And settings defined using the class' ``.resource`` and ``.resource_registry`` decorators will be passed through the ``manage`` method:: >>> e = MyScope.resource(lambda value=23: value) >>> e() managing 23 23 >>> def e2(suffix, value=23): ... if suffix: ... return suffix ... return value >>> e2 = MyScope.resource_registry(e2) >>> lookup(e2) managing 23 23 >>> lookup(e2.foo) managing foo 'foo' And are always calculated strictly within the state where the scope was created:: >>> this = State.get() >>> def check_state(value=None): ... print State.get() is this >>> check = MyScope.resource(check_state) >>> check() True managing None >>> check2 = MyScope.resource(check_state) >>> context.call_with(State.child())(lambda x: check2()) True managing None Which means that you can't access such an expression from a state where its rule has been changed, as that would create a dependency inversion:: >>> s = State.child() >>> s is s.__enter__() True >>> check2 <<= lambda:check_state() >>> check2() Traceback (most recent call last): ... ScopeError: Redefined rule in sub-state >>> s.__exit__(None, None, None) >>> new.__exit__(None, None, None) exiting None None None TODO: prove that manage() has to be called without an error to enable atexit() Scoped Settings =============== Scope's management wraps the application of a setting's function body:: >>> def res(value=42): ... print "initializing" ... return value >>> res = context.Scope.resource(res) >>> s = context.Scope().new() >>> ss = s.__enter__() >>> res() initializing 42 >>> res() 42 >>> s.__exit__(None, None, None) Resources ========= ``@context.resource`` is a decorator that turns a zero-argument function into a dynamic variable, whose default value in a new context is the function's return value when called within the relevant scope (``Action`` by default):: >>> def my_res(value=42): ... """A resource""" ... return value # default value >>> my_res = context.resource(my_res) >>> help(my_res) Help on function my_res...: <BLANKLINE> my_res() A resource <BLANKLINE> >>> my_res() Traceback (most recent call last): ... RuntimeError: No Action is currently active >>> a = context.Action.new() >>> aa = a.__enter__() >>> my_res() 42 >>> a.__exit__(None, None, None) Actions ======= >>> print context.Action.get() Traceback (most recent call last): ... RuntimeError: No Action is currently active >>> anew = context.Action.new() >>> class TestResource(object): ... def __enter__(self): ... print "Setting up" ... return self ... def __exit__(self, typ, val, tb): ... print "Tearing down", map(str,(typ,val,tb)) >>> res = context.resource(lambda expr=TestResource: expr()) >>> res() Traceback (most recent call last): ... RuntimeError: No Action is currently active >>> act = anew.__enter__() >>> context.Action.get() is act True >>> r1 = res() Setting up >>> r1 <TestResource object...> >>> r2 = res() >>> r1 is r2 True >>> anew.__exit__(None,None,None) Tearing down ['None', 'None', 'None'] >>> print context.Action.get() Traceback (most recent call last): ... RuntimeError: No Action is currently active >>> anew = context.Action.new() >>> act = anew.__enter__() >>> r = res() Setting up >>> r is res() True >>> old = State.child().swap() >>> r is res() True >>> old = old.swap() >>> r3 = res() >>> r4 = res() >>> r3 is r4 True >>> r3 is r2 False >>> anew.__exit__(TypeError,TypeError("Foo"),None) Tearing down [...'exceptions.TypeError'..., 'Foo', ...] >>> def my_factory(): ... print "Factory running" ... return 42 >>> anew = context.Action.new() >>> act = anew.__enter__() >>> State[res] = my_factory >>> res() Factory running 42 >>> res() 42 >>> class Failure(object): ... def __enter__(self): raise RuntimeError("Foo!") ... def __exit__(self,*exc): ... raise AssertionError("This shouldn't get called!") >>> res2 = context.resource(lambda expr=Failure: expr()) >>> res2() Traceback (most recent call last): ... RuntimeError: Foo! >>> class Success(object): ... def __enter__(self): ... print "entering" ... return 99 ... def __exit__(self, *exc): ... print "exiting", exc >>> res3 = context.resource(lambda expr=Success: expr()) >>> res3() entering 99 >>> anew.__exit__(None,None,None) # no __exit__ for failed __enter__ exiting (None, None, None) Source Lines ============ >>> from peak.context import Source, Line >>> s = Source("<test>", "def foo():\n def bar(): return 42\n return bar") >>> s Source('<test>') >>> list(s) ['def foo():\n', ' def bar(): return 42\n', ' return bar'] >>> exec s.compile('exec') >>> bar = foo() >>> bar.func_code.co_firstlineno 2 >>> s[1].strip() 'def bar(): return 42' >>> _.line 2 >>> (s[1]+'\n').splitlines()[1].line 3 >>> exec(s[1].strip().compile('exec')) >>> bar.func_code.co_firstlineno 2 >>> l = Line("lambda: (\n lambda: \n lambda: 42)", s, 19) >>> f = eval(l.compile('eval')) >>> f.func_code.co_firstlineno 19 >>> f().func_code.co_firstlineno 20 >>> f()().func_code.co_firstlineno 21 >>> f()()() 42 >>> from pkg_resources import yield_lines >>> list(yield_lines(s)) ['def foo():', 'def bar(): return 42', 'return bar'] >>> _[2].line 3 PEP 343 Implementation Tests ============================ An example context manager:: >>> def demo_manager(value): ... print "before" ... ... try: ... yield value ... context.reraise() # propagate error, if any ... except TypeError, exc: ... print "caught TypeError" ... print "after" >>> demo_manager = context.manager(demo_manager) Applied to a simple function using ``with_()``:: >>> def something(ob): ... print "got", ob ... return 42 >>> context.with_(demo_manager(99), something) before got 99 after 42 Errors in the called function get passed to ``__exit__()`` and reraised:: >>> def fail(v): ... raise TypeError("foo") >>> context.with_(demo_manager(57), fail) before caught TypeError after >>> def fail(v): ... raise KeyError("foo") >>> try: ... print context.with_(demo_manager(57), fail) ... except KeyError: ... print "KeyError escaped!" before KeyError escaped! You don't have to decorate a new function to use ``call_with`` or ``with_()``:: >>> def something(ob): ... print "got", ob ... return 42 Just wrap the context object, then pass the result the function to call:: >>> context.call_with(demo_manager(None))(something) before got None after 42 Or use ``with_()``, passing in the context and the function in one call:: >>> context.with_(demo_manager(None),something) before got None after 42 Finally, notice that ``__enter__`` may return a different object, which will be passed in to the called function as its sole argument:: >>> context.call_with(demo_manager(99))(something) before got 99 after 42 >>> context.with_(demo_manager(99),something) before got 99 after 42
cvs-admin@eby-sarna.com Powered by ViewCVS 1.0-dev |
ViewCVS and CVS Help |