Commits

Lynn Rees committed 2af4d23

- temp

Comments (0)

Files changed (13)

call chain to do.mm

+<map version="0.9.0">
+<!--To view this file, download free mind mapping software Freeplane from http://freeplane.sourceforge.net -->
+<node TEXT="call chain to do" ID="ID_1791372525" CREATED="1333385124681" MODIFIED="1333385136051">
+<hook NAME="MapStyle" max_node_width="600"/>
+<node TEXT="split root/manager" POSITION="right" ID="ID_998421968" CREATED="1333385136638" MODIFIED="1333385147339"/>
+<node TEXT="auto pypi download" POSITION="right" ID="ID_1849918399" CREATED="1333385148352" MODIFIED="1333385154978"/>
+<node TEXT="config by subclass" POSITION="right" ID="ID_1087281301" CREATED="1333385155504" MODIFIED="1333385165993"/>
+<node TEXT="inherit config" POSITION="right" ID="ID_1076054236" CREATED="1333385166495" MODIFIED="1333385173042"/>
+<node TEXT="verify paths" POSITION="right" ID="ID_248582175" CREATED="1333385173647" MODIFIED="1333385185314"/>
+<node TEXT="better namespace mgmt" POSITION="right" ID="ID_167423364" CREATED="1333385186031" MODIFIED="1333385198697"/>
+<node TEXT="scanning" POSITION="right" ID="ID_1132753274" CREATED="1333385199119" MODIFIED="1333385224530"/>
+<node TEXT="better namespaced todo for ide" POSITION="right" ID="ID_1913812434" CREATED="1333385293164" MODIFIED="1333385309304"/>
+<node TEXT="load by keywords" POSITION="right" ID="ID_1339124316" CREATED="1333385381436" MODIFIED="1333385386958"/>
+</node>
+</map>

callchain/call.py

-# -*- coding: utf-8 -*-
-'''callchain call mixins'''
-
-from functools import partial
-from itertools import chain, count
-
-from twoq.support import isstring
-from appspace.builders import Appspace
-from stuf.utils import OrderedDict, lazy
-
-from callchain.managers import Events
-from callchain.core import ConfigMixin
-from callchain.patterns import Pathways
-from callchain.keys.base import NoServiceError
-from callchain.support import Empty, Queue, PriorityQueue, total_ordering
-
-
-@total_ordering
-class _Partial(object):
-
-    '''partial wrapper'''
-
-    __slots__ = ('_call', 'count', '__call__')
-
-    counter = count()
-
-    def __init__(self, call, *args, **settings):
-        self.__call__ = partial(call, *args, **settings)
-        priority = settings.pop('priority', None)
-        self.count = next(self.counter) if priority is None else priority
-
-    def __lt__(self, other):
-        return self.count < other
-
-
-###############################################################################
-## chain components ###########################################################
-###############################################################################
-
-
-class inside(object):
-
-    '''internal chain configuration'''
-
-    __slots__ = ('pattern', 'required', 'defaults', 'args', 'kw')
-
-    def __init__(self, pattern, required=None, defaults=None, *args, **kw):
-        '''
-        init
-
-        @param pattern: pattern configuration class or appspace label
-        @param required: required global settings (default: None)
-        @param defaults: default global settings (default: None)
-        '''
-        self.pattern = pattern
-        self.required = required
-        self.defaults = defaults
-        self.args = args
-        self.kw = kw
-
-    def __call__(self, that):
-        # internal appspace manager
-        that._M = Pathways.appspace(
-            self.pattern,
-            self.required,
-            self.defaults,
-            *self.args,
-            **self.kw
-        )
-        # lock internal appspace global settings
-        that._M.settings.lock()
-        # set internal appspace global settings
-        that._G = that._M.settings.final
-        return that
-
-
-class CallMixin(ConfigMixin):
-
-    '''chain mixin'''
-
-    def __enter__(self):
-        '''enter execution context'''
-        return self
-
-    def __exit__(self, e, t, b):
-        '''exit execution context'''
-        # invoke call chain
-        self.commit()
-
-    def _load(self, label):
-        '''
-        silent internal switch to...
-
-        @param label: label of appspaced thing
-        '''
-        try:
-            # look up internal appspaced linked call chain...
-            _M = self._M
-            key = _M.service(label)
-            return getattr(_M.get(key, key)(self), label)
-        except NoServiceError:
-            # ...or lookup some other appspaced thing
-            return super(CallMixin, self)._load(label)
-
-    @lazy
-    def space(self):
-        '''external appspace interface'''
-        return Appspace(self.M) if self.M is not None else None
-
-    def _setup(self, root):
-        '''call chain setup'''
-        # call chain queue
-        self._chain = self._queue()
-
-    def chain(self, call, key=False, *args, **kw):
-        '''
-        add `call` or appspaced `call` to call chain, partializing it with any
-        passed arguments
-
-        @param call: call or appspaced call label
-        @param key: appspace key (default: False)
-        '''
-        self._chain.put(
-            _Partial(call, *(key,) + args, **kw) if not isstring(call)
-            else _Partial(self.M.get(call, key), *args, **kw)
-        )
-        return self
-
-    def commit(self):
-        '''consume call chain'''
-        with self.ctx3():
-            return self._xtend(
-                c() for c in self.iterexcept(self._chain.get_nowait, Empty)
-            )
-
-    def switch(self, label, key=False):
-        '''
-        overt switch to linked chain configured in external appspace
-
-        @param label: linked chain label
-        @param key: linked chain chain key (default: False)
-        '''
-        return self.M.get(label, key)(self)
-
-    def tap(self, call, key=False):
-        '''
-        add call
-
-        @param call: callable or appspace label
-        @param key: link call chain key (default: False)
-        '''
-        return super(CallMixin, self).tap(
-            self._M.get(call, key) if isstring(call) else call
-        )
-
-    def wrap(self, call, key=False):
-        '''build current callable from factory'''
-        return super(CallMixin, self).wrap(
-            self._M.get(call, key) if isstring(call) else call
-        )
-
-    class Meta:
-        pass
-
-
-class PriorityMixin(CallMixin):
-
-    '''chain mixin'''
-
-    _queue = PriorityQueue
-
-    def chain(self, call, key=False, *args, **kw):
-        '''
-        add `call` or appspaced `call` to call chain, partializing it with any
-        passed arguments
-
-        @param call: call or appspaced call label
-        @param key: appspace key (default: False)
-        '''
-        self._chain.put(
-            _Partial(call, *(key,) + args, **kw)
-            if not isstring(call)
-            else _Partial(self.M.get(call, key), *args, **kw)
-        )
-        return self
-
-
-class ChainMixin(CallMixin):
-
-    '''chain mixin'''
-
-    _queue = Queue
-
-    def chain(self, call, key=False, *args, **kw):
-        '''
-        add `call` or appspaced `call` to call chain, partializing it with any
-        passed arguments
-
-        @param call: call or appspaced call label
-        @param key: appspace key (default: False)
-        '''
-        if not isstring(call):
-            self._chain.put_nowait(self._partial(call, *(key,) + args, **kw))
-        else:
-            self._chain.put_nowait(
-                self._partial(self.M.get(call, key), *args, **kw)
-            )
-        return self
-
-
-###############################################################################
-## event chain components #####################################################
-###############################################################################
-
-
-class einside(inside):
-
-    '''internal event chain configuration'''
-
-    __slots__ = ('pattern', 'required', 'defaults', 'args', 'kw', 'events')
-
-    def __init__(
-        self, patterns, events=None, required=None, defaults=None, *args, **kw
-    ):
-        '''
-        init
-
-        @param patterns: pattern config or appspace label (default: None)
-        @param events: events configuration (default: None)
-        @param required: required settings (default: None)
-        @param defaults: default settings (default: None)
-        '''
-        super(einside, self).__init__(
-            patterns, required, defaults, *args, **kw
-        )
-        self.events = events
-
-    def __call__(self, that):
-        that = super(einside, self).__call__(that)
-        that.E = Events('events')
-        that.E.update(self.events)
-        return that
-
-
-class EventMixin(ChainMixin):
-
-    '''event chain mixin'''
-
-    @property
-    def _linkedchain(self):
-        '''new linked chain'''
-        return self._M.get('chain', 'event')(self)
-
-    def _events(self, *events):
-        '''calls bound to `events`'''
-        return chain(*tuple(self._imap(self._event, events)))
-
-    def commit(self):
-        '''run event chain'''
-        fire = self.fire
-        try:
-            # 1. "before" event 2. "work" event
-            fire('before', 'work')
-            # everything else
-            super(EventMixin, self).commit()
-            # 3. "change" event 4. "any" event 5. "after" event
-            fire('change', 'any', 'after')
-        except:
-            # 6. "problem" event
-            fire('problem')
-        finally:
-            # 7. event that runs "anyway"
-            return fire('anyway')
-
-    def fire(self, *events):
-        '''
-        run calls bound to `events` **NOW**
-
-        @param *events: event labels
-        '''
-        with self.ctx1(workq=self._WORKVAR):
-            return self.exhaustcall(
-                lambda x: x(), self._xtend(self._events(*events))._iterable,
-            )
-
-    def on(self, event, call, key=False, *args, **kw):
-        '''
-        bind call to `event`
-
-        @param event: event label
-        @param call: label for call or eventspaced thing
-        @param key: key label (default: False)
-        '''
-        self._eventq(event).chain(call, key, *args, **kw)
-        return self
-
-    def off(self, event):
-        '''
-        clear calls bound to `event`
-
-        @param event: event label
-        '''
-        self.E.unset(event)
-        return self
-
-    def trigger(self, *events):
-        '''
-        extend primary call chain with partials bound to `events`
-
-        @param *events: event labels
-        '''
-        self._chain.extend(self._events(*events))
-        return self
-
-    def queues(self, *events):
-        '''
-        ordered mapping of processing queues for `events`
-
-        @param *events: event labels
-        '''
-        return OrderedDict((e, self._eventq(e)) for e in events)

callchain/chain.py

-# -*- coding: utf-8 -*-
-'''root chain mixins'''
-
-import codecs
-from itertools import chain
-
-from appspace.keys import NoAppError
-from stuf.utils import lazy, lazy_class
-
-from callchain.core import CoreMixin
-from callchain.managers import Events
-from callchain.patterns import Pathways
-from callchain.core import ResetLocalMixin
-
-
-class RootMixin(ResetLocalMixin):
-
-    '''root chain mixin'''
-
-    def __init__(self, pattern=None, required=None, defaults=None, **kw):
-        '''
-        init
-
-        @param pattern: pattern configuration or appspace label (default: None)
-        @param required: required settings (default: None)
-        @param defaults: default settings (default: None)
-        '''
-        super(RootMixin, self).__init__(pattern)
-        if pattern is not None:
-            # external appspace
-            self.M = Pathways.appspace(pattern, required, defaults)
-            # freeze external appspace global settings
-            self.M.freeze(kw)
-        else:
-            self.M = None
-
-    def __call__(self, *args):
-        '''new chain session'''
-        # clear call chain and queues and extend incoming things
-        return self.clear().extend(args)
-
-    @lazy_class
-    def port(self):
-        '''python 2.x <-> python 3.x porting helper'''
-        from twoq.support import port
-        return port
-
-    def back(self, branch):
-        '''
-        handle switch from branch chain
-
-        @param branch: branch chain
-        '''
-        self.clear()
-        # sync with branch callable
-        self._call = branch._call
-        # sync with branch postitional arguments
-        self._args = branch._args
-        # sync with branch keyword arguments
-        self._kw = branch._kw
-        # sync with branch incoming and outgoing things
-        return self.extend(branch.incoming).outextend(branch.outgoing)
-
-    def read(self, mode='rb', encoding=None, errors='strict', *paths):
-        '''
-        read incoming things from files
-
-        @param mode: mode to open files (default: 'r')
-        @param encoding: encoding for text data (default: `None`)
-        @param errors: error handling in encoding text data (default: 'strict')
-        @param *paths: sequence of filesystem paths
-        '''
-        with self._context:
-            append = self._append
-            for path in paths:
-                with codecs.open(path, mode, encoding) as f:
-                    append(f.read())
-        return self
-
-    def write(self, mode='rb', encoding=None, errors='strict', *paths):
-        '''
-        write outgoing things to files
-
-        @param mode: mode to open files (default: 'r')
-        @param encoding: encoding for text data (default: `None`)
-        @param errors: error handling in encoding text data (default: 'strict')
-        @param *paths: sequence of filesystem paths
-        '''
-        values = self.value()
-        for idx, path in enumerate(paths):
-            with codecs.open(path, mode, encoding) as f:
-                f.write(values[idx])
-        return self
-
-
-class EventRootMixin(RootMixin):
-
-    '''root event mixin'''
-
-    def __init__(
-        self,
-        patterns=None,
-        events=None,
-        required=None,
-        defaults=None,
-        *args,
-        **kw
-    ):
-        '''
-        init
-
-        @param patterns: pattern config or eventspace label (default: None)
-        @param events: events configuration (default: None)
-        @param required: required settings (default: None)
-        @param defaults: default settings (default: None)
-        '''
-        super(EventRootMixin, self).__init__(
-            patterns, required, defaults, *args, **kw
-        )
-        # update event registry with any other events
-        if events is not None:
-            self.E.update(events)
-
-    def _eventq(self, event):
-        '''
-        linked chain bound to `event`
-
-        @param event: event label
-        '''
-        key = self.E.event(event)
-        # fetch linked chain bound to event
-        queue = self.E.get(key)
-        if queue is None:
-            # create linked chain if nonexistent
-            queue = self._linkedchain
-            self.E.on(key, queue)
-        return queue
-
-    def _event(self, event):
-        '''
-        calls bound to `event`
-
-        @param event: event label
-        '''
-        return self.E.events(self.E.event(event))
-
-    def event(self, event):
-        '''
-        create or fetch `event`
-
-        @param event: event label
-        '''
-        self.E.event(event)
-        return self
-
-    def unevent(self, event):
-        '''
-        drop `event`
-
-        @param event: event label
-        '''
-        self.E.unevent(event)
-        return self
-
-
-class BranchMixin(ResetLocalMixin):
-
-    ''''branch mixin'''
-
-    def _setup(self, root):
-        '''
-        configure branch
-
-        @param root: root chain
-        '''
-        super(BranchMixin, self)._setup(root)
-        # root object
-        self.root = root
-        # root internal appspace manager
-        self._M = root._M
-        # root internal global settings
-        self._G = root._G
-        # root external appspace manager
-        self.M = root.M
-
-
-class EventBranchMixin(BranchMixin):
-
-    '''event branch mixin'''
-
-    @lazy
-    def E(self):
-        '''local event registry'''
-        return Events('events')
-
-    def _eventq(self, event):
-        '''
-        linked chain bound to `event`
-
-        @param event: event label
-        '''
-        # fetch linked chain bound to event
-        key = self.root.event(event)
-        queue = self.E.get(key)
-        if queue is None:
-            # create linked chain if nonexistent
-            queue = self._linkedchain
-            self.E.on(key, queue)
-        return queue
-
-    def _event(self, event):
-        '''
-        calls bound to `event`
-
-        @param event: event label
-        '''
-        key = self.root.event(event)
-        return chain(self.E.events(key), self.root.E.events(key))
-
-
-class BranchletMixin(CoreMixin):
-
-    '''chainlet mixin'''
-
-    def _setup(self, root):
-        '''
-        configure chainlet
-
-        @param root: root chain
-        '''
-        super(BranchletMixin, self)._setup(root)
-        # sync with root postitional arguments
-        self._args = root._args
-        # sync with root keyword arguments
-        self._kw = root._kw
-        # sync with root callable
-        self._call = root._call
-        # sync with root incoming things and outgoing things
-        self.inclear().extend(root.incoming).outextend(root.outgoing)
-
-
-class LinkedMixin(ResetLocalMixin):
-
-    '''linked chain mixin'''
-
-    def close(self):
-        '''close out linked chain and switch to root chain'''
-        return self.root.back(self)
-
-
-class ChainletMixin(ResetLocalMixin):
-
-    '''chainlet mixin'''
-
-    def _load(self, label):
-        '''
-        silent internal switch back...
-
-        @param label: appspaced thing label
-        '''
-        # fetch appspaced thing...
-        try:
-            return super(ChainletMixin, self)._load(label)
-        # ...or revert to root chain
-        except NoAppError:
-            return getattr(self.back(), label)
-
-    def _syncback(self, key, value):
-        '''
-        sync chainlet with root chain
-
-        @param key: key of value
-        @param value: value of value
-        '''
-        self.__dict__[key] = self.root.__dict__[key] = value
-
-    def back(self):
-        '''switch back to root chain'''
-        return self.root.back(self)

callchain/core.py

     @either
     def L(self):
         '''local settings'''
-        return self._M.localize(self) if self._M is not None else frozenstuf()
+        return self.M.localize(self) if self._M is not None else frozenstuf()
 
     def _setup(self, root):
         '''call chain setup'''
 
         @param label: label of appspaced thing
         '''
-        _M = self._M
+        _M = self.M
         try:
             # get appspace thing
             thing = _M.get(label, _M._current)

callchain/descriptors.py

+# -*- coding: utf-8 -*-
+'''context descriptors'''
+
+from inspect import isclass
+from functools import update_wrapper
+
+from stuf.utils import lazybase
+from appspace.keys import appifies
+
+from spine.keys.core import (
+    KDirect, KFactory, KExtend, KCall, KMethod, KRefactory, KReextend, KJoin)
+
+###############################################################################
+## base descriptor classes ####################################################
+###############################################################################
+
+
+class _readonly(lazybase):
+
+    '''read-only descriptor'''
+
+    def __set__(self, this, value):
+        raise AttributeError('attribute is read-only')
+
+    def __delete__(self, this):
+        raise AttributeError('attribute is read-only')
+
+
+class _appbase(_readonly):
+
+    def __init__(self, label, branch=False):
+        '''
+        init
+
+        @param label: application label
+        @param branch: branch label (default: False)
+        '''
+        super(_appbase, self).__init__()
+        self.label = label
+        self.branch = branch
+
+
+class _configurable(_appbase):
+
+    def __init__(self, label, branch=False, *args, **kw):
+        '''
+        init
+
+        @param label: application label
+        @param branch: branch label (default: False)
+        '''
+        super(_configurable, self).__init__(label, branch)
+        # method attributes
+        self.attrs = args
+        # method keywords
+        self.extra = kw
+
+
+class _factory(_configurable):
+
+    def __get__(self, this, that):
+        new_app = this.M.get(self.label, self.branch)
+        if isclass(new_app):
+            # build application
+            new_app = self._factory(new_app, this)
+        return new_app
+
+
+###############################################################################
+## direct invocation descriptor classes #######################################
+###############################################################################
+
+
+@appifies(KDirect)
+class app(_appbase):
+
+    '''passes application from appspace directly to host'''
+
+    def __get__(self, this, that):
+        return this.M.get(self.label, self.branch)
+
+
+@appifies(KJoin)
+class join(_configurable):
+
+    '''
+    pass appspaced application directly to host after assigning current
+    session to it
+    '''
+
+    def __get__(self, this, that):
+        new_app = this.M.get(self.label, self.branch)
+        new_app.session = this.session
+        return new_app
+
+
+@appifies(KCall)
+class call(_configurable):
+
+    '''calls application from appspace when accessed on object/class'''
+
+    def __get__(self, this, that):
+        return this.M.apply(
+            self.label,
+            self.branch,
+            *(getattr(this, attr) for attr in self.attrs),
+            **self.extra
+        )
+
+
+@appifies(KMethod)
+class method(_configurable):
+
+    '''adds method from appspace when accessed on object/class'''
+
+    def __get__(self, this, that):
+        return this.M.partial(
+            self.label,
+            self.branch,
+            *(getattr(this, attr) for attr in self.attrs),
+            **self.extra
+        )
+
+
+###############################################################################
+## factory descriptor classes #################################################
+###############################################################################
+
+
+@appifies(KFactory)
+class factory(_factory):
+
+    '''
+    builds object from appspaced factory once, adds it to appspace, and
+    passes it to host
+    '''
+
+    def _factory(self, new_app, this):
+        new_app = new_app(
+            *(getattr(this, attr) for attr in self.attrs), **self.extra
+        )
+        return this.M.set(new_app, self.label, self.branch)
+
+
+@appifies(KExtend)
+class extend(_factory):
+
+    '''
+    builds object hat extends host functionality from appspaced factory once,
+    adds it to appspace, and passes it to host
+    '''
+
+    def _factory(self, new_app, this):
+        new_app = new_app(
+            this, *(getattr(this, attr) for attr in self.attrs), **self.extra
+        )
+        return this.M.set(new_app, self.label, self.branch)
+
+
+@appifies(KRefactory)
+class refactory(_factory):
+
+    '''builds new object from appspaced factory and passes it to host'''
+
+    def _factory(self, new_app, this):
+        new_app = new_app(
+            *(getattr(this, attr) for attr in self.attrs), **self.extra
+        )
+        return new_app
+
+
+@appifies(KReextend)
+class reextend(_factory):
+
+    '''
+    builds new object from appspaced factory that extends host functionality
+    '''
+
+    def _factory(self, new_app, this):
+        new_app = new_app(
+            this, *(getattr(this, attr) for attr in self.attrs), **self.extra
+        )
+        return new_app
+
+###############################################################################
+## defer descriptor classes ###################################################
+###############################################################################
+
+
+class defer(_readonly):
+
+    '''defer method call'''
+
+    def __init__(self, method):
+        '''
+        init
+
+        @param method: method to defer
+        '''
+        super(defer, self).__init__()
+        self.method = method
+
+    def __get__(self, this, that):
+        return self._factory(that) if this is None else self._factory(this)
+
+    def _factory(self, this):
+        method = self.method
+        def coroutine(*args, **kw): #@IgnorePep8
+            # pack object reference into arguments
+            args = (this,) + args
+            # chain method
+            this.session.chain(method, *args, **kw)
+            return this
+        update_wrapper(coroutine, method)
+        return coroutine

callchain/events.py

+# -*- coding: utf-8 -*-
+'''root chain mixins'''
+
+from itertools import chain
+
+from stuf.utils import OrderedDict, lazy
+
+from callchain.managers import Events
+from callchain.chains import ChainMixin, BranchMixin, RootMixin
+
+
+class EventMixin(ChainMixin):
+
+    '''event chain mixin'''
+
+    @property
+    def _linkedchain(self):
+        '''new linked chain'''
+        return self._M.get('chain', 'event')(self)
+
+    def _events(self, *events):
+        '''calls bound to `events`'''
+        return chain(*tuple(self._imap(self._event, events)))
+
+    def commit(self):
+        '''run event chain'''
+        fire = self.fire
+        try:
+            # 1. "before" event 2. "work" event
+            fire('before', 'work')
+            # everything else
+            super(EventMixin, self).commit()
+            # 3. "change" event 4. "any" event 5. "after" event
+            fire('change', 'any', 'after')
+        except:
+            # 6. "problem" event
+            fire('problem')
+        finally:
+            # 7. event that runs "anyway"
+            return fire('anyway')
+
+    def fire(self, *events):
+        '''
+        run calls bound to `events` **NOW**
+
+        @param *events: event labels
+        '''
+        with self.ctx1(workq=self._WORKVAR):
+            return self.exhaustcall(
+                lambda x: x(), self._xtend(self._events(*events))._iterable,
+            )
+
+    def on(self, event, call, key=False, *args, **kw):
+        '''
+        bind call to `event`
+
+        @param event: event label
+        @param call: label for call or eventspaced thing
+        @param key: key label (default: False)
+        '''
+        self._eventq(event).chain(call, key, *args, **kw)
+        return self
+
+    def off(self, event):
+        '''
+        clear calls bound to `event`
+
+        @param event: event label
+        '''
+        self.E.unset(event)
+        return self
+
+    def trigger(self, *events):
+        '''
+        extend primary call chain with partials bound to `events`
+
+        @param *events: event labels
+        '''
+        self._chain.extend(self._events(*events))
+        return self
+
+    def queues(self, *events):
+        '''
+        ordered mapping of processing queues for `events`
+
+        @param *events: event labels
+        '''
+        return OrderedDict((e, self._eventq(e)) for e in events)
+
+
+class EventRootMixin(RootMixin):
+
+    '''root event mixin'''
+
+    def _eventq(self, event):
+        '''
+        linked chain bound to `event`
+
+        @param event: event label
+        '''
+        key = self.E.event(event)
+        # fetch linked chain bound to event
+        queue = self.E.get(key)
+        if queue is None:
+            # create linked chain if nonexistent
+            queue = self._linkedchain
+            self.E.on(key, queue)
+        return queue
+
+    def _event(self, event):
+        '''
+        calls bound to `event`
+
+        @param event: event label
+        '''
+        return self.E.events(self.E.event(event))
+
+
+class EventBranchMixin(BranchMixin):
+
+    '''event branch mixin'''
+
+    @lazy
+    def E(self):
+        '''local event registry'''
+        return Events('events')
+
+    def _eventq(self, event):
+        '''
+        linked chain bound to `event`
+
+        @param event: event label
+        '''
+        # fetch linked chain bound to event
+        key = self.root.event(event)
+        queue = self.E.get(key)
+        if queue is None:
+            # create linked chain if nonexistent
+            queue = self._linkedchain
+            self.E.on(key, queue)
+        return queue
+
+    def _event(self, event):
+        '''
+        calls bound to `event`
+
+        @param event: event label
+        '''
+        key = self.root.event(event)
+        return chain(self.E.events(key), self.root.E.events(key))

callchain/keys/base.py

     '''required settings key'''
 
 
+class KService(AppspaceKey):
+
+    '''service key'''
+
+
 class NoServiceError(Exception):
 
     '''no service error'''

callchain/keys/core.py

 from appspace.keys import Attribute, AppspaceKey
 
 
-class KService(AppspaceKey):
-
-    '''service key'''
-
-
 class KThings(KService):
 
     '''queuing key'''

callchain/lets.py

+# -*- coding: utf-8 -*-
+'''root chain mixins'''
+
+import codecs
+
+from itertools import count
+from functools import partial
+
+from appspace import NoAppError
+from twoq.support import isstring
+from appspace.builders import Appspace
+from stuf.utils import lazy, lazy_class
+
+from callchain.keys.base import NoServiceError
+from callchain.core import ResetLocalMixin, CoreMixin, ConfigMixin
+from callchain.support import Empty, Queue, PriorityQueue, total_ordering
+
+
+@total_ordering
+class _Partial(object):
+
+    '''partial wrapper'''
+
+    __slots__ = ('_call', 'count', '__call__')
+
+    counter = count()
+
+    def __init__(self, call, *args, **settings):
+        self.__call__ = partial(call, *args, **settings)
+        priority = settings.pop('priority', None)
+        self.count = next(self.counter) if priority is None else priority
+
+    def __lt__(self, other):
+        return self.count < other
+
+
+class RootMixin(ResetLocalMixin):
+
+    '''root chain mixin'''
+
+    def __init__(self, **kw):
+        super(RootMixin, self).__init__()
+        if self.M is not None:
+            # freeze external appspace global settings
+            self.M.freeze(kw)
+        else:
+            self.M = None
+
+    def __call__(self, *args):
+        '''new chain session'''
+        # clear call chain and queues and extend incoming things
+        return self.clear().extend(args)
+
+    @lazy_class
+    def port(self):
+        '''python 2.x <-> python 3.x porting helper'''
+        from twoq.support import port
+        return port
+
+    def back(self, branch):
+        '''
+        handle switch from branch chain
+
+        @param branch: branch chain
+        '''
+        self.clear()
+        # sync with branch callable
+        self._call = branch._call
+        # sync with branch postitional arguments
+        self._args = branch._args
+        # sync with branch keyword arguments
+        self._kw = branch._kw
+        # sync with branch incoming and outgoing things
+        return self.extend(branch.incoming).outextend(branch.outgoing)
+
+    def read(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        read incoming things from files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)
+        @param errors: error handling in encoding text data (default: 'strict')
+        @param *paths: sequence of filesystem paths
+        '''
+        with self._context:
+            append = self._append
+            for path in paths:
+                with codecs.open(path, mode, encoding) as f:
+                    append(f.read())
+        return self
+
+    def write(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        write outgoing things to files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)
+        @param errors: error handling in encoding text data (default: 'strict')
+        @param *paths: sequence of filesystem paths
+        '''
+        values = self.value()
+        for idx, path in enumerate(paths):
+            with codecs.open(path, mode, encoding) as f:
+                f.write(values[idx])
+        return self
+
+    def join(self, *things):
+        '''
+        join things to session
+
+        @param things: some things
+        '''
+        # root internal global settings
+        G = self.G
+        # root external appspace manager
+        M = self.M
+        for thing in things:
+            # add session
+            thing.root = self
+            # root internal global settings
+            thing.G = G
+            # root external appspace manager
+            thing.M = M
+        return self
+
+
+class BranchMixin(ResetLocalMixin):
+
+    ''''branch mixin'''
+
+    def _setup(self, root):
+        '''
+        configure branch
+
+        @param root: root chain
+        '''
+        super(BranchMixin, self)._setup(root)
+        root.join(self)
+
+
+#    def __init__(self, manager, key):
+#        '''
+#        init
+#
+#        @param manager: main spine
+#        @param key: settings key
+#        '''
+#        super(Spinelet, self).__init__(manager)
+#        # add external application appspace
+#        self.M = manager.M
+#        # add internal application appspace
+#        self._M = manager._M
+#        # add internal application settings
+#        self._G = manager._G
+#        # appspace name
+#        self.appspace = self._G.appspace
+#        # namespace name
+#        self.namespace = self._G[key]
+#
+#    def _configure(self, label):
+#        '''
+#        configure appspace class
+#
+#        @param label: application label
+#        '''
+#        app = self._M.get(label, self.appspace)
+#        # add external application appspace
+#        app.M = self.M
+#        # add external application settings
+#        app.G = self.M.settings.final
+#        return app
+#
+#    @property
+#    def Manager(self):
+#        '''configured manager class'''
+#        app = self._M.get(self.appspace).get(self.namespace.manager)
+#        # add external application appspace
+#        app.M = self.M
+#        return app
+#
+#    def __init__(self, manager):
+#        '''
+#        init
+#
+#        @param manager: main spine
+#        '''
+#        super(Descriptor, self).__init__(manager)
+#        # add internal application appspace
+#        self._M = manager._M
+#        # add internal application settings
+#        self._G = manager._G
+#        # appspace name
+#        self.appspace = self._G.appspace
+#        # namespace name
+#        self.namespace = self._G.base
+#
+#    def _get(self, label):
+#        return self._M.get(label, self.appspace)
+
+
+class BranchletMixin(CoreMixin):
+
+    '''chainlet mixin'''
+
+    def _setup(self, root):
+        '''
+        configure chainlet
+
+        @param root: root chain
+        '''
+        super(BranchletMixin, self)._setup(root)
+        # sync with root postitional arguments
+        self._args = root._args
+        # sync with root keyword arguments
+        self._kw = root._kw
+        # sync with root callable
+        self._call = root._call
+        # sync with root incoming things and outgoing things
+        self.inclear().extend(root.incoming).outextend(root.outgoing)
+
+
+class LinkedMixin(ResetLocalMixin):
+
+    '''linked chain mixin'''
+
+    def close(self):
+        '''close out linked chain and switch to root chain'''
+        return self.root.back(self)
+
+
+class ChainletMixin(ResetLocalMixin):
+
+    '''chainlet mixin'''
+
+    def _load(self, label):
+        '''
+        silent internal switch back...
+
+        @param label: appspaced thing label
+        '''
+        # fetch appspaced thing...
+        try:
+            return super(ChainletMixin, self)._load(label)
+        # ...or revert to root chain
+        except NoAppError:
+            return getattr(self.back(), label)
+
+    def _syncback(self, key, value):
+        '''
+        sync chainlet with root chain
+
+        @param key: key of value
+        @param value: value of value
+        '''
+        self.__dict__[key] = self.root.__dict__[key] = value
+
+    def back(self):
+        '''switch back to root chain'''
+        return self.root.back(self)
+
+
+class CallMixin(ConfigMixin):
+
+    '''chain mixin'''
+
+    def __enter__(self):
+        '''enter execution context'''
+        return self
+
+    def __exit__(self, e, t, b):
+        '''exit execution context'''
+        # invoke call chain
+        self.commit()
+
+    def _load(self, label):
+        '''
+        silent internal switch to...
+
+        @param label: label of appspaced thing
+        '''
+        try:
+            # look up internal appspaced linked call chain...
+            _M = self._M
+            key = _M.service(label)
+            return getattr(_M.get(key, key)(self), label)
+        except NoServiceError:
+            # ...or lookup some other appspaced thing
+            return super(CallMixin, self)._load(label)
+
+    @lazy
+    def space(self):
+        '''external appspace interface'''
+        return Appspace(self.M) if self.M is not None else None
+
+    def _setup(self, root):
+        '''call chain setup'''
+        # call chain queue
+        self._chain = self._queue()
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        self._chain.put(
+            _Partial(call, *(key,) + args, **kw) if not isstring(call)
+            else _Partial(self.M.get(call, key), *args, **kw)
+        )
+        return self
+
+    def commit(self):
+        '''consume call chain'''
+        with self.ctx3():
+            return self._xtend(
+                c() for c in self.iterexcept(self._chain.get_nowait, Empty)
+            )
+
+    def switch(self, label, key=False):
+        '''
+        overt switch to linked chain configured in external appspace
+
+        @param label: linked chain label
+        @param key: linked chain chain key (default: False)
+        '''
+        return self.M.get(label, key)(self)
+
+    def tap(self, call, key=False):
+        '''
+        add call
+
+        @param call: callable or appspace label
+        @param key: link call chain key (default: False)
+        '''
+        return super(CallMixin, self).tap(
+            self._M.get(call, key) if isstring(call) else call
+        )
+
+    def wrap(self, call, key=False):
+        '''build current callable from factory'''
+        return super(CallMixin, self).wrap(
+            self._M.get(call, key) if isstring(call) else call
+        )
+
+    class Meta:
+        pass
+
+
+class PriorityMixin(CallMixin):
+
+    '''chain mixin'''
+
+    _queue = PriorityQueue
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        self._chain.put(
+            _Partial(call, *(key,) + args, **kw)
+            if not isstring(call)
+            else _Partial(self.M.get(call, key), *args, **kw)
+        )
+        return self
+
+
+class ChainMixin(CallMixin):
+
+    '''chain mixin'''
+
+    _queue = Queue
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        if not isstring(call):
+            self._chain.put_nowait(self._partial(call, *(key,) + args, **kw))
+        else:
+            self._chain.put_nowait(
+                self._partial(self.M.get(call, key), *args, **kw)
+            )
+        return self

callchain/links.py

+# -*- coding: utf-8 -*-
+'''root chain mixins'''
+
+import codecs
+
+from itertools import count
+from functools import partial
+
+from appspace import NoAppError
+from twoq.support import isstring
+from appspace.builders import Appspace
+from stuf.utils import lazy, lazy_class
+
+from callchain.keys.base import NoServiceError
+from callchain.core import ResetLocalMixin, CoreMixin, ConfigMixin
+from callchain.support import Empty, Queue, PriorityQueue, total_ordering
+
+
+@total_ordering
+class _Partial(object):
+
+    '''partial wrapper'''
+
+    __slots__ = ('_call', 'count', '__call__')
+
+    counter = count()
+
+    def __init__(self, call, *args, **settings):
+        self.__call__ = partial(call, *args, **settings)
+        priority = settings.pop('priority', None)
+        self.count = next(self.counter) if priority is None else priority
+
+    def __lt__(self, other):
+        return self.count < other
+
+
+class RootMixin(ResetLocalMixin):
+
+    '''root chain mixin'''
+
+    def __init__(self, **kw):
+        super(RootMixin, self).__init__()
+        if self.M is not None:
+            # freeze external appspace global settings
+            self.M.freeze(kw)
+        else:
+            self.M = None
+
+    def __call__(self, *args):
+        '''new chain session'''
+        # clear call chain and queues and extend incoming things
+        return self.clear().extend(args)
+
+    @lazy_class
+    def port(self):
+        '''python 2.x <-> python 3.x porting helper'''
+        from twoq.support import port
+        return port
+
+    def back(self, branch):
+        '''
+        handle switch from branch chain
+
+        @param branch: branch chain
+        '''
+        self.clear()
+        # sync with branch callable
+        self._call = branch._call
+        # sync with branch postitional arguments
+        self._args = branch._args
+        # sync with branch keyword arguments
+        self._kw = branch._kw
+        # sync with branch incoming and outgoing things
+        return self.extend(branch.incoming).outextend(branch.outgoing)
+
+    def read(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        read incoming things from files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)
+        @param errors: error handling in encoding text data (default: 'strict')
+        @param *paths: sequence of filesystem paths
+        '''
+        with self._context:
+            append = self._append
+            for path in paths:
+                with codecs.open(path, mode, encoding) as f:
+                    append(f.read())
+        return self
+
+    def write(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        write outgoing things to files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)
+        @param errors: error handling in encoding text data (default: 'strict')
+        @param *paths: sequence of filesystem paths
+        '''
+        values = self.value()
+        for idx, path in enumerate(paths):
+            with codecs.open(path, mode, encoding) as f:
+                f.write(values[idx])
+        return self
+
+    def join(self, *things):
+        '''
+        join things to session
+
+        @param things: some things
+        '''
+        # root internal global settings
+        G = self.G
+        # root external appspace manager
+        M = self.M
+        for thing in things:
+            # add session
+            thing.root = self
+            # root internal global settings
+            thing.G = G
+            # root external appspace manager
+            thing.M = M
+        return self
+
+
+class BranchMixin(ResetLocalMixin):
+
+    ''''branch mixin'''
+
+    def _setup(self, root):
+        '''
+        configure branch
+
+        @param root: root chain
+        '''
+        super(BranchMixin, self)._setup(root)
+        root.join(self)
+
+
+#    def __init__(self, manager, key):
+#        '''
+#        init
+#
+#        @param manager: main spine
+#        @param key: settings key
+#        '''
+#        super(Spinelet, self).__init__(manager)
+#        # add external application appspace
+#        self.M = manager.M
+#        # add internal application appspace
+#        self._M = manager._M
+#        # add internal application settings
+#        self._G = manager._G
+#        # appspace name
+#        self.appspace = self._G.appspace
+#        # namespace name
+#        self.namespace = self._G[key]
+#
+#    def _configure(self, label):
+#        '''
+#        configure appspace class
+#
+#        @param label: application label
+#        '''
+#        app = self._M.get(label, self.appspace)
+#        # add external application appspace
+#        app.M = self.M
+#        # add external application settings
+#        app.G = self.M.settings.final
+#        return app
+#
+#    @property
+#    def Manager(self):
+#        '''configured manager class'''
+#        app = self._M.get(self.appspace).get(self.namespace.manager)
+#        # add external application appspace
+#        app.M = self.M
+#        return app
+#
+#    def __init__(self, manager):
+#        '''
+#        init
+#
+#        @param manager: main spine
+#        '''
+#        super(Descriptor, self).__init__(manager)
+#        # add internal application appspace
+#        self._M = manager._M
+#        # add internal application settings
+#        self._G = manager._G
+#        # appspace name
+#        self.appspace = self._G.appspace
+#        # namespace name
+#        self.namespace = self._G.base
+#
+#    def _get(self, label):
+#        return self._M.get(label, self.appspace)
+
+
+class BranchletMixin(CoreMixin):
+
+    '''chainlet mixin'''
+
+    def _setup(self, root):
+        '''
+        configure chainlet
+
+        @param root: root chain
+        '''
+        super(BranchletMixin, self)._setup(root)
+        # sync with root postitional arguments
+        self._args = root._args
+        # sync with root keyword arguments
+        self._kw = root._kw
+        # sync with root callable
+        self._call = root._call
+        # sync with root incoming things and outgoing things
+        self.inclear().extend(root.incoming).outextend(root.outgoing)
+
+
+class LinkedMixin(ResetLocalMixin):
+
+    '''linked chain mixin'''
+
+    def close(self):
+        '''close out linked chain and switch to root chain'''
+        return self.root.back(self)
+
+
+class ChainletMixin(ResetLocalMixin):
+
+    '''chainlet mixin'''
+
+    def _load(self, label):
+        '''
+        silent internal switch back...
+
+        @param label: appspaced thing label
+        '''
+        # fetch appspaced thing...
+        try:
+            return super(ChainletMixin, self)._load(label)
+        # ...or revert to root chain
+        except NoAppError:
+            return getattr(self.back(), label)
+
+    def _syncback(self, key, value):
+        '''
+        sync chainlet with root chain
+
+        @param key: key of value
+        @param value: value of value
+        '''
+        self.__dict__[key] = self.root.__dict__[key] = value
+
+    def back(self):
+        '''switch back to root chain'''
+        return self.root.back(self)
+
+
+class CallMixin(ConfigMixin):
+
+    '''chain mixin'''
+
+    def __enter__(self):
+        '''enter execution context'''
+        return self
+
+    def __exit__(self, e, t, b):
+        '''exit execution context'''
+        # invoke call chain
+        self.commit()
+
+    def _load(self, label):
+        '''
+        silent internal switch to...
+
+        @param label: label of appspaced thing
+        '''
+        try:
+            # look up internal appspaced linked call chain...
+            _M = self._M
+            key = _M.service(label)
+            return getattr(_M.get(key, key)(self), label)
+        except NoServiceError:
+            # ...or lookup some other appspaced thing
+            return super(CallMixin, self)._load(label)
+
+    @lazy
+    def space(self):
+        '''external appspace interface'''
+        return Appspace(self.M) if self.M is not None else None
+
+    def _setup(self, root):
+        '''call chain setup'''
+        # call chain queue
+        self._chain = self._queue()
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        self._chain.put(
+            _Partial(call, *(key,) + args, **kw) if not isstring(call)
+            else _Partial(self.M.get(call, key), *args, **kw)
+        )
+        return self
+
+    def commit(self):
+        '''consume call chain'''
+        with self.ctx3():
+            return self._xtend(
+                c() for c in self.iterexcept(self._chain.get_nowait, Empty)
+            )
+
+    def switch(self, label, key=False):
+        '''
+        overt switch to linked chain configured in external appspace
+
+        @param label: linked chain label
+        @param key: linked chain chain key (default: False)
+        '''
+        return self.M.get(label, key)(self)
+
+    def tap(self, call, key=False):
+        '''
+        add call
+
+        @param call: callable or appspace label
+        @param key: link call chain key (default: False)
+        '''
+        return super(CallMixin, self).tap(
+            self._M.get(call, key) if isstring(call) else call
+        )
+
+    def wrap(self, call, key=False):
+        '''build current callable from factory'''
+        return super(CallMixin, self).wrap(
+            self._M.get(call, key) if isstring(call) else call
+        )
+
+    class Meta:
+        pass
+
+
+class PriorityMixin(CallMixin):
+
+    '''chain mixin'''
+
+    _queue = PriorityQueue
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        self._chain.put(
+            _Partial(call, *(key,) + args, **kw)
+            if not isstring(call)
+            else _Partial(self.M.get(call, key), *args, **kw)
+        )
+        return self
+
+
+class ChainMixin(CallMixin):
+
+    '''chain mixin'''
+
+    _queue = Queue
+
+    def chain(self, call, key=False, *args, **kw):
+        '''
+        add `call` or appspaced `call` to call chain, partializing it with any
+        passed arguments
+
+        @param call: call or appspaced call label
+        @param key: appspace key (default: False)
+        '''
+        if not isstring(call):
+            self._chain.put_nowait(self._partial(call, *(key,) + args, **kw))
+        else:
+            self._chain.put_nowait(
+                self._partial(self.M.get(call, key), *args, **kw)
+            )
+        return self

callchain/managers.py

 # -*- coding: utf-8 -*-
 '''callchain managers'''
 
-from twoq import twoq
-from stuf import stuf
 from stuf.six import u
 from appspace import Registry
-from appspace.keys import AApp
-from appspace.managers import Manager as _Manager
-from stuf.utils import bi, getcls, lazy, exhaustmap
+from stuf.utils import exhaustmap, selfname
 
-from callchain.settings import Settings
 from callchain.keys.event import EEvent
-from callchain.keys.core import KService
-from callchain.keys.base import KSettings, NoServiceError
+from callchain.patterns import Pathways
+from callchain.core import ResetLocalMixin
 
 
-class Manager(_Manager):
+class inside(object):
 
-    '''chain manager'''
+    '''internal chain configuration'''
 
-    __slots__ = ('_current', '_root', '_key')
+    __slots__ = ('pattern', 'required', 'defaults', 'args', 'kw')
 
-    def __init__(self, label, key=AApp):
+    def __init__(self, pattern, required=None, defaults=None, *args, **kw):
         '''
         init
 
-        @param label: label for appspace
-        @param key: default key for appspace (default: AApp)
+        @param pattern: pattern configuration class or appspace label
+        @param required: required global settings (default: None)
+        @param defaults: default global settings (default: None)
         '''
-        super(Manager, self).__init__(label, key)
-        self.ez_register(KSettings, label, Settings)
+        self.pattern = pattern
+        self.required = required
+        self.defaults = defaults
+        self.args = args
+        self.kw = kw
 
-    @lazy
-    def settings(self):
-        '''appspace settings'''
-        return self.ez_lookup(KSettings, self._root)()
+    def __call__(self, that):
+        # internal appspace manager
+        that._M = Pathways.appspace(
+            self.pattern,
+            self.required,
+            self.defaults,
+            *self.args,
+            **self.kw
+        )
+        # lock internal appspace global settings
+        that._M.settings.lock()
+        # set internal appspace global settings
+        that._G = that._M.settings.final
+        return that
 
-    @bi
-    def localize(self, thing, *args, **kw):
+
+class ManagerMixin(ResetLocalMixin):
+
+    def __init__(self, pattern=None, required=None, defaults=None, **kw):
         '''
-        local settings from thing and its base classes plus any custom settings
+        init
 
-        @param thing: some thing with local settings
+        @param pattern: pattern configuration or appspace label (default: None)
+        @param required: required settings (default: None)
+        @param defaults: default settings (default: None)
         '''
-        return (twoq([type.mro(getcls(thing)), [thing]]).smash().pick('Meta')
-        .members().tap(lambda x: not x[0].startswith('__')).filter()
-        .reup().wrap(stuf).map().invoke('update', *args, **kw).value())
+        super(ManagerMixin, self).__init__(pattern)
+        if pattern is not None:
+            # external appspace
+            self.M = Pathways.appspace(pattern, required, defaults)
+            # freeze external appspace global settings
+            self.M.freeze(kw)
+        else:
+            self.M = None
 
-    def freeze(self, *args, **kw):
-        '''finalize settings, adding any passed settings'''
-        self.settings.update(*args, **kw)
-        self.settings.lock()
+    def register(self, thing):
+        '''
+        register some thing in userspace
 
-    def service(self, label):
+        @param thing: some thing
         '''
-        fetch service
-
-        @param label: service label
-        '''
-        service = self.lookup1(KService, KService, label)
-        if service is None:
-            raise NoServiceError(label)
-        return service
+        self.M.set(thing, selfname(thing), self.G.userspace)
 
 
 class Events(Registry):
         @param labels: eventconf
         '''
         exhaustmap(vars(labels), self.pack, lambda x: not x[0].startswith('_'))
+
+
+class einside(inside):
+
+    '''internal event chain configuration'''
+
+    __slots__ = ('pattern', 'required', 'defaults', 'args', 'kw', 'events')
+
+    def __init__(
+        self, patterns, events=None, required=None, defaults=None, *args, **kw
+    ):
+        '''
+        init
+
+        @param patterns: pattern config or appspace label (default: None)
+        @param events: events configuration (default: None)
+        @param required: required settings (default: None)
+        @param defaults: default settings (default: None)
+        '''
+        super(einside, self).__init__(
+            patterns, required, defaults, *args, **kw
+        )
+        self.events = events
+
+    def __call__(self, that):
+        that = super(einside, self).__call__(that)
+        that.E = Events('events')
+        that.E.update(self.events)
+        return that
+
+
+class EventManager(ManagerMixin):
+
+    def __init__(
+        self,
+        patterns=None,
+        events=None,
+        required=None,
+        defaults=None,
+        *args,
+        **kw
+    ):
+        '''
+        init
+
+        @param patterns: pattern config or eventspace label (default: None)
+        @param events: events configuration (default: None)
+        @param required: required settings (default: None)
+        @param defaults: default settings (default: None)
+        '''
+        super(EventManager, self).__init__(
+            patterns, required, defaults, *args, **kw
+        )
+        # update event registry with any other events
+        if events is not None:
+            self.E.update(events)
+
+    def event(self, event):
+        '''
+        create or fetch `event`
+
+        @param event: event label
+        '''
+        self.E.event(event)
+        return self
+
+    def unevent(self, event):
+        '''
+        drop `event`
+
+        @param event: event label
+        '''
+        self.E.unevent(event)
+        return self

callchain/patterns.py

 # -*- coding: utf-8 -*-
 '''callchain patterns'''
 
-from twoq import port
+from stuf import stuf
+from twoq import port, twoq
 from stuf.six import strings
-from stuf.utils import exhaust, imap
+from appspace.keys import AApp
 from appspace.utils import lazyimport
+from appspace import Manager as _Manager
+from stuf.utils import exhaust, imap, bi, getcls, lazy
 from appspace.keys import ConfigurationError, ANamespace
-from appspace.spaces import Branch, Namespace, Patterns, patterns
+from appspace import Branch, Namespace, Patterns, patterns
 
-from callchain.managers import Manager
-from callchain.keys.core import KService
+from callchain.settings import Settings
+from callchain.keys.base import KSettings, NoServiceError, KService
+
+
+class _PatternsMixin(object):
+
+    @classmethod
+    def _key(cls, label, manager):
+        try:
+            # lazily load key
+            key = cls.key
+            if isinstance(key, strings):
+                key = lazyimport(key)
+            # register class key
+            ez_register = manager.ez_register
+            ez_register(ANamespace, label, key)
+            exhaust(imap(
+                lambda x: ez_register(KService, x, label),
+                iter(key.names(True)),
+            ))
+        except AttributeError:
+            key = manager.key(ANamespace, label)
+
+
+class Manager(_Manager):
+
+    '''chain manager'''
+
+    __slots__ = ('_current', '_root', '_key')
+
+    def __init__(self, label, key=AApp):
+        '''
+        init
+
+        @param label: label for appspace
+        @param key: default key for appspace (default: AApp)
+        '''
+        super(Manager, self).__init__(label, key)
+        self.ez_register(KSettings, label, Settings)
+
+    @lazy
+    def settings(self):
+        '''appspace settings'''
+        return self.ez_lookup(KSettings, self._root)()
+
+    @bi
+    def localize(self, thing, *args, **kw):
+        '''
+        local settings from thing and its base classes plus any custom settings
+
+        @param thing: some thing with local settings
+        '''
+        return (twoq([type.mro(getcls(thing)), [thing]]).smash().pick('Meta')
+        .members().tap(lambda x: not x[0].startswith('__')).filter()
+        .reup().wrap(stuf).map().invoke('update', *args, **kw).value())
+
+    def freeze(self, *args, **kw):
+        '''finalize settings, adding any passed settings'''
+        self.settings.update(*args, **kw)
+        self.settings.lock()
+
+    def service(self, label):
+        '''
+        fetch service
+
+        @param label: service label
+        '''
+        service = self.lookup1(KService, KService, label)
+        if service is None:
+            raise NoServiceError(label)
+        return service
 
 
 class Pathways(Patterns):
             manager.settings.defaults = defaults
 
 
-class _PatternsMixin(object):
-
-    @classmethod
-    def _key(cls, label, manager):
-        try:
-            # lazily load key
-            key = cls.key
-            if isinstance(key, strings):
-                key = lazyimport(key)
-            # register class key
-            ez_register = manager.ez_register
-            ez_register(ANamespace, label, key)
-            exhaust(imap(
-                lambda x: ez_register(KService, x, label),
-                iter(key.names(True)),
-            ))
-        except AttributeError:
-            key = manager.key(ANamespace, label)
-
-
 class Branchways(_PatternsMixin, Branch):
 
     '''branch ways'''

callchain/roots.py

+# -*- coding: utf-8 -*-
+'''root chain mixins'''
+
+import codecs
+
+from itertools import count
+from functools import partial
+
+from appspace import NoAppError
+from twoq.support import isstring
+from appspace.builders import Appspace
+from stuf.utils import lazy, lazy_class
+
+from callchain.keys.base import NoServiceError
+from callchain.core import ResetLocalMixin, CoreMixin, ConfigMixin
+from callchain.support import Empty, Queue, PriorityQueue, total_ordering
+
+
+@total_ordering
+class _Partial(object):
+
+    '''partial wrapper'''
+
+    __slots__ = ('_call', 'count', '__call__')
+
+    counter = count()
+
+    def __init__(self, call, *args, **settings):
+        self.__call__ = partial(call, *args, **settings)
+        priority = settings.pop('priority', None)
+        self.count = next(self.counter) if priority is None else priority
+
+    def __lt__(self, other):
+        return self.count < other
+
+
+class RootMixin(ResetLocalMixin):
+
+    '''root chain mixin'''
+
+    def __init__(self, **kw):
+        super(RootMixin, self).__init__()
+        if self.M is not None:
+            # freeze external appspace global settings
+            self.M.freeze(kw)
+        else:
+            self.M = None
+
+    def __call__(self, *args):
+        '''new chain session'''
+        # clear call chain and queues and extend incoming things
+        return self.clear().extend(args)
+
+    @lazy_class
+    def port(self):
+        '''python 2.x <-> python 3.x porting helper'''
+        from twoq.support import port
+        return port
+
+    def back(self, branch):
+        '''
+        handle switch from branch chain
+
+        @param branch: branch chain
+        '''
+        self.clear()
+        # sync with branch callable
+        self._call = branch._call
+        # sync with branch postitional arguments
+        self._args = branch._args
+        # sync with branch keyword arguments
+        self._kw = branch._kw
+        # sync with branch incoming and outgoing things
+        return self.extend(branch.incoming).outextend(branch.outgoing)
+
+    def read(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        read incoming things from files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)
+        @param errors: error handling in encoding text data (default: 'strict')
+        @param *paths: sequence of filesystem paths
+        '''
+        with self._context:
+            append = self._append
+            for path in paths:
+                with codecs.open(path, mode, encoding) as f:
+                    append(f.read())
+        return self
+
+    def write(self, mode='rb', encoding=None, errors='strict', *paths):
+        '''
+        write outgoing things to files
+
+        @param mode: mode to open files (default: 'r')
+        @param encoding: encoding for text data (default: `None`)