mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 14:26:55 +00:00
Resolves Issue #282
This commit is contained in:
parent
a8c9715b88
commit
d70122d254
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -107,7 +107,7 @@ class ColorLogHandler(LoggingLogHandler):
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'red',
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(ColorLogHandler, self).__init__(*args, **kw)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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'))
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user