Resolves Issue #282

This commit is contained in:
BJ Dierkes 2015-05-06 16:45:39 -05:00
parent a8c9715b88
commit d70122d254
12 changed files with 233 additions and 90 deletions

View File

@ -39,6 +39,8 @@ Features:
* :issue:`197` - Added support for colorized logging.
* :issue:`281` - Added support for Python `with` operation.
* :issue:`282` - Added support to define/register hooks and handlers via
``CementApp.Meta``.
* :issue:`290` - Added ability to disable Cement framework logging via
``CementApp.Meta.framework_logging = False``.
* :issue:`297` - Added **experimental** support for reloading

View File

@ -580,6 +580,40 @@ class CementApp(meta.MetaMixin):
it enabled.
"""
define_hooks = []
"""
List of hook definitions (label). Will be passed to
``hook.define(<hook_label>)``. Must be a list of strings.
I.e. ``['my_custom_hook', 'some_other_hook']``
"""
hooks = []
"""
List of hooks to register when the app is created. Will be passed to
``hook.register(<hook_label>, <hook_func>)``. Must be a list of
tuples in the form of ``(<hook_label>, <hook_func>)``.
I.e. ``[('post_argument_parsing', my_hook_func)]``.
"""
define_handlers = []
"""
List of interfaces classes to define handlers. Must be a list of
uninstantiated interface classes.
I.e. ``['MyCustomInterface', 'SomeOtherInterface']``
"""
handlers = []
"""
List of handler classes to register. Will be passed to
``handler.register(<handler_class>)``. Must be a list of
uninstantiated handler classes.
I.e. ``[MyCustomHandler, SomeOtherHandler]``
"""
def __init__(self, label=None, **kw):
super(CementApp, self).__init__(**kw)
@ -895,10 +929,18 @@ class CementApp(meta.MetaMixin):
hook.define('pre_render')
hook.define('post_render')
# define application hooks from meta
for label in self._meta.define_hooks:
hook.define(label)
# register some built-in framework hooks
hook.register('post_setup', add_handler_override_options, weight=-99)
hook.register('post_argument_parsing', handler_override, weight=-99)
# register application hooks from meta
for label, func in self._meta.hooks:
hook.register(label, func)
# define and register handlers
handler.define(extension.IExtension)
handler.define(log.ILog)
@ -910,10 +952,18 @@ class CementApp(meta.MetaMixin):
handler.define(controller.IController)
handler.define(cache.ICache)
# define application handlers
for interface_class in self._meta.define_handlers:
handler.define(interface_class)
# extension handler is the only thing that can't be loaded... as,
# well, an extension. ;)
handler.register(extension.CementExtensionHandler)
# register application handlers
for handler_class in self._meta.handlers:
handler.register(handler_class)
def _parse_args(self):
for res in hook.run('pre_argument_parsing', self):
pass

View File

@ -107,7 +107,7 @@ class ColorLogHandler(LoggingLogHandler):
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
}
}
def __init__(self, *args, **kw):
super(ColorLogHandler, self).__init__(*args, **kw)

View File

@ -23,7 +23,25 @@ Defining a Hook
---------------
A hook can be defined anywhere, however it is generally recommended to define
the hook as early as possible, and within the application setup if possible:
the hook as early as possible. A hook definition simply gives a label to the
hook, and allows the developer to register functions to that hook. It's label
is arbitrary.
The most convenient way to define a hook is via
``CementApp.Meta.define_hooks``:
.. code-block:: python
from cement.core.foundation import CementApp
class MyApp(CementApp):
class Meta:
label = 'myapp'
define_hooks = ['my_example_hook']
Alternatively, if you need more control you might do it in
``CementApp.setup()``:
.. code-block:: python
@ -42,7 +60,6 @@ the hook as early as possible, and within the application setup if possible:
hook.define('my_example_hook')
Registering Functions to a Hook
-------------------------------
@ -53,15 +70,46 @@ been created, after the hook is defined, and before the hook is run. Note
that every hook is different, and therefore should be clearly documented by
the 'owner' of the hook (application developer, plugin developer, etc).
The most convenient way to register a hook function is with
``CementApp.Meta.hooks``:
.. code-block:: python
from cement.core.foundation import CementApp
from cement.core import hook
def my_func(app):
def my_hook1(app):
pass
def my_hook2(app):
pass
class MyApp(CementApp):
class Meta:
hooks = [
('post_argument_parsing', my_hook1),
('pre_close', my_hook2),
]
with MyApp() as app:
app.run()
Where ``CementApp.Meta.hooks`` is a list of tuples that define the hook label,
and the function to register to that hook.
Alternatively, if you need more control you might use:
.. code-block:: python
from cement.core.foundation import CementApp
from cement.core import hook
def my_hook1(app):
pass
with CementApp('myapp') as app:
hook.register('my_example_hook', my_func)
hook.register('post_argument_parsing', my_hook1)
app.run()

View File

@ -69,6 +69,20 @@ The following defines a basic interface:
"""
class MyApp(CementApp):
class Meta:
label = 'myapp'
define_handlers = [MyInterface]
Alternatively, if you need more control you might define a handler this
way:
.. code-block:: python
from cement.core.foundation import CementApp
from cement.core import handler
with CementApp('myapp') as app:
# define interfaces after app is created
handler.define(MyInterface)
@ -158,6 +172,18 @@ is a handler that implements the MyInterface above:
def do_something(self):
print "Doing work!"
class MyApp(CementApp):
class Meta:
label = 'myapp'
handlers = [MyHandler]
Alternatively, if you need more control you might use this approach:
.. code-block:: python
from cement.core.foundation import CementApp
from cement.core import handler
with CementApp('myapp') as app:
# register handler after the app is created
handler.register(MyHandler)

View File

@ -230,24 +230,13 @@ handle command dispatch and rapid development.
class MyApp(CementApp):
class Meta:
label = 'myapp'
base_controller = MyBaseController
base_controller = 'base'
handlers = [MyBaseController, MySecondController)
# create the app
app = MyApp()
# Register any handlers that aren't passed directly to CementApp
handler.register(MySecondController)
# setup the application
app.setup()
# run the application
app.run()
# close the app
app.close()
with MyApp() as app:
app.run()
As you can see, we're able to build out the core functionality of our app such

View File

@ -96,17 +96,19 @@ still maintain the existing shared commands (or override them as necessary).
def command2(self):
print("Inside Controller2.command2()")
class MyApp(CementApp):
class Meta:
label = 'myapp'
handlers = [
MyAppBaseController,
Controller1,
Controller2,
]
def main():
app = CementApp('myapp')
# register controllers handlers
handler.register(MyAppBaseController)
handler.register(Controller1)
handler.register(Controller2)
app.setup()
app.run()
app.close()
with MyApp() as app:
app.run()
if __name__ == '__main__':
main()

View File

@ -67,16 +67,16 @@ Example
class MyApp(CementApp):
class Meta:
label = 'myapp'
base_controller = MyBaseController
base_controller = 'base'
handlers = [
MyBaseController,
MySecondController,
]
def main():
app = MyApp()
handler.register(MySecondController)
app.setup()
app.run()
app.close()
with MyApp() as app:
app.run()
if __name__ == '__main__':
main()

View File

@ -84,26 +84,17 @@ sub-commands, that are implemented via nested-controllers.
class MyApp(CementApp):
class Meta:
label = 'myapp'
handlers = [
BaseController,
EmbeddedController,
SecondLevelController,
ThirdLevelController,
]
def main():
# create the app
app = MyApp()
# register controllers to the app
handler.register(BaseController)
handler.register(EmbeddedController)
handler.register(SecondLevelController)
handler.register(ThirdLevelController)
# setup the app
app.setup()
# run the app
app.run()
# close the app
app.close()
with MyApp() as app:
app.run()
if __name__ == '__main__':
main()

View File

@ -75,25 +75,20 @@ and a 'hosts' controller and we want to have a 'list' sub-command under both:
def default(self):
print "Inside HostsListController.default()"
class MyApp(CementApp):
class Meta:
label = 'myapp'
handlers = [
MyAppBaseController,
UsersController,
HostsController,
UsersListController,
HostsListController,
]
def main():
# create the application
app = CementApp('myapp')
# register non-base controllers
handler.register(MyAppBaseController)
handler.register(UsersController)
handler.register(HostsController)
handler.register(UsersListController)
handler.register(HostsListController)
# setup the application
app.setup()
# run it
app.run()
# close it
app.close()
with MyApp() as app:
app.run()
if __name__ == '__main__':
main()

View File

@ -72,25 +72,20 @@ Multiple Stacked Controllers
def command4(self):
print "Inside FourthController.command4()"
class MyApp(CementApp):
class Meta:
label = 'myapp'
handlers = [
MyAppBaseController,
SecondController,
ThirdController,
FourthController,
]
def main():
try:
# create the application
app = CementApp('myapp')
# register controllers
handler.register(MyAppBaseController)
handler.register(SecondController)
handler.register(ThirdController)
handler.register(FourthController)
# setup the application
app.setup()
# run the application
with MyApp() as app:
app.run()
finally:
# close the application
app.close()
if __name__ == '__main__':
main()

View File

@ -5,6 +5,7 @@ import sys
import json
from cement.core import foundation, exc, backend, config, extension, plugin
from cement.core import log, output, handler, hook, arg, controller
from cement.core.interface import Interface
from cement.utils import test
from cement.utils.misc import init_defaults, rando, minimal_logger
@ -19,6 +20,18 @@ class DeprecatedApp(foundation.CementApp):
label = 'deprecated'
defaults = None
class HookTestException(Exception):
pass
class MyTestInterface(Interface):
class IMeta:
label = 'my_test_interface'
class MyTestHandler(handler.CementBaseHandler):
class Meta:
label = 'my_test_handler'
interface = MyTestInterface
class TestOutputHandler(output.CementOutputHandler):
file_suffix = None
@ -375,3 +388,35 @@ class FoundationTestCase(test.CementCoreTestCase):
app.setup()
app.run()
def test_define_hooks_meta(self):
app = self.make_app(APP, define_hooks=['my_custom_hook'])
app.setup()
self.ok(hook.defined('my_custom_hook'))
@test.raises(HookTestException)
def test_register_hooks_meta(self):
def my_custom_hook_func():
raise HookTestException('OK')
app = self.make_app(APP,
define_hooks=['my_custom_hook'],
hooks=[('my_custom_hook', my_custom_hook_func)])
app.setup()
for res in hook.run('my_custom_hook'):
pass
def test_define_handlers_meta(self):
app = self.make_app(APP, define_handlers=[MyTestInterface])
app.setup()
self.ok(handler.defined('my_test_interface'))
def test_register_handlers_meta(self):
app = self.make_app(APP,
define_handlers=[MyTestInterface],
handlers=[MyTestHandler],
)
app.setup()
self.ok(handler.registered('my_test_interface', 'my_test_handler'))