import sys
from .config import Configurable, Directive, commit, create_code_info
from .compat import with_metaclass
class Config(object):
"""The object that contains the configurations.
The configurations are specified by the :attr:`Action.config`
class attribute of :class:`Action`.
"""
pass
class AppMeta(type):
"""Dectate metaclass.
Sets up ``config`` and ``dectate`` class attributes.
"""
def __new__(cls, name, bases, d):
extends = [base.dectate for base in bases
if hasattr(base, 'dectate')]
d['config'] = config = Config()
d['dectate'] = configurable = Configurable(extends, config)
result = super(AppMeta, cls).__new__(cls, name, bases, d)
configurable.app_class = result
return result
class App(with_metaclass(AppMeta)):
"""A configurable application object.
Subclass this in your framework and add directives using
the :meth:`App.directive` decorator.
Set the ``logger_name`` class attribute to the logging prefix
that Dectate should log to. By default it is ``"dectate.directive"``.
"""
logger_name = 'dectate.directive'
"""The prefix to use for directive debug logging."""
dectate = None
"""A dectate Configurable instance is installed here.
This is installed when the class object is initialized, so during
import-time when you use the ``class`` statement and subclass
:class:`dectate.App`.
This keeps tracks of the registrations done by using directives as long
as committed configurations.
"""
config = None
"""Config object that contains the configuration after commit.
This is installed when the class object is initialized, so during
import-time when you use the ``class`` statement and subclass
:class:`dectate.App`, but is only filled after you commit the
configuration.
This keeps the final configuration result after commit. It is
a very dumb object that has no methods and is just a container for
attributes that contain the real configuration.
"""
@classmethod
def get_directive_methods(cls):
for name in dir(cls):
attr = getattr(cls, name)
im_func = getattr(attr, '__func__', None)
if im_func is None:
continue
if hasattr(im_func, 'action_factory'):
yield name, attr
@classmethod
def commit(cls):
"""Commit this class and any depending on it.
This is intended to be overridden by subclasses if committing
the class also commits other classes automatically, such as in
the case in Morepath when one app is mounted into another. In
such case it should return an iterable of all committed
classes.
:return: an iterable of committed classes
"""
commit(cls)
return [cls]
@classmethod
def is_committed(cls):
"""True if this app class was ever committed.
:return: bool that is ``True`` when the app was committed before.
"""
return cls.dectate.committed
@classmethod
def clean(cls):
"""A method that sets or restores the state of the class.
Normally Dectate only sets up configuration into the ``config``
attribute, but in some cases you may touch other aspects of the
class during configuration time. You can override this classmethod
to set up the state of the class in its pristine condition.
"""
pass
def directive(action_factory):
"""Create a classmethod to hook action to application class.
You pass in a :class:`dectate.Action` or a
:class:`dectate.Composite` subclass and can attach the result as a
class method to an :class:`dectate.App` subclass::
class FooAction(dectate.Action):
...
class MyApp(dectate.App):
my_directive = dectate.directive(MyAction)
Alternatively you can also define the direction inline using
this as a decorator::
class MyApp(dectate.App):
@directive
class my_directive(dectate.Action):
...
:param action_factory: an action class to use as the directive.
:return: a class method that represents the directive.
"""
def method(cls, *args, **kw):
frame = sys._getframe(1)
code_info = create_code_info(frame)
return Directive(action_factory, code_info, cls, args, kw)
# sphinxext and App.get_action_classes need to recognize this
method.action_factory = action_factory
method.__doc__ = action_factory.__doc__
method.__module__ = action_factory.__module__
return classmethod(method)