Source

appspace / appspace / builders.py

Full commit
# -*- coding: utf-8 -*-
'''appspace builder'''

from functools import partial
from itertools import starmap

from stuf.six import strings
from stuf.utils import selfname, exhaust, exhaustmap, twoway

from appspace.registry import Manager, StrictManager, lazyimport
from appspace.keys import (
    AAppspace, AppLookupError, NoAppError, ABranch, ANamespace, AApp, appifies)


class _Filter(object):

    @classmethod
    def _filter(self, x):
        return not x[0].startswith('_')


class Patterns(_Filter):

    '''patterns for manager configured by class'''

    key = AApp
    strict = False

    @twoway
    def _manager(self):
        '''manager class'''
        return StrictManager if self.strict else Manager

    @classmethod
    def build(cls):
        '''build manager configuration from class'''
        l = selfname(cls)
        # set key
        key = cls.key
        if isinstance(key, strings):
            # load key if string
            key = lazyimport(key)
        manager = cls._manager(l, key)  # pylint: disable-msg=e1121
        b = partial(manager.keyed, ABranch)
        m, n = manager.set, partial(manager.keyed, ANamespace)
        t = lambda x, y: y.build(manager) if (n(y) or b(y)) else m(y, x, l)
        exhaustmap(vars(cls), t, cls._filter)
        return manager

    @staticmethod
    def factory(label, manager, *args):
        '''
        factory for manager

        @param label: label for manager
        '''
        # build manager
        manager = manager(label)
        # register things in manager
        exhaust(starmap(lambda x, y: manager.set(y, x), iter(args)))
        return manager

    @classmethod
    def patterns(cls, label, *args):
        '''
        configure appspace

        @param label: name of branch appspace
        @param *args: tuple of module paths or component inclusions
        '''
        return cls.factory(label, cls._manager, *args)
    
    
class _PatternMixin(_Filter):

    @classmethod
    def _key(cls, label, manager):
        try:
            # lazily load key
            key = cls.key
            if isinstance(key, strings):
                key = lazyimport(key)
            # register class key
            manager.ez_register(ANamespace, label, key)
        except AttributeError:
            key = manager.key(ANamespace, label)



@appifies(ANamespace)
class Branch(_PatternMixin):

    '''branch configuration'''

    @classmethod
    def build(cls, manager):
        '''gather branch configuration'''
        cls._key(selfname(cls), manager)
        i, m = cls.include, manager.set
        t = lambda x: not x[0].startswith('_') or isinstance(x[1], strings)
        exhaustmap(vars(cls), lambda x, y: m(i(y), x), t)

    @staticmethod
    def include(module):
        '''
        configure branch appspace

        @param module: module import path
        '''
        return ('include', module)
    
    
@appifies(ANamespace)
class Namespace(_PatternMixin):

    '''configuration namespace'''

    @classmethod
    def build(cls, manager):
        '''gather namespace configuration'''
        label = selfname(cls)
        cls._key(label, manager)
        m, n = manager.set, partial(manager.keyed, ANamespace)
        t = lambda k, v: v.build(manager) if n(v) else m(v, k, label)
        exhaustmap(vars(cls), t, cls._filter)


@appifies(AAppspace)
class Appspace(object):

    '''appspace interface'''

    __slots__ = ['manager']

    def __init__(self, manager):
        '''
        init

        @param manager: appspace manager
        '''
        self.manager = manager

    def __getattr__(self, label):
        try:
            return object.__getattribute__(self, label)
        except AttributeError:
            return self.__getitem__(label)

    def __getitem__(self, label):
        try:
            item = self.manager.get(label, self.manager._current)
        except AppLookupError:
            try:
                # try finding namespace
                self.manager.namespace(label)
            except AppLookupError:
                raise NoAppError(label)
            else:
                # temporarily swap primary label
                self.manager._current = label
                return self
        else:
            # ensure current label is set back to default
            self.manager._current = self.manager._root
            return item

    def __call__(self, label, *args, **kw):
        try:
            result = self.__getitem__(label)
            return result(*args, **kw)
        except TypeError:
            return result


factory = Patterns.factory
include = Branch.include
space_patterns = Patterns.patterns

def patterns(label, *args, **kw):
    '''
    factory for manager

    @param label: label for manager
    '''
    return Appspace(space_patterns(label, *args, **kw))


def class_patterns(clspatterns):
    '''
    factory for manager configured with class patterns

    @param clspatterns: class patterns
    '''
    return Appspace(clspatterns.build())