Commits

Lynn Rees  committed 91164f3

- convention

  • Participants
  • Parent commits b2f0db8
  • Branches pu

Comments (0)

Files changed (33)

File callchain/chainlet/base.py

 from appspace import NoAppError
 
 from callchain.base import ResetLocalMixin, CoreMixin
-from callchain.mixins import BranchMixin, EventBranchMixin
+from callchain.mixin import BranchMixin, EventBranchMixin
 
 
 class ThingletMixin(CoreMixin):

File callchain/chainlet/keys.py

 
 from appspace import Attribute, AppLookupError
 
-from callchain.keys.mixin import KBranch
+from callchain.key.mixin import KBranch
 
 
 class NoService(AppLookupError):

File callchain/chainlet/registry.py

 from stuf.six import u
 
 from appspace.registry import Registry
-from callchain.keys.event import EEvent
+from callchain.key.event import EEvent
 
 
 class EventRegistry(Registry):

File callchain/config.py

+# -*- coding: utf-8 -*-
+'''callchain settings management'''
+
+from appspace import key
+
+from stuf import frozenstuf
+from appspace.utils import object_walk
+from stuf.utils import deepget, lazy_set, setter
+
+from callchain.base import ResetLocalMixin
+from callchain.key.config import (
+    KDefaults, KInternalRequired, KExternalRequired, KRequired,
+    KSettingsManager, KInternalDefaults, KExternalDefaults)
+
+
+class lock_set(lazy_set):
+
+    '''lazily assign attributes with a custom setter'''
+
+    def __get__(self, this, that):
+        if this is None:
+            return self
+        # check if settings are locked
+        if this._locked:
+            return setter(this, self.name, self.method(this))
+        return self.method(this)
+
+
+@key(KSettingsManager)
+class Settings(ResetLocalMixin):
+
+    '''settings management'''
+
+    def __init__(self):
+        super(Settings, self).__init__()
+        # default settings
+        self._default = dict()
+        # end settings
+        self._final = dict()
+        # required settings
+        self._required = dict()
+        # turn off lock initially
+        self._locked = False
+
+    def __repr__(self, *args, **kwargs):
+        return str(self._final)
+
+    def _update_default(self, settings):
+        '''
+        update default settings
+
+        @param settings: new settings
+        '''
+        if KDefaults.implementedBy(settings):
+            self._default.update(object_walk(settings))
+        else:
+            raise TypeError('invalid default settings')
+
+    def _update_required(self, settings):
+        '''
+        update required settings
+
+        @param settings: new settings
+        '''
+        if KRequired.implementedBy(settings):
+            self._required.update(object_walk(settings))
+        else:
+            raise TypeError('invalid required settings')
+
+    @lock_set
+    def defaults(self):
+        '''get default settings separately'''
+        return frozenstuf(self._default)
+
+    @defaults.setter
+    def defaults(self, value):
+        '''
+        set default settings separately
+
+        @param value: default settings
+        '''
+        if value is not None:
+            if KDefaults.implementedBy(value):
+                self._default.clear()
+                self._update_default(value)
+            else:
+                raise TypeError('invalid default settings')
+
+    @lock_set
+    def final(self):
+        '''finalized settings'''
+        end = self._default.copy()
+        end.update(self._final.copy())
+        end.update(self._required.copy())
+        return frozenstuf(end)
+
+    @lock_set
+    def required(self):
+        '''get required settings separately'''
+        return frozenstuf(self._required)
+
+    @required.setter
+    def required(self, value):
+        '''
+        set required settings separately
+
+        @param value: required settings
+        '''
+        if KRequired.implementedBy(value):
+            self._required.clear()
+            self._update_required(value)
+        else:
+            raise TypeError('invalid required settings')
+
+    def get(self, key, default=None):
+        '''
+        value from settings
+
+        @param key: key in settings
+        @param default: default value (default: None)
+        '''
+        return deepget(self._final, key, default)
+
+    def freeze(self, *args, **kw):
+        '''finalize settings, adding any passed settings'''
+        self._final.update(*args, **kw)
+        self._locked = True
+
+
+@key(KInternalDefaults)
+class InternalDefaultSettings(object):
+
+    '''internal default settings'''
+
+    userspace = 'appspace'
+
+
+@key(KExternalDefaults)
+class ExternalDefaultSettings(object):
+
+    '''external default settings'''
+
+    userspace = 'appspace'
+
+
+@key(KInternalRequired)
+class InternalRequiredSettings(object):
+
+    '''internal required settings'''
+
+    appspace = userspace = 'appspace'
+
+
+@key(KExternalRequired)
+class ExternalRequiredSettings(object):
+
+    '''external required settings'''
+
+    appspace = userspace = 'appspace'

File 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 key
-
-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 #######################################
-###############################################################################
-
-
-@key(KDirect)
-class app(_appbase):
-
-    '''passes application from appspace directly to host'''
-
-    def __get__(self, this, that):
-        return this.M.get(self.label, self.branch)
-
-
-@key(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
-
-
-@key(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
-        )
-
-
-@key(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 #################################################
-###############################################################################
-
-
-@key(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)
-
-
-@key(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)
-
-
-@key(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
-
-
-@key(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

File callchain/key/__init__.py

+# -*- coding: utf-8 -*-
+'''callchain keys'''

File callchain/key/base.py

+# -*- coding: utf-8 -*-
+'''callchain reset keys'''
+
+from appspace.keys import AppspaceKey, Attribute
+
+
+class KRoutes(AppspaceKey):
+
+    '''routes key'''
+
+
+class KExternalRoutes(KRoutes):
+
+    '''external routes key'''
+
+
+class KInternalRoutes(KRoutes):
+
+    '''internal routes key'''
+
+
+class KEventRegistry(AppspaceKey):
+
+    '''events registry key'''
+
+
+class KResetType(AppspaceKey):
+
+    '''reset type key'''
+
+    def reset():  # @NoSelf
+        '''reset previously accessed `lazybase` attributes'''
+
+
+class KResetLocal(KResetType):
+
+    '''reset thread local key'''
+
+
+class KCore(KResetLocal):
+
+    '''core key'''
+
+    G = Attribute('external application global settings')
+
+    def __init__(root):  # @NoSelf
+        '''
+        init
+
+        @param root: root chain
+        '''
+
+
+class KConfig(KCore):
+
+    '''configuration access key'''
+
+    defaults = Attribute('default settings by their lonesome')
+    required = Attribute('required settings by their lonesome')

File callchain/key/config.py

+# -*- coding: utf-8 -*-
+'''callchain settings keys'''
+
+from appspace.keys import ANamespace
+
+
+class KSettings(ANamespace):
+
+    '''settings key'''
+
+
+class KDefaults(KSettings):
+
+    '''default settings key'''
+
+
+class KInternalDefaults(KDefaults):
+
+    '''internal default settings key'''
+
+
+class KExternalDefaults(KDefaults):
+
+    '''external required settings key'''
+
+
+class KRequired(KSettings):
+
+    '''required settings key'''
+
+
+class KInternalRequired(KRequired):
+
+    '''internal required settings key'''
+
+
+class KExternalRequired(KRequired):
+
+    '''external required settings key'''
+
+
+class KSettingsManager(ANamespace):
+
+    '''settings manager key'''
+
+
+class KExternalSettings(KSettingsManager):
+
+    '''external settings manager key'''
+
+
+class KInternalSettings(KSettingsManager):
+
+    '''internal settings manager key'''

File callchain/key/event.py

+# -*- coding: utf-8 -*-
+'''event keys'''
+
+from appspace.keys import AppspaceKey
+
+
+class EEvent(AppspaceKey):
+
+    '''event key'''
+
+
+class EBefore(EEvent):
+
+    '''before event key'''
+
+
+class EWork(EEvent):
+
+    '''work event key'''
+
+
+class EChange(EEvent):
+
+    '''change event key'''
+
+
+class EAny(EEvent):
+
+    '''any events key'''
+
+
+class EAfter(EEvent):
+
+    '''after event key'''
+
+
+class EProblem(EEvent):
+
+    '''problem event key'''
+
+
+class EAnyway(EEvent):
+
+    '''run anyway event key'''

File callchain/key/mixin.py

+# -*- coding: utf-8 -*-
+'''callchain reset keys'''
+
+from appspace.keys import Attribute
+
+from callchain.key.base import KResetLocal, KCore
+
+
+class KChain(KResetLocal):
+
+    '''chain key'''
+
+    def chain(call, key=False, *args, **kw):  # @NoSelf
+        '''
+        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)
+        '''
+
+    def clear():  # @NoSelf
+        '''clear things'''
+
+    def tap(call, key=False):  # @NoSelf
+        '''
+        add call
+
+        @param call: callable or appspace label
+        @param key: link call chain key (default: False)
+        '''
+
+    def wrap(call, key=False):  # @NoSelf
+        '''build current callable from factory'''
+
+
+class KEvent(KChain):
+
+    '''event chain key'''
+
+    def on(event, call, key=False, *args, **kw):  # @NoSelf
+        '''
+        bind call to `event`
+
+        @param event: event label
+        @param call: label for call or eventspaced thing
+        @param key: key label (default: False)
+        '''
+
+    def off(event):  # @NoSelf
+        '''
+        clear calls bound to `event`
+
+        @param event: event label
+        '''
+
+    def trigger(*events):  # @NoSelf
+        '''
+        extend primary call chain with partials bound to `events`
+
+        @param *events: event labels
+        '''
+
+
+class KCall(KResetLocal):
+
+    '''call key'''
+
+    L = Attribute('local settings extracted')
+    Meta = Attribute('local settings')
+    port = Attribute('python 2.x <-> python 3.x porting helper')
+    space = Attribute('external appspace interface')
+
+    def __enter__():  # @NoSelf
+        '''enter execution context'''
+
+    def __exit__(e, t, b):  # @NoSelf
+        '''exit execution context'''
+
+    def switch(label, key=False):  # @NoSelf
+        '''
+        overt switch to linked chain configured in external appspace
+
+        @param label: linked chain label
+        @param key: linked chain chain key (default: False)
+        '''
+
+    def commit():  # @NoSelf
+        '''consume call chain'''
+
+
+class KEventCall(KCall):
+
+    '''event call key'''
+
+    def fire(*events):  # @NoSelf
+        '''
+        run calls bound to `events` **NOW**
+
+        @param *events: event labels
+        '''
+
+    def queues(*events):  # @NoSelf
+        '''
+        ordered mapping of processing queues for `events`
+
+        @param *events: event labels
+        '''
+
+
+class KBranch(KCore):
+
+    ''''branch key'''
+
+    G = Attribute('root external global settings')
+    M = Attribute('root external appspace manager')
+    _G = Attribute('root internal global settings')
+    _M = Attribute('root internal appspace manager')
+    root = Attribute('root object')
+
+
+class KEventBranch(KBranch):
+
+    '''event branch key'''
+
+    E = Attribute('local event registry')

File callchain/key/queue.py

+# -*- coding: utf-8 -*-
+'''callchain queue keys'''
+
+from appspace.keys import Attribute, AppspaceKey
+
+
+class KThings(AppspaceKey):
+
+    '''queuing key'''
+
+    incoming = Attribute('incoming queue')
+    outgoing = Attribute('outgoing queue')
+    balanced = Attribute('if incoming and outgoing things are balanced')
+
+    def __len__():  # @NoSelf
+        '''number of incoming things'''
+
+    def outcount():  # @NoSelf
+        '''number of outgoing things'''
+
+    ###########################################################################
+    ## queue clearance ########################################################
+    ###########################################################################
+
+    def clear():  # @NoSelf
+        '''clear every thing'''
+
+    def inclear():  # @NoSelf
+        '''clear incoming things'''
+
+    def outclear():  # @NoSelf
+        '''clear outgoing things'''
+
+    ###########################################################################
+    ## context rotation #######################################################
+    ###########################################################################
+
+    def ctx1(**kw):  # @NoSelf
+        '''swap to one-armed context'''
+
+    def ctx2(**kw):  # @NoSelf
+        '''swap to two-armed context'''
+
+    def ctx3(**kw):  # @NoSelf
+        '''swap to three-armed context'''
+
+    def ctx4(**kw):  # @NoSelf
+        '''swap to four-armed context'''
+
+    def autoctx(**kw):  # @NoSelf
+        '''swap to auto-synchronizing context'''
+
+    def ro():  # @NoSelf
+        '''swap to read-only context'''
+
+    def swap(**kw):  # @NoSelf
+        '''swap contexts'''
+
+    def reswap(self):  # @NoSelfs
+        '''swap to current preferred context'''
+
+    def unswap():  # @NoSelf
+        '''swap to default context'''
+
+    def rw():  # @NoSelf
+        '''swap to default context'''
+
+    ###########################################################################
+    ## current callable management ############################################
+    ###########################################################################
+
+    def args(*args, **kw):  # @NoSelf
+        '''arguments for current callable'''
+
+    def detap():  # @NoSelf
+        '''clear current callable'''
+
+    def wrap(call):  # @NoSelf
+        '''
+        build current callable from factory
+
+        @param call: a callable
+        '''
+
+    def unwrap():  # @NoSelf
+        '''clear factory'''
+
+    ###########################################################################
+    ## things rotation ########################################################
+    ###########################################################################
+
+    def outshift():  # @NoSelf
+        '''shift incoming things to outgoing things'''
+
+    def outsync():  # @NoSelf
+        '''
+        shift incoming things to outgoing things, clearing outgoing things
+        '''
+
+    def reup():  # @NoSelf
+        '''put incoming things in incoming things as one incoming thing'''
+
+    def shift():  # @NoSelf
+        '''shift outgoing things to incoming things'''
+
+    def sync():  # @NoSelf
+        '''
+        shift outgoing things to incoming things, clearing incoming things
+        '''
+
+    ###########################################################################
+    ## things appending #######################################################
+    ###########################################################################
+
+    def append(thing):  # @NoSelf
+        '''
+        append `thing` to right side of incoming things
+
+        @param thing: some thing
+        '''
+
+    def appendleft(thing):  # @NoSelf
+        '''
+        append `thing` to left side of incoming things
+
+        @param thing: some thing
+        '''
+
+    ###########################################################################
+    ## things extension #######################################################
+    ###########################################################################
+
+    def extend(things):  # @NoSelf
+        '''
+        extend right side of incoming things with `things`
+
+        @param thing: some things
+        '''
+
+    def extendleft(things):  # @NoSelf
+        '''
+        extend left side of incoming things with `things`
+
+        @param thing: some things
+        '''
+
+    def outextend(things):  # @NoSelf
+        '''
+        extend right side of outgoing things with `things`
+
+        @param thing: some things
+        '''
+
+    ###########################################################################
+    ## iteration runners ######################################################
+    ###########################################################################
+
+    def __iter__():  # @NoSelf
+        '''yield outgoing things, clearing outgoing things as it iterates'''
+
+    def breakcount(call, length, exception=StopIteration):  # @NoSelf
+        '''
+        rotate through iterator until it reaches its original length
+
+        @param iterable: an iterable to exhaust
+        '''
+
+    def exhaust(iterable, exception=StopIteration):  # @NoSelf
+        '''
+        call next on an iterator until it's exhausted
+
+        @param iterable: iterable to exhaust
+        @param exception: exception marking end of iteration
+        '''
+
+    def exhaustmap(map, call, filter=False, exception=StopIteration):  # @NoSelf @IgnorePep8
+        '''
+        call `next` on an iterator until it's exhausted
+
+        @param mapping: a mapping to exhaust
+        @param call: call to handle what survives the filter
+        @param filter: a filter to apply to mapping (default: `None`)
+        @param exception: exception sentinel (default: `StopIteration`)
+        '''
+
+    def iterexcept(call, exception, start=None):  # @NoSelf
+        '''
+        call a function repeatedly until an exception is raised
+
+        Converts a call-until-exception interface to an iterator interface.
+        Like `iter(call, sentinel)` but uses an exception instead of a sentinel
+        to end the loop.
+
+        Raymond Hettinger, Python Cookbook recipe # 577155
+        '''
+
+
+class KResult(KThings):
+
+    def end():  # @NoSelf
+        '''return outgoing things then clear out everything'''
+
+    def first():  # @NoSelf
+        '''first incoming thing'''
+
+    def last():  # @NoSelf
+        '''last incoming thing'''
+
+    def peek():  # @NoSelf
+        '''results from read-only context'''
+
+    def results():  # @NoSelf
+        '''yield outgoing things, clearing outgoing things as it iterates'''
+
+    def value():  # @NoSelf
+        '''return outgoing things and clear outgoing things'''

File callchain/keys/__init__.py

-# -*- coding: utf-8 -*-
-'''callchain keys'''

File callchain/keys/base.py

-# -*- coding: utf-8 -*-
-'''callchain reset keys'''
-
-from appspace.keys import AppspaceKey, Attribute
-
-
-class KRoutes(AppspaceKey):
-
-    '''routes key'''
-
-
-class KExternalRoutes(KRoutes):
-
-    '''external routes key'''
-
-
-class KInternalRoutes(KRoutes):
-
-    '''internal routes key'''
-
-
-class KEventRegistry(AppspaceKey):
-
-    '''events registry key'''
-
-
-class KResetType(AppspaceKey):
-
-    '''reset type key'''
-
-    def reset():  # @NoSelf
-        '''reset previously accessed `lazybase` attributes'''
-
-
-class KResetLocal(KResetType):
-
-    '''reset thread local key'''
-
-
-class KCore(KResetLocal):
-
-    '''core key'''
-
-    G = Attribute('external application global settings')
-
-    def __init__(root):  # @NoSelf
-        '''
-        init
-
-        @param root: root chain
-        '''
-
-
-class KConfig(KCore):
-
-    '''configuration access key'''
-
-    defaults = Attribute('default settings by their lonesome')
-    required = Attribute('required settings by their lonesome')

File callchain/keys/event.py

-# -*- coding: utf-8 -*-
-'''event keys'''
-
-from appspace.keys import AppspaceKey
-
-
-class EEvent(AppspaceKey):
-
-    '''event key'''
-
-
-class EBefore(EEvent):
-
-    '''before event key'''
-
-
-class EWork(EEvent):
-
-    '''work event key'''
-
-
-class EChange(EEvent):
-
-    '''change event key'''
-
-
-class EAny(EEvent):
-
-    '''any events key'''
-
-
-class EAfter(EEvent):
-
-    '''after event key'''
-
-
-class EProblem(EEvent):
-
-    '''problem event key'''
-
-
-class EAnyway(EEvent):
-
-    '''run anyway event key'''

File callchain/keys/mixin.py

-# -*- coding: utf-8 -*-
-'''callchain reset keys'''
-
-from appspace.keys import Attribute
-
-from callchain.keys.base import KResetLocal, KCore
-
-
-class KChain(KResetLocal):
-
-    '''chain key'''
-
-    def chain(call, key=False, *args, **kw):  # @NoSelf
-        '''
-        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)
-        '''
-
-    def clear():  # @NoSelf
-        '''clear things'''
-
-    def tap(call, key=False):  # @NoSelf
-        '''
-        add call
-
-        @param call: callable or appspace label
-        @param key: link call chain key (default: False)
-        '''
-
-    def wrap(call, key=False):  # @NoSelf
-        '''build current callable from factory'''
-
-
-class KEvent(KChain):
-
-    '''event chain key'''
-
-    def on(event, call, key=False, *args, **kw):  # @NoSelf
-        '''
-        bind call to `event`
-
-        @param event: event label
-        @param call: label for call or eventspaced thing
-        @param key: key label (default: False)
-        '''
-
-    def off(event):  # @NoSelf
-        '''
-        clear calls bound to `event`
-
-        @param event: event label
-        '''
-
-    def trigger(*events):  # @NoSelf
-        '''
-        extend primary call chain with partials bound to `events`
-
-        @param *events: event labels
-        '''
-
-
-class KCall(KResetLocal):
-
-    '''call key'''
-
-    L = Attribute('local settings extracted')
-    Meta = Attribute('local settings')
-    port = Attribute('python 2.x <-> python 3.x porting helper')
-    space = Attribute('external appspace interface')
-
-    def __enter__():  # @NoSelf
-        '''enter execution context'''
-
-    def __exit__(e, t, b):  # @NoSelf
-        '''exit execution context'''
-
-    def switch(label, key=False):  # @NoSelf
-        '''
-        overt switch to linked chain configured in external appspace
-
-        @param label: linked chain label
-        @param key: linked chain chain key (default: False)
-        '''
-
-    def commit():  # @NoSelf
-        '''consume call chain'''
-
-
-class KEventCall(KCall):
-
-    '''event call key'''
-
-    def fire(*events):  # @NoSelf
-        '''
-        run calls bound to `events` **NOW**
-
-        @param *events: event labels
-        '''
-
-    def queues(*events):  # @NoSelf
-        '''
-        ordered mapping of processing queues for `events`
-
-        @param *events: event labels
-        '''
-
-
-class KBranch(KCore):
-
-    ''''branch key'''
-
-    G = Attribute('root external global settings')
-    M = Attribute('root external appspace manager')
-    _G = Attribute('root internal global settings')
-    _M = Attribute('root internal appspace manager')
-    root = Attribute('root object')
-
-
-class KEventBranch(KBranch):
-
-    '''event branch key'''
-
-    E = Attribute('local event registry')

File callchain/keys/queue.py

-# -*- coding: utf-8 -*-
-'''callchain queue keys'''
-
-from appspace.keys import Attribute, AppspaceKey
-
-
-class KThings(AppspaceKey):
-
-    '''queuing key'''
-
-    incoming = Attribute('incoming queue')
-    outgoing = Attribute('outgoing queue')
-    balanced = Attribute('if incoming and outgoing things are balanced')
-
-    def __len__():  # @NoSelf
-        '''number of incoming things'''
-
-    def outcount():  # @NoSelf
-        '''number of outgoing things'''
-
-    ###########################################################################
-    ## queue clearance ########################################################
-    ###########################################################################
-
-    def clear():  # @NoSelf
-        '''clear every thing'''
-
-    def inclear():  # @NoSelf
-        '''clear incoming things'''
-
-    def outclear():  # @NoSelf
-        '''clear outgoing things'''
-
-    ###########################################################################
-    ## context rotation #######################################################
-    ###########################################################################
-
-    def ctx1(**kw):  # @NoSelf
-        '''swap to one-armed context'''
-
-    def ctx2(**kw):  # @NoSelf
-        '''swap to two-armed context'''
-
-    def ctx3(**kw):  # @NoSelf
-        '''swap to three-armed context'''
-
-    def ctx4(**kw):  # @NoSelf
-        '''swap to four-armed context'''
-
-    def autoctx(**kw):  # @NoSelf
-        '''swap to auto-synchronizing context'''
-
-    def ro():  # @NoSelf
-        '''swap to read-only context'''
-
-    def swap(**kw):  # @NoSelf
-        '''swap contexts'''
-
-    def reswap(self):  # @NoSelfs
-        '''swap to current preferred context'''
-
-    def unswap():  # @NoSelf
-        '''swap to default context'''
-
-    def rw():  # @NoSelf
-        '''swap to default context'''
-
-    ###########################################################################
-    ## current callable management ############################################
-    ###########################################################################
-
-    def args(*args, **kw):  # @NoSelf
-        '''arguments for current callable'''
-
-    def detap():  # @NoSelf
-        '''clear current callable'''
-
-    def wrap(call):  # @NoSelf
-        '''
-        build current callable from factory
-
-        @param call: a callable
-        '''
-
-    def unwrap():  # @NoSelf
-        '''clear factory'''
-
-    ###########################################################################
-    ## things rotation ########################################################
-    ###########################################################################
-
-    def outshift():  # @NoSelf
-        '''shift incoming things to outgoing things'''
-
-    def outsync():  # @NoSelf
-        '''
-        shift incoming things to outgoing things, clearing outgoing things
-        '''
-
-    def reup():  # @NoSelf
-        '''put incoming things in incoming things as one incoming thing'''
-
-    def shift():  # @NoSelf
-        '''shift outgoing things to incoming things'''
-
-    def sync():  # @NoSelf
-        '''
-        shift outgoing things to incoming things, clearing incoming things
-        '''
-
-    ###########################################################################
-    ## things appending #######################################################
-    ###########################################################################
-
-    def append(thing):  # @NoSelf
-        '''
-        append `thing` to right side of incoming things
-
-        @param thing: some thing
-        '''
-
-    def appendleft(thing):  # @NoSelf
-        '''
-        append `thing` to left side of incoming things
-
-        @param thing: some thing
-        '''
-
-    ###########################################################################
-    ## things extension #######################################################
-    ###########################################################################
-
-    def extend(things):  # @NoSelf
-        '''
-        extend right side of incoming things with `things`
-
-        @param thing: some things
-        '''
-
-    def extendleft(things):  # @NoSelf
-        '''
-        extend left side of incoming things with `things`
-
-        @param thing: some things
-        '''
-
-    def outextend(things):  # @NoSelf
-        '''
-        extend right side of outgoing things with `things`
-
-        @param thing: some things
-        '''
-
-    ###########################################################################
-    ## iteration runners ######################################################
-    ###########################################################################
-
-    def __iter__():  # @NoSelf
-        '''yield outgoing things, clearing outgoing things as it iterates'''
-
-    def breakcount(call, length, exception=StopIteration):  # @NoSelf
-        '''
-        rotate through iterator until it reaches its original length
-
-        @param iterable: an iterable to exhaust
-        '''
-
-    def exhaust(iterable, exception=StopIteration):  # @NoSelf
-        '''
-        call next on an iterator until it's exhausted
-
-        @param iterable: iterable to exhaust
-        @param exception: exception marking end of iteration
-        '''
-
-    def exhaustmap(map, call, filter=False, exception=StopIteration):  # @NoSelf @IgnorePep8
-        '''
-        call `next` on an iterator until it's exhausted
-
-        @param mapping: a mapping to exhaust
-        @param call: call to handle what survives the filter
-        @param filter: a filter to apply to mapping (default: `None`)
-        @param exception: exception sentinel (default: `StopIteration`)
-        '''
-
-    def iterexcept(call, exception, start=None):  # @NoSelf
-        '''
-        call a function repeatedly until an exception is raised
-
-        Converts a call-until-exception interface to an iterator interface.
-        Like `iter(call, sentinel)` but uses an exception instead of a sentinel
-        to end the loop.
-
-        Raymond Hettinger, Python Cookbook recipe # 577155
-        '''
-
-
-class KResult(KThings):
-
-    def end():  # @NoSelf
-        '''return outgoing things then clear out everything'''
-
-    def first():  # @NoSelf
-        '''first incoming thing'''
-
-    def last():  # @NoSelf
-        '''last incoming thing'''
-
-    def peek():  # @NoSelf
-        '''results from read-only context'''
-
-    def results():  # @NoSelf
-        '''yield outgoing things, clearing outgoing things as it iterates'''
-
-    def value():  # @NoSelf
-        '''return outgoing things and clear outgoing things'''

File callchain/keys/settings.py

-# -*- coding: utf-8 -*-
-'''callchain settings keys'''
-
-from appspace.keys import ANamespace
-
-
-class KSettings(ANamespace):
-
-    '''settings key'''
-
-
-class KDefaults(KSettings):
-
-    '''default settings key'''
-
-
-class KInternalDefaults(KDefaults):
-
-    '''internal default settings key'''
-
-
-class KExternalDefaults(KDefaults):
-
-    '''external required settings key'''
-
-
-class KRequired(KSettings):
-
-    '''required settings key'''
-
-
-class KInternalRequired(KRequired):
-
-    '''internal required settings key'''
-
-
-class KExternalRequired(KRequired):
-
-    '''external required settings key'''
-
-
-class KSettingsManager(ANamespace):
-
-    '''settings manager key'''
-
-
-class KExternalSettings(KSettingsManager):
-
-    '''external settings manager key'''
-
-
-class KInternalSettings(KSettingsManager):
-
-    '''internal settings manager key'''

File callchain/linked/core.py

 '''root chain mixins'''
 
 from callchain.base import ResetLocalMixin
-from callchain.mixins import (
+from callchain.mixin import (
     BranchMixin, EventBranchMixin, ChainMixin, PriorityMixin, EventMixin)
 
 

File callchain/linked/keys.py

 # -*- coding: utf-8 -*-
 '''callchain contrib keys'''
 
-from callchain.keys.base import KConfig
-from callchain.keys.queue import KResult, KThings
-from callchain.keys.mixin import KCall, KChain, KBranch
+from callchain.key.base import KConfig
+from callchain.key.queue import KResult, KThings
+from callchain.key.mixin import KCall, KChain, KBranch
 
 
 class KLinked(KBranch, KConfig, KCall, KChain):

File callchain/mixin.py

+# -*- coding: utf-8 -*-
+'''root chain mixins'''
+
+from itertools import count, chain
+from functools import partial
+
+from twoq.support import isstring
+from appspace.builders import Appspace
+from stuf.utils import OrderedDict, lazy
+
+from callchain.chainlet.keys import NoService
+from callchain.chainlet.registry import EventRegistry
+from callchain.base import ResetLocalMixin, ConfigMixin
+from callchain.support import Empty, Queue, PriorityQueue, total_ordering
+
+#    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)
+
+
+@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 BranchMixin(ResetLocalMixin):
+
+    ''''branch mixin'''
+
+    def _setup(self, root):
+        '''
+        configure branch
+
+        @param root: root chain
+        '''
+        super(BranchMixin, self)._setup(root)
+        root.join(self)
+
+
+class EventBranchMixin(BranchMixin):
+
+    '''event branch mixin'''
+
+    @lazy
+    def E(self):
+        '''local event registry'''
+        return EventRegistry('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 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 NoService:
+            # ...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
+
+
+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)

File callchain/mixins.py

-# -*- coding: utf-8 -*-
-'''root chain mixins'''
-
-from itertools import count, chain
-from functools import partial
-
-from twoq.support import isstring
-from appspace.builders import Appspace
-from stuf.utils import OrderedDict, lazy
-
-from callchain.chainlet.keys import NoService
-from callchain.chainlet.registry import EventRegistry
-from callchain.base import ResetLocalMixin, ConfigMixin
-from callchain.support import Empty, Queue, PriorityQueue, total_ordering
-
-#    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)
-
-
-@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 BranchMixin(ResetLocalMixin):
-
-    ''''branch mixin'''
-
-    def _setup(self, root):
-        '''
-        configure branch
-
-        @param root: root chain
-        '''
-        super(BranchMixin, self)._setup(root)
-        root.join(self)
-
-
-class EventBranchMixin(BranchMixin):
-
-    '''event branch mixin'''
-
-    @lazy
-    def E(self):
-        '''local event registry'''
-        return EventRegistry('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 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 NoService:
-            # ...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
-
-
-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