mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 14:16:46 +00:00
Resolves Issue #281
This commit is contained in:
parent
42c6298f51
commit
c71ccfe42f
@ -18,7 +18,7 @@ See the :ref:`upgrading` section for more information related to
|
||||
any incompatible changes, and how to update your application to fix them.
|
||||
|
||||
|
||||
2.5.1 - DEVELOPMENT (will be released as 2.5./dev or 2.6.0/stable)
|
||||
2.5.1 - DEVELOPMENT (will be released as 2.5.2/dev or 2.6.0/stable)
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
This is a branch off of the 2.4.x stable code base. Maintenance releases for
|
||||
@ -31,7 +31,7 @@ Bugs:
|
||||
|
||||
Features:
|
||||
|
||||
* None
|
||||
* :issue:`281` - Added support for Python `with` operation
|
||||
|
||||
|
||||
2.4.0 - Wed Sep 17, 2014
|
||||
|
||||
@ -125,21 +125,32 @@ class CementApp(meta.MetaMixin):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
app = foundation.CementApp('helloworld')
|
||||
try:
|
||||
app.setup()
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
with CementApp('helloworld') as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
Alternatively, the above could be written as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
app = foundation.CementApp('helloworld')
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close()
|
||||
|
||||
|
||||
A more advanced example looks like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, controller
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
|
||||
class MyController(controller.CementBaseController):
|
||||
class MyController(CementBaseController):
|
||||
class Meta:
|
||||
label = 'base'
|
||||
arguments = [
|
||||
@ -150,22 +161,18 @@ class CementApp(meta.MetaMixin):
|
||||
some_config_param='some_value',
|
||||
)
|
||||
|
||||
@controller.expose(help='This is the default command', hide=True)
|
||||
@expose(help='This is the default command', hide=True)
|
||||
def default(self):
|
||||
print('Hello World')
|
||||
|
||||
class MyApp(foundation.CementApp):
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'helloworld'
|
||||
extensions = ['daemon','json',]
|
||||
base_controller = MyController
|
||||
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
@ -187,6 +194,13 @@ class CementApp(meta.MetaMixin):
|
||||
Used internally, and should not be used by developers. This is set
|
||||
to `True` if `--debug` is passed at command line."""
|
||||
|
||||
exit_on_close = True
|
||||
"""
|
||||
Whether or not to call ``sys.exit()`` when ``close()`` is called.
|
||||
Generally only used for testing to avoid having to catch
|
||||
``SystemExit`` three thousand times.
|
||||
"""
|
||||
|
||||
config_files = None
|
||||
"""
|
||||
List of config files to parse.
|
||||
@ -561,6 +575,7 @@ class CementApp(meta.MetaMixin):
|
||||
self._loaded_bootstrap = None
|
||||
self._parsed_args = None
|
||||
self._last_rendered = None
|
||||
self.exit_code = 0
|
||||
|
||||
self.ext = None
|
||||
self.config = None
|
||||
@ -698,14 +713,23 @@ class CementApp(meta.MetaMixin):
|
||||
for res in hook.run('post_run', self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
self.setup()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
# only close the app if there are no unhandled exceptions
|
||||
if exc_type is None:
|
||||
self.close()
|
||||
|
||||
def close(self, code=None):
|
||||
"""
|
||||
Close the application. This runs the pre_close and post_close hooks
|
||||
allowing plugins/extensions/etc to 'cleanup' at the end of program
|
||||
execution.
|
||||
|
||||
:param code: An exit status to exit with (`int`). If a code is given
|
||||
then call `sys.exit(code)`, otherwise `sys.exit()` is not called.
|
||||
:param code: An exit code to exit with (`int`), if `None` is passed
|
||||
then exit with whatever `self.exit_code` is currently set to.
|
||||
|
||||
"""
|
||||
for res in hook.run('pre_close', self):
|
||||
@ -719,7 +743,10 @@ class CementApp(meta.MetaMixin):
|
||||
if code is not None:
|
||||
assert type(code) == int, \
|
||||
"Invalid exit status code (must be integer)"
|
||||
sys.exit(code)
|
||||
self.exit_code = code
|
||||
|
||||
if self._meta.exit_on_close is True:
|
||||
sys.exit(self.exit_code)
|
||||
|
||||
def render(self, data, template=None, out=sys.stdout):
|
||||
"""
|
||||
|
||||
@ -77,12 +77,14 @@ trigger daemon functionality before app.run() is called.
|
||||
.. code-block:: python
|
||||
|
||||
from time import sleep
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
app = foundation.CementApp('myapp', extensions=['daemon'])
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
extensions = ['daemon']
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.daemonize()
|
||||
app.run()
|
||||
|
||||
@ -91,11 +93,10 @@ trigger daemon functionality before app.run() is called.
|
||||
count = count + 1
|
||||
print('Iteration: %s' % count)
|
||||
sleep(10)
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
An alternative to the above is to put app.daemonize() within a framework hook:
|
||||
An alternative to the above is to put the ``daemonize()`` call within a
|
||||
framework hook:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -114,11 +115,16 @@ rather than the entire parent application. For example:
|
||||
|
||||
from cement.core import foundation, controller, handler
|
||||
|
||||
class MyAppBaseController(controller.CementBaseController):
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
|
||||
class MyBaseController(CementBaseController):
|
||||
class Meta:
|
||||
label = 'base'
|
||||
|
||||
@controller.expose(help="run the daemon command.")
|
||||
@expose(help="run the daemon command.")
|
||||
def run_forever(self):
|
||||
from time import sleep
|
||||
self.app.daemonize()
|
||||
@ -129,17 +135,15 @@ rather than the entire parent application. For example:
|
||||
print(count)
|
||||
sleep(10)
|
||||
|
||||
app = foundation.CementApp('myapp',
|
||||
extensions=['daemon'],
|
||||
base_controller=MyAppBaseController,
|
||||
)
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
base_controller = MyBaseController
|
||||
extensions = ['daemon']
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
By default, even after app.daemonize() is called... the application will
|
||||
continue to run in the foreground, but will still manage the pid and
|
||||
|
||||
@ -86,9 +86,8 @@ class MemcachedCacheHandler(cache.CementCacheHandler):
|
||||
extensions = ['memcached']
|
||||
cache_handler = 'memcached'
|
||||
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
# Run the app
|
||||
app.run()
|
||||
|
||||
# Set a cached value
|
||||
@ -103,9 +102,6 @@ class MemcachedCacheHandler(cache.CementCacheHandler):
|
||||
# Delete the entire cache
|
||||
app.cache.purge()
|
||||
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
interface = cache.ICache
|
||||
|
||||
@ -25,6 +25,7 @@ class TestApp(foundation.CementApp):
|
||||
argv = []
|
||||
base_controller = None
|
||||
arguments = []
|
||||
exit_on_close = False
|
||||
|
||||
|
||||
class CementTestCase(unittest.TestCase):
|
||||
|
||||
@ -15,7 +15,7 @@ out of a single file. The following is a minimal example that creates a
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
|
||||
|
||||
@ -40,27 +40,16 @@ out of a single file. The following is a minimal example that creates a
|
||||
def cmd2(self):
|
||||
print("Inside BaseController.cmd2()")
|
||||
|
||||
class MyApp(foundation.CementApp):
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
base_controller = BaseController
|
||||
|
||||
|
||||
def main():
|
||||
# create the app - before the try/except/finally block
|
||||
app = MyApp()
|
||||
|
||||
try:
|
||||
# setup the app
|
||||
app.setup()
|
||||
|
||||
# run the app
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
|
||||
finally:
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -87,7 +76,7 @@ application layout, and is a great starting point for anyone new to Cement.
|
||||
The primary detail about how to layout your code is this: All CLI/Cement
|
||||
related code should live separate from the "core logic" of your application.
|
||||
Most likely, you will have some code that is re-usable by other people and you
|
||||
do not want to mix this with your Cement code, becuase that will rely on
|
||||
do not want to mix this with your Cement code, because that will rely on
|
||||
Cement being loaded to function properly (like it is when called from command
|
||||
line).
|
||||
|
||||
@ -130,7 +119,8 @@ The following expands on the above to give an example of how you might handle
|
||||
exceptions at the highest level (wrapped around the app object). It is very
|
||||
well known that exception handling should happen as close to the source of the
|
||||
exception as possible, and you should do that. However at the top level
|
||||
(generally in your ``main.py`` or similar) you want to handle the exception so
|
||||
(generally in your ``main.py`` or similar) you want to handle certain
|
||||
exceptions (such as argument errors, or user interaction related errors) so
|
||||
that they are presented nicely to the user. End-users don't like stack
|
||||
traces!
|
||||
|
||||
@ -140,57 +130,40 @@ but you could also catch your own application specific exception this way:
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
from cement.core.exc import FrameworkError, CaughtSignal
|
||||
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.exc import FrameworkError, CaughtSignal
|
||||
|
||||
|
||||
def main():
|
||||
# create the app
|
||||
app = CementApp('myapp')
|
||||
with CementApp('myapp') as app:
|
||||
try:
|
||||
app.run()
|
||||
|
||||
# default our exit status (return code) to 0 (non-error)
|
||||
ret = 0
|
||||
except CaughtSignal as e:
|
||||
# determine what the signal is, and do something with it?
|
||||
from signal import SIGINT, SIGABRT
|
||||
|
||||
try:
|
||||
# setup the app
|
||||
app.setup()
|
||||
if e.signum == SIGINT:
|
||||
# do something... maybe change the exit code?
|
||||
app.exit_code = 110
|
||||
elif e.signum == SIGABRT:
|
||||
# do something else...
|
||||
app.exit_code = 111
|
||||
|
||||
# run the app
|
||||
app.run()
|
||||
except FrameworkError as e:
|
||||
# do something when a framework error happens
|
||||
print("FrameworkError => %s" % e)
|
||||
|
||||
except CaughtSignal as e:
|
||||
# maybe determine what the signal is, and do something with it?
|
||||
from signal import SIGINT, SIGABRT
|
||||
# and maybe set the exit code to something unique as well
|
||||
app.exit_code = 300
|
||||
|
||||
if e.signum == SIGINT:
|
||||
# do something... maybe change the return status
|
||||
ret = 110
|
||||
elif e.signum == SIGABRT:
|
||||
# do something else...
|
||||
ret = 111
|
||||
|
||||
except FrameworkError as e:
|
||||
# do something when a framework error happens
|
||||
print("FrameworkError => %s" % e)
|
||||
|
||||
# set the exit status to 1 (error)
|
||||
ret = 1
|
||||
|
||||
finally:
|
||||
# if --debug was passed, we want to see a full stack trace
|
||||
if app.debug:
|
||||
import traceback
|
||||
print("")
|
||||
print('TRACEBACK:')
|
||||
print('-' * 77)
|
||||
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
traceback.print_tb(exc_traceback, limit=20, file=sys.stdout)
|
||||
|
||||
print("")
|
||||
|
||||
# allow everything to cleanup nicely, and exit with out custom
|
||||
# error code
|
||||
app.close(ret)
|
||||
finally:
|
||||
# Maybe we want to see a full-stack trace for the above
|
||||
# exceptions, but only if --debug was passed?
|
||||
if app.debug:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -41,15 +41,9 @@ Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
|
||||
# create the application
|
||||
app = foundation.CementApp('myapp')
|
||||
|
||||
try:
|
||||
# setup the application
|
||||
app.setup()
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
with CementApp('myapp') as app:
|
||||
# run the application
|
||||
app.run()
|
||||
|
||||
@ -65,5 +59,3 @@ Example:
|
||||
# delete the entire cache
|
||||
app.cache.purge()
|
||||
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
@ -3,7 +3,7 @@ Application Cleanup
|
||||
|
||||
The concept of 'cleanup' after application run time is nothing new. What
|
||||
happens during 'cleanup' all depends on the application. This might mean
|
||||
cleaning up temporary files, removing session data, or removing a PID
|
||||
closing and deleting temporary files, removing session data, or deleting a PID
|
||||
(Process ID) file.
|
||||
|
||||
To allow for application cleanup not only within your program, but also
|
||||
@ -15,33 +15,52 @@ For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
app = foundation.CementApp('helloworld')
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
app = CementApp('helloworld')
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close()
|
||||
|
||||
|
||||
You will note that we put ``app.run()`` within a ``try`` block, and
|
||||
``app.close()`` in a ``finally`` block. The important thing to note is that
|
||||
regardless of whether an exception is encountered or not, we always run
|
||||
``app.close()``. This is where the ``pre_close`` and ``post_close`` hooks are
|
||||
run, allowing extensions/plugins/etc to cleanup after the program runs.
|
||||
Calling ``app.close()`` ensures that the ``pre_close`` and ``post_close``
|
||||
framework hooks are run, allowing extensions/plugins/etc to cleanup after the
|
||||
program runs.
|
||||
|
||||
Also note that you can optionally pass an exit code to ``app.close()`` to tell
|
||||
Cement to exit the app here as well:
|
||||
Note that when using the Python ``with`` operator, the ``setup()`` and
|
||||
``close()`` methods are automatically called. For example, the following is
|
||||
exactly the same as the above example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# non-error exit status is generally 0
|
||||
app.close(0)
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
# or exit with an error
|
||||
app.close(1)
|
||||
with CementApp('helloworld') as app:
|
||||
app.run()
|
||||
|
||||
|
||||
Also note that you can optionally set an exit code to alter that status in
|
||||
which your application exits:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = CementApp('helloworld')
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close(27)
|
||||
|
||||
|
||||
Or Alternatively:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with CementApp('helloworld') as app:
|
||||
app.run()
|
||||
app.exit_code = 123
|
||||
|
||||
|
||||
The default exit code is ``0``, however any uncaught exceptions will cause the
|
||||
application to exit with a code of ``1`` (error).
|
||||
|
||||
|
||||
Running Cleanup Code
|
||||
|
||||
@ -265,37 +265,28 @@ happen in *all* config sections if enabled:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.utils.misc import init_defaults
|
||||
|
||||
defaults = init_defaults('myapp')
|
||||
defaults['myapp']['foo'] = 'bar'
|
||||
|
||||
app = foundation.CementApp(
|
||||
label='myapp',
|
||||
config_defaults=defaults,
|
||||
arguments_override_config=True,
|
||||
)
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
config_defaults = defaults
|
||||
arguments_override_config = True
|
||||
|
||||
try:
|
||||
# First setup the application
|
||||
app.setup()
|
||||
|
||||
# Add arguments
|
||||
with MyApp() as app:
|
||||
app.args.add_argument('--foo', action='store', dest='foo')
|
||||
|
||||
# Run the application (this parses command line, among other things)
|
||||
app.run()
|
||||
|
||||
finally:
|
||||
# close the application
|
||||
app.close()
|
||||
|
||||
|
||||
With ``arguments_override_config`` enabled, running the application and
|
||||
With ``arguments_override_config`` enabled, running the above application and
|
||||
passing the ``--foo=some_value`` option will override the ``foo`` setting
|
||||
under the ``[myapp]`` section as well as any other section that has a matching
|
||||
key.
|
||||
under a ``[myapp]`` configuration section as well as any other section that
|
||||
has a matching ``foo`` key.
|
||||
|
||||
|
||||
Configuration Options Versus Meta Options
|
||||
|
||||
@ -70,18 +70,15 @@ handle command dispatch and rapid development.
|
||||
def command2(self):
|
||||
self.app.log.info("Inside base.command2 function.")
|
||||
|
||||
# create an application
|
||||
app = CementApp('example', base_controller=MyAppBaseController)
|
||||
|
||||
try:
|
||||
# setup the application
|
||||
app.setup()
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'example'
|
||||
base_controller = MyAppBaseController
|
||||
|
||||
# run the application
|
||||
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
# close the application
|
||||
app.close()
|
||||
|
||||
|
||||
As you can see, we're able to build out the core functionality of our app
|
||||
|
||||
@ -60,22 +60,20 @@ The following example shows how to alter these settings for your application:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.ext import CementExtensionHandler
|
||||
|
||||
class MyExtensionHandler(CementExtensionHandler):
|
||||
pass
|
||||
|
||||
app = foundation.CementApp('myapp',
|
||||
extension_handler = MyExtensionHandler
|
||||
extensions = ['myapp.ext.ext_something_fancy']
|
||||
)
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
extension_handler = MyExtensionHandler
|
||||
extensions = ['myapp.ext.ext_something_fancy']
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
Creating an Extension
|
||||
@ -145,17 +143,15 @@ in ``myapp/ext/ext_something_fancy.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
app = foundation.CementApp('myapp',
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
extensions = ['myapp.ext.ext_something_fancy']
|
||||
)
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
Note that Cement provides a shortcut for Cement extensions. For example, the
|
||||
@ -163,14 +159,14 @@ following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = foundation.CementApp('myapp', extensions=['json', 'daemon'])
|
||||
CementApp('myapp', extensions=['json', 'daemon'])
|
||||
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = foundation.CementApp('myapp',
|
||||
CementApp('myapp',
|
||||
extensions=[
|
||||
'cement.ext.ext_json',
|
||||
'cement.ext.ext_daemon',
|
||||
@ -211,12 +207,8 @@ configuration files are loaded).
|
||||
]
|
||||
|
||||
def main():
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -22,60 +22,47 @@ API Reference:
|
||||
Defining a Hook
|
||||
---------------
|
||||
|
||||
A hook can be defined as follows:
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, hook
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core import hook
|
||||
|
||||
# First create the application
|
||||
app = foundation.CementApp('myapp')
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
|
||||
# Then define any application hooks
|
||||
hook.define('my_example_hook')
|
||||
def setup(self):
|
||||
# always run core setup first
|
||||
super(MyApp, self).setup()
|
||||
|
||||
# Setup the application
|
||||
app.setup()
|
||||
# define application hooks here
|
||||
hook.define('my_example_hook')
|
||||
|
||||
|
||||
Hooks should be defined as early on in the bootstrap process as possible,
|
||||
after the CementApp() is instantiated, but before 'app.setup()' is called.
|
||||
|
||||
|
||||
Registering Functions to a Hook
|
||||
-------------------------------
|
||||
|
||||
A hook is just an identifier, but the functions registered to that hook are
|
||||
what get run when the hook is called. Registering a hook function should also
|
||||
be done early on in the bootstrap process (before the hook is called
|
||||
obviously).
|
||||
be done early on in the bootstrap process, any time after the application has
|
||||
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).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, hook
|
||||
from cement.core import hook
|
||||
|
||||
# First create the application
|
||||
app = foundation.CementApp('myapp')
|
||||
|
||||
# Then define any application hooks
|
||||
hook.define('my_example_hook')
|
||||
|
||||
def my_func():
|
||||
# do something
|
||||
something = "The result of my hook."
|
||||
return something
|
||||
|
||||
def my_other_func():
|
||||
# do something
|
||||
def my_func(app):
|
||||
pass
|
||||
|
||||
# Register any hook functions. In the real world, this would likely be
|
||||
# done elsewhere in the application such as in plugins.
|
||||
hook.register('my_example_hook', my_func)
|
||||
hook.register('my_example_hook', my_other_func)
|
||||
|
||||
# Setup the application
|
||||
app.setup()
|
||||
with CementApp('myapp') as app:
|
||||
hook.register('my_example_hook', my_func)
|
||||
app.run()
|
||||
|
||||
|
||||
What you return depends on what the developer defining the hook is expecting.
|
||||
@ -108,10 +95,11 @@ That said, this is how you run a hook:
|
||||
# do something with res?
|
||||
pass
|
||||
|
||||
|
||||
As you can see we iterate over the hook, rather than just calling
|
||||
``hook.run()``. This is necessary because ``hook.run()`` yields the results
|
||||
from each hook as they are run. Hooks can be run anywhere *after* the hook is
|
||||
defined, and hooks are registered to that hook.
|
||||
from each hook function as they are run. Hooks can be run anywhere *after*
|
||||
the hook is defined, and hooks are registered to that hook.
|
||||
|
||||
|
||||
Controlling Hook Run Order
|
||||
@ -127,62 +115,55 @@ a custom application hook:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, controller, handler, hook
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core import handler, hook
|
||||
|
||||
# define an application base controller
|
||||
class MyAppBaseController(controller.CementBaseController):
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
interface = controller.IController
|
||||
label = 'base'
|
||||
description = "My Application does amazing things!"
|
||||
label = 'myapp'
|
||||
|
||||
config_defaults = {}
|
||||
arguments = []
|
||||
def setup(self):
|
||||
# always run core setup
|
||||
super(MyApp, self).setup()
|
||||
|
||||
@controller.expose(hide=True, aliases=['run'])
|
||||
def default(self):
|
||||
for res in hook.run('myapp_default_command_hook', self.app):
|
||||
pass
|
||||
# define hooks in setup
|
||||
hook.define('my_hook')
|
||||
|
||||
# create an application
|
||||
app = foundation.CementApp('myapp', base_controller=MyAppBaseController)
|
||||
|
||||
# define a hook
|
||||
hook.define('my_hook')
|
||||
|
||||
# the following are the function that will run when ``my_hook`` is called
|
||||
def func1(app):
|
||||
print 'Inside func1 of %s.' % app.name
|
||||
print 'Inside hook func1'
|
||||
|
||||
def func2(app):
|
||||
print 'Inside func2 of %s.' % app.name
|
||||
print 'Inside hook func2'
|
||||
|
||||
def func3(app):
|
||||
print 'Inside func3 of %s.' % app.name
|
||||
print 'Inside hook func3'
|
||||
|
||||
# register some hook functions
|
||||
hook.register('my_hook', func1, weight=0)
|
||||
hook.register('my_hook', func2, weight=100)
|
||||
hook.register('my_hook', func3, weight=-99)
|
||||
|
||||
try:
|
||||
# setup the application
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
# register all hook functions *after* the hook is defined (setup) but
|
||||
# also *before* the hook is called (different for every hook)
|
||||
hook.register('my_hook', func1, weight=0)
|
||||
hook.register('my_hook', func2, weight=100)
|
||||
hook.register('my_hook', func3, weight=-99)
|
||||
|
||||
# run the application
|
||||
app.run()
|
||||
|
||||
finally:
|
||||
# close the application
|
||||
app.close()
|
||||
# run our custom hook
|
||||
for res in hook.run('my_hook', app):
|
||||
pass
|
||||
|
||||
|
||||
And the result is:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python test.py
|
||||
Inside func3 of myapp.
|
||||
Inside func1 of myapp.
|
||||
Inside func2 of myapp.
|
||||
$ python my_hook_test.py
|
||||
Inside hook func3
|
||||
Inside hook func1
|
||||
Inside hook func2
|
||||
|
||||
|
||||
As you can see, it doesn’t matter what order we register the hook, the
|
||||
|
||||
@ -377,26 +377,17 @@ already setup by Cement, but we're putting it here for clarity:
|
||||
)
|
||||
|
||||
|
||||
# create the app
|
||||
app = MyApp()
|
||||
|
||||
try:
|
||||
# setup the app
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
# run the application
|
||||
app.run()
|
||||
|
||||
# define some data for the output handler
|
||||
data = dict(foo='bar')
|
||||
|
||||
# run the app
|
||||
app.run()
|
||||
|
||||
# render something using our output handlers, using mustache by
|
||||
# default which use the default.m template
|
||||
# render something using out output handlers, using mustache by
|
||||
# default which will use the default.m templae
|
||||
app.render(data, 'default.m')
|
||||
|
||||
finally:
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
|
||||
Note what we see at command line:
|
||||
|
||||
@ -31,10 +31,7 @@ Example Usage
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
|
||||
app = MyApp()
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
|
||||
# send an email message
|
||||
@ -46,9 +43,6 @@ Example Usage
|
||||
bcc=['boss@example.com']
|
||||
)
|
||||
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
Note that the default mail handler simply prints messages to the screen, and
|
||||
does not actually send anything. You can override this pretty easily without
|
||||
|
||||
@ -132,12 +132,8 @@ the Mustache templating langugage, as well as Json output handling.
|
||||
output_handler = 'mustache'
|
||||
|
||||
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
**/usr/lib/myapp/templates/default.m**
|
||||
|
||||
@ -206,12 +206,8 @@ same if not defined:
|
||||
|
||||
|
||||
def main():
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -1,38 +1,32 @@
|
||||
Quick Start
|
||||
===========
|
||||
|
||||
The following outlines creating a sample 'helloworld' application.
|
||||
The following creates and runs a sample 'helloworld' application.
|
||||
|
||||
*helloworld.py*
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
app = foundation.CementApp('helloworld')
|
||||
try:
|
||||
app.setup()
|
||||
with CementApp('helloworld') as app:
|
||||
app.run()
|
||||
print('Hello World')
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
Note that `app.close()` by default does not `exit` the application, but you
|
||||
can easily do that here also:
|
||||
|
||||
The above is equivalent to (should you need more control over setup and
|
||||
closing an application):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# non-error exit status is generally 0
|
||||
app.close(0)
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
# or exit with an error
|
||||
app.close(1)
|
||||
app = CementApp('helloworld')
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close()
|
||||
|
||||
|
||||
If an `exit code` is passed to `app.close()` then Cement with call
|
||||
`sys.exit(code)` at the end of execution.
|
||||
|
||||
And running the application looks like:
|
||||
Running the application looks like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
@ -125,43 +119,44 @@ config creation, and logging.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, hook
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core import hook
|
||||
from cement.utils.misc import init_defaults
|
||||
|
||||
# set default config options
|
||||
# define our default configuration options
|
||||
defaults = init_defaults('myapp')
|
||||
defaults['myapp']['debug'] = False
|
||||
defaults['myapp']['foo'] = 'bar'
|
||||
defaults['myapp']['some_param'] = 'some value'
|
||||
|
||||
# create an application
|
||||
app = foundation.CementApp('myapp', config_defaults=defaults)
|
||||
# define any hook functions here
|
||||
def my_cleanup_hook(app):
|
||||
pass
|
||||
|
||||
# register any framework hook functions after app creation, and before
|
||||
# app.setup()
|
||||
def my_hook(app):
|
||||
assert 'foo' in app.config.keys('myapp')
|
||||
# define the application class
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
config_defaults = defaults
|
||||
extensions = ['daemon', memcached', 'json', 'yaml']
|
||||
|
||||
hook.register('post_setup', my_hook)
|
||||
with MyApp() as app:
|
||||
# register framework or custom application hooks
|
||||
hook.register('pre_close', my_cleanup_hook)
|
||||
|
||||
try:
|
||||
# setup the application
|
||||
app.setup()
|
||||
|
||||
# add arguments
|
||||
# add arguments to the parser
|
||||
app.args.add_argument('-f', '--foo', action='store', metavar='STR',
|
||||
help='the notorious foo option')
|
||||
|
||||
# log stuff
|
||||
app.log.debug("About to run my myapp application!")
|
||||
|
||||
# run the application
|
||||
app.run()
|
||||
|
||||
# add application logic
|
||||
# continue with additional application logic
|
||||
if app.pargs.foo:
|
||||
app.log.info("Received the 'foo' option with value '%s'." % app.pargs.foo)
|
||||
else:
|
||||
app.log.warn("Did not receive a value for 'foo' option.")
|
||||
app.log.info("Received option: foo => %s" % app.pargs.foo)
|
||||
|
||||
finally:
|
||||
# close the application
|
||||
app.close()
|
||||
|
||||
And execution:
|
||||
|
||||
@ -177,7 +172,7 @@ And execution:
|
||||
-f STR, --foo STR the notorious foo option
|
||||
|
||||
$ python myapp.py --foo=bar
|
||||
INFO: Received the 'foo' option with value 'bar'.
|
||||
INFO: Received option: foo => bar
|
||||
|
||||
|
||||
Diving Right In
|
||||
@ -190,53 +185,52 @@ handle command dispatch and rapid development.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import backend, foundation, controller, handler
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
# define an application base controller
|
||||
class MyAppBaseController(controller.CementBaseController):
|
||||
class MyBaseController(CementBaseController):
|
||||
class Meta:
|
||||
label = 'base'
|
||||
description = "My Application does amazing things!"
|
||||
|
||||
config_defaults = dict(
|
||||
foo='bar',
|
||||
some_other_option='my default value',
|
||||
)
|
||||
|
||||
arguments = [
|
||||
(['-f', '--foo'], dict(action='store', help='the notorious foo option')),
|
||||
(['-C'], dict(action='store_true', help='the big C option'))
|
||||
( ['-f', '--foo'],
|
||||
dict(action='store', help='the notorious foo option') ),
|
||||
( ['-C'],
|
||||
dict(action='store_true', help='the big C option') ),
|
||||
]
|
||||
|
||||
@controller.expose(hide=True, aliases=['run'])
|
||||
@expose(hide=True)
|
||||
def default(self):
|
||||
self.app.log.info('Inside base.default function.')
|
||||
self.app.log.info('Inside MyBaseController.default()')
|
||||
if self.app.pargs.foo:
|
||||
self.app.log.info("Recieved option 'foo' with value '%s'." % \
|
||||
self.pargs.foo)
|
||||
print("Recieved option: foo => %s" % self.app.pargs.foo)
|
||||
|
||||
@controller.expose(help="this command does relatively nothing useful.")
|
||||
@expose(help="this command does relatively nothing useful")
|
||||
def command1(self):
|
||||
self.app.log.info("Inside base.command1 function.")
|
||||
self.app.log.info("Inside MyBaseController.command1()")
|
||||
|
||||
@controller.expose(aliases=['cmd2'], help="more of nothing.")
|
||||
@expose(aliases=['cmd2'], help="more of nothing")
|
||||
def command2(self):
|
||||
self.app.log.info("Inside base.command2 function.")
|
||||
self.app.log.info("Inside MyBaseController.command2()")
|
||||
|
||||
# define a second controller
|
||||
class MySecondController(controller.CementBaseController):
|
||||
|
||||
class MySecondController(CementBaseController):
|
||||
class Meta:
|
||||
label = 'secondary'
|
||||
label = 'second'
|
||||
stacked_on = 'base'
|
||||
|
||||
@controller.expose(help='this is some command', aliases=['some-cmd'])
|
||||
def some_other_command(self):
|
||||
pass
|
||||
@expose(help='this is some command', aliases=['some-cmd'])
|
||||
def second_cmd1(self):
|
||||
self.app.log.info("Inside MySecondController.second_cmd1")
|
||||
|
||||
class MyApp(foundation.CementApp):
|
||||
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'helloworld'
|
||||
base_controller = MyAppBaseController
|
||||
label = 'myapp'
|
||||
base_controller = MyBaseController
|
||||
|
||||
|
||||
|
||||
# create the app
|
||||
app = MyApp()
|
||||
@ -244,15 +238,15 @@ handle command dispatch and rapid development.
|
||||
# Register any handlers that aren't passed directly to CementApp
|
||||
handler.register(MySecondController)
|
||||
|
||||
try:
|
||||
# setup the application
|
||||
app.setup()
|
||||
# setup the application
|
||||
app.setup()
|
||||
|
||||
# run the application
|
||||
app.run()
|
||||
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
# run the application
|
||||
app.run()
|
||||
finally:
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
As you can see, we're able to build out the core functionality of our app such
|
||||
as arguments and sub-commands via controller classes.
|
||||
@ -269,10 +263,10 @@ Lets see what this looks like:
|
||||
commands:
|
||||
|
||||
command1
|
||||
this command does relatively nothing useful.
|
||||
this command does relatively nothing useful
|
||||
|
||||
command2 (aliases: cmd2)
|
||||
more of nothing.
|
||||
more of nothing
|
||||
|
||||
some-other-command (aliases: some-cmd)
|
||||
this is some command
|
||||
@ -284,11 +278,14 @@ Lets see what this looks like:
|
||||
-f FOO, --foo FOO the notorious foo option
|
||||
-C the big C option
|
||||
|
||||
$ python myapp.py
|
||||
INFO: Inside MyBaseController.default()
|
||||
|
||||
$ python myapp.py command1
|
||||
INFO: Inside base.command1 function.
|
||||
INFO: Inside MyBaseController.command1()
|
||||
|
||||
$ python myapp.py command2
|
||||
INFO: Inside base.command2 function.
|
||||
INFO: Inside MyBaseController.command2()
|
||||
|
||||
$ python myapp.py cmd2
|
||||
INFO: Inside base.command2 function.
|
||||
$ python myapp.py second-cmd1
|
||||
INFO: Inside MySecondController.second_cmd1()
|
||||
|
||||
@ -49,76 +49,61 @@ A basic application using default handling might look like:
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement.core import foundation, exc
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.exc import CaughtSignal
|
||||
|
||||
app = foundation.CementApp('myapp')
|
||||
with CementApp('myapp') as app:
|
||||
try:
|
||||
app.run()
|
||||
except CaughtSignal as e:
|
||||
# do something with e.signum or e.frame (passed from signal)
|
||||
if e.signum == signal.SIGTERM:
|
||||
print("Caught SIGTERM...")
|
||||
elif e.signum == signal.SIGINT:
|
||||
print("Caught SIGINT...")
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
app.run()
|
||||
except exc.CaughtSignal as e:
|
||||
# do something with e.signum or e.frame (passed from signal library)
|
||||
|
||||
if e.signum == signal.SIGTERM:
|
||||
print("Caught signal SIGTERM...")
|
||||
# do something to handle signal here
|
||||
elif e.signum == signal.SIGINT:
|
||||
print("Caught signal SIGINT...")
|
||||
# do something to handle signal here
|
||||
finally:
|
||||
app.close()
|
||||
The above provides a very simple means of handling the most common
|
||||
signals, which in turns allowes our application to "exit clean" by running
|
||||
``app.close()`` and any ``pre_close`` or ``post_close`` hooks. If we don't
|
||||
catch the signals, then the exceptions will be unhandled and the application
|
||||
will not exit clean.
|
||||
|
||||
As you can see, this provides a very simple means of handling the most common
|
||||
signals allowing us to still call app.close() after handling the signal. This
|
||||
is extremely important as 'app.close()' is where the 'pre_close' and
|
||||
'post_close' hooks are called, allowing the
|
||||
framework/application/extensions/plugins to all perform any cleanup actions
|
||||
they may need.
|
||||
|
||||
Using The Signal Hook
|
||||
---------------------
|
||||
|
||||
An alternative way of adding multiple callbacks to a signal handler is by
|
||||
using the cement signal hook. This hook is called anytime a handled signal
|
||||
using the Cement signal hook. This hook is called anytime a handled signal
|
||||
is encountered.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement.core import foundation, exc, hook
|
||||
|
||||
app = foundation.CementApp('myapp')
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.exc import CaughtSignal
|
||||
from cement.core import hook
|
||||
|
||||
def my_signal_handler(signum, frame):
|
||||
# do something with signum/frame
|
||||
|
||||
if signum == signal.SIGTERM:
|
||||
print("Caught signal SIGTERM...")
|
||||
# do something to handle signal here
|
||||
print("Caught SIGTERM...")
|
||||
elif signum == signal.SIGINT:
|
||||
print("Caught signal SIGINT...")
|
||||
# do something to handle signal here
|
||||
print("Caught SIGINT...")
|
||||
|
||||
hook.register('signal', my_signal_handler)
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
with CementApp('myapp') as app:
|
||||
hook.register('signal', my_signal_handler)
|
||||
app.run()
|
||||
except exc.CaughtSignal as e:
|
||||
pass
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
The key thing to note here is that the main application itself handles the
|
||||
exc.CaughtSignal exception, where as using the cement 'signal' hook is
|
||||
``CaughtSignal`` exception, where as using the cement ``signal`` hook is
|
||||
useful for plugins and extensions to be able to tie into the signal handling
|
||||
outside of the main application. Both serve the same purpose however the
|
||||
application object is not available (passed to) the cement 'signal' hook which
|
||||
limits what can be done within the callback function. For this reason
|
||||
any extensions or plugins should use the 'pre_close' hook as much as
|
||||
possible as it is always run when app.close() is called and receives the
|
||||
app object as one of its parameters.
|
||||
application object is not available (passed to) the cement ``signal`` hook
|
||||
which limits what can be done within the callback function. For this reason
|
||||
any extensions or plugins should use the ``pre_close`` hook as much as
|
||||
possible as it is always run when ``app.close()`` is called and receives the
|
||||
``app`` object as one of its parameters.
|
||||
|
||||
|
||||
Configuring Which Signals To Catch
|
||||
@ -130,19 +115,21 @@ foundation.CementApp():
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement.core import foundation, exc
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
|
||||
|
||||
app = foundation.CementApp('myapp', catch_signals=SIGNALS)
|
||||
CementApp('myapp', catch_signals=SIGNALS)
|
||||
...
|
||||
|
||||
|
||||
What happens is, Cement iterates over the catch_signals list and adds a
|
||||
generic handler function (the same) for each signal. Because the handler
|
||||
calls the cement 'signal' hook, and then raises an exception which both pass the
|
||||
'signum' and 'frame' parameters, you are able to handle the logic elsewhere
|
||||
rather than assigning a unique callback function for every signal.
|
||||
|
||||
|
||||
What If I Don't Like Your Signal Handler Callback?
|
||||
--------------------------------------------------
|
||||
|
||||
@ -154,33 +141,34 @@ callback.
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement.core import foundation, exc, hook
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core import hook
|
||||
|
||||
SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
|
||||
|
||||
def my_signal_handler(signum, frame):
|
||||
# do something with signum/frame
|
||||
print 'Caught signal %s' % signum
|
||||
|
||||
# execute the cement signal hook
|
||||
for res in hook.run('signal', signum, frame):
|
||||
pass
|
||||
|
||||
app = foundation.CementApp('myapp',
|
||||
catch_signals=SIGNALS,
|
||||
signal_handler=my_signal_handler)
|
||||
...
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
catch_signals = SIGNALS
|
||||
signal_handler = my_signal_handler
|
||||
|
||||
|
||||
|
||||
This Is Stupid, and UnPythonic - How Do I Disable It?
|
||||
-----------------------------------------------------
|
||||
|
||||
To each their own. If you simply do not want any kind of signal handling
|
||||
performed, just set 'catch_signals=None'.
|
||||
performed, just set ``catch_signals=None``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement.core import foundation, exc
|
||||
from cement.core.foundation import foundation
|
||||
|
||||
app = foundation.CementApp('myapp', catch_signals=None)
|
||||
CementApp('myapp', catch_signals=None)
|
||||
|
||||
@ -12,8 +12,10 @@ still maintain the existing shared commands (or override them as necessary).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, handler
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
|
||||
class AbstractBaseController(CementBaseController):
|
||||
"""
|
||||
@ -95,18 +97,16 @@ still maintain the existing shared commands (or override them as necessary).
|
||||
print("Inside Controller2.command2()")
|
||||
|
||||
def main():
|
||||
app = foundation.CementApp('myapp')
|
||||
app = CementApp('myapp')
|
||||
|
||||
try:
|
||||
# register controllers handlers
|
||||
handler.register(MyAppBaseController)
|
||||
handler.register(Controller1)
|
||||
handler.register(Controller2)
|
||||
# register controllers handlers
|
||||
handler.register(MyAppBaseController)
|
||||
handler.register(Controller1)
|
||||
handler.register(Controller2)
|
||||
|
||||
app.setup()
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -31,12 +31,9 @@ include, so we've added an example below.
|
||||
base_controller = MyBaseController
|
||||
|
||||
|
||||
app = MyApp()
|
||||
try:
|
||||
app.setup()
|
||||
with MyApp() as app:
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
This looks like:
|
||||
|
||||
|
||||
@ -73,11 +73,9 @@ Example
|
||||
app = MyApp()
|
||||
handler.register(MySecondController)
|
||||
|
||||
try:
|
||||
app.setup()
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
app.setup()
|
||||
app.run()
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -23,8 +23,9 @@ sub-commands, that are implemented via nested-controllers.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, handler
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
class BaseController(CementBaseController):
|
||||
class Meta:
|
||||
@ -79,31 +80,30 @@ sub-commands, that are implemented via nested-controllers.
|
||||
def third_cmd7(self):
|
||||
print("Inside ThirdLevelController.third_cmd7()")
|
||||
|
||||
class MyApp(foundation.CementApp):
|
||||
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
# create the app
|
||||
app = MyApp()
|
||||
# create the app
|
||||
app = MyApp()
|
||||
|
||||
# register controllers to the app
|
||||
handler.register(BaseController)
|
||||
handler.register(EmbeddedController)
|
||||
handler.register(SecondLevelController)
|
||||
handler.register(ThirdLevelController)
|
||||
# register controllers to the app
|
||||
handler.register(BaseController)
|
||||
handler.register(EmbeddedController)
|
||||
handler.register(SecondLevelController)
|
||||
handler.register(ThirdLevelController)
|
||||
|
||||
# setup the app
|
||||
app.setup()
|
||||
# setup the app
|
||||
app.setup()
|
||||
|
||||
# run the app
|
||||
app.run()
|
||||
# run the app
|
||||
app.run()
|
||||
|
||||
finally:
|
||||
# close the app
|
||||
app.close()
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -26,8 +26,9 @@ and a 'hosts' controller and we want to have a 'list' sub-command under both:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, handler
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
# define application controllers
|
||||
class MyAppBaseController(CementBaseController):
|
||||
@ -75,25 +76,24 @@ and a 'hosts' controller and we want to have a 'list' sub-command under both:
|
||||
print "Inside HostsListController.default()"
|
||||
|
||||
def main():
|
||||
try:
|
||||
# create the application
|
||||
app = foundation.CementApp('myapp')
|
||||
# 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)
|
||||
# register non-base controllers
|
||||
handler.register(MyAppBaseController)
|
||||
handler.register(UsersController)
|
||||
handler.register(HostsController)
|
||||
handler.register(UsersListController)
|
||||
handler.register(HostsListController)
|
||||
|
||||
# setup the application
|
||||
app.setup()
|
||||
# setup the application
|
||||
app.setup()
|
||||
|
||||
# run it
|
||||
app.run()
|
||||
finally:
|
||||
# close it
|
||||
app.close()
|
||||
# run it
|
||||
app.run()
|
||||
|
||||
# close it
|
||||
app.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -5,8 +5,9 @@ Multiple Stacked Controllers
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core import foundation, controller, handler
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import handler
|
||||
|
||||
# define application controllers
|
||||
class MyAppBaseController(CementBaseController):
|
||||
@ -74,7 +75,7 @@ Multiple Stacked Controllers
|
||||
def main():
|
||||
try:
|
||||
# create the application
|
||||
app = foundation.CementApp('myapp')
|
||||
app = CementApp('myapp')
|
||||
|
||||
# register controllers
|
||||
handler.register(MyAppBaseController)
|
||||
|
||||
@ -16,50 +16,50 @@ class CementDevtoolsController(CementBaseController):
|
||||
class Meta:
|
||||
label = 'base'
|
||||
arguments = [
|
||||
(['-y, --noprompt'],
|
||||
dict(help='answer yes to prompts.', action='store_true',
|
||||
(['-y, --noprompt'],
|
||||
dict(help='answer yes to prompts.', action='store_true',
|
||||
dest='noprompt')),
|
||||
(['--ignore-errors'],
|
||||
dict(help="don't stop operations because of errors",
|
||||
dict(help="don't stop operations because of errors",
|
||||
action='store_true', dest='ignore_errors')),
|
||||
(['--loud'], dict(help='add more verbose output',
|
||||
(['--loud'], dict(help='add more verbose output',
|
||||
action='store_true', dest='loud')),
|
||||
(['modifier1'],
|
||||
(['modifier1'],
|
||||
dict(help='command modifier positional argument', nargs='?')),
|
||||
]
|
||||
|
||||
|
||||
def _do_error(self, msg):
|
||||
if self.app.pargs.ignore_errors:
|
||||
self.app.log.error(msg)
|
||||
else:
|
||||
raise Exception(msg)
|
||||
|
||||
@expose(hide=True)
|
||||
|
||||
@expose(hide=True)
|
||||
def default(self):
|
||||
raise AssertionError("A sub-command is required. See --help.")
|
||||
|
||||
|
||||
def _do_git(self):
|
||||
# make sure we don't have any uncommitted changes
|
||||
print('Checking for Uncommitted Changes')
|
||||
out, err, res = shell.exec_cmd(['git', '--no-pager', 'diff'])
|
||||
if len(out) > 0:
|
||||
self._do_error('There are uncommitted changes. See `git status`.')
|
||||
|
||||
|
||||
# make sure we don't have any un-added files
|
||||
print('Checking for Untracked Files')
|
||||
out, err, res = shell.exec_cmd(['git', 'status'])
|
||||
if re.match('Untracked files', out):
|
||||
self._do_error('There are untracked files. See `git status`.')
|
||||
|
||||
|
||||
# make sure there isn't an existing tag
|
||||
print("Checking for Duplicate Git Tag")
|
||||
out, err, res = shell.exec_cmd(['git', 'tag'])
|
||||
for ver in out.split('\n'):
|
||||
if ver == VERSION:
|
||||
self._do_error("Tag %s already exists" % VERSION)
|
||||
|
||||
|
||||
print("Tagging Git Release")
|
||||
out, err, res = shell.exec_cmd(['git', 'tag', '-a', '-m', VERSION,
|
||||
out, err, res = shell.exec_cmd(['git', 'tag', '-a', '-m', VERSION,
|
||||
VERSION])
|
||||
if res > 0:
|
||||
self._do_error("Unable to tag release with git.")
|
||||
@ -67,7 +67,7 @@ class CementDevtoolsController(CementBaseController):
|
||||
def _do_tests(self):
|
||||
print('Running Nose Tests')
|
||||
out, err, res = shell.exec_cmd(['which', 'nosetests'])
|
||||
|
||||
|
||||
if self.app.pargs.loud:
|
||||
cmd_args = ['coverage', 'run', out.strip(), '--verbosity=3']
|
||||
res = shell.exec_cmd2(cmd_args)
|
||||
@ -77,7 +77,7 @@ class CementDevtoolsController(CementBaseController):
|
||||
if res > 0:
|
||||
self._do_error("\n\nNose tests did not pass.\n\n" +
|
||||
"$ %s\n%s" % (' '.join(cmd_args), err))
|
||||
|
||||
|
||||
def _do_pep8(self):
|
||||
print("Checking PEP8 Compliance")
|
||||
cmd_args = ['pep8', '-r', 'cement/', '--exclude=*.pyc']
|
||||
@ -105,7 +105,7 @@ class CementDevtoolsController(CementBaseController):
|
||||
if res > 0:
|
||||
self._do_error("\n\nFailed to build sphinx documentation\n\n" +
|
||||
"$ %s\n%s" % (' '.join(cmd_args), out))
|
||||
|
||||
|
||||
@expose(help='create a cement release')
|
||||
def make_release(self):
|
||||
print('')
|
||||
@ -115,26 +115,26 @@ class CementDevtoolsController(CementBaseController):
|
||||
res = raw_input("Continue? [yN] ")
|
||||
if res not in ['Y', 'y', '1']:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
tmp = tempfile.mkdtemp()
|
||||
print("Destination: %s" % tmp)
|
||||
os.makedirs(os.path.join(tmp, 'source'))
|
||||
os.makedirs(os.path.join(tmp, 'doc'))
|
||||
|
||||
|
||||
self._do_pep8()
|
||||
self._do_tests()
|
||||
self._do_git()
|
||||
self._do_sphinx(os.path.join(tmp, 'doc'))
|
||||
|
||||
|
||||
tar_path = os.path.join(tmp, 'source', 'cement-%s.tar' % VERSION)
|
||||
gzip_path = "%s.gz" % tar_path
|
||||
|
||||
|
||||
print("Generating Release Files")
|
||||
cmd_args = ['git', 'archive', VERSION,
|
||||
cmd_args = ['git', 'archive', VERSION,
|
||||
'--prefix=cement-%s/' % VERSION,
|
||||
'--output=%s' % tar_path]
|
||||
out, err, res = shell.exec_cmd(cmd_args)
|
||||
|
||||
|
||||
cmd_args = ['gzip', tar_path]
|
||||
out, err, res = shell.exec_cmd(cmd_args)
|
||||
if res > 0:
|
||||
@ -142,26 +142,23 @@ class CementDevtoolsController(CementBaseController):
|
||||
"$ %s" % (' '.join(cmd_args), err))
|
||||
|
||||
print('')
|
||||
|
||||
|
||||
@expose(help='get the current version of the sources')
|
||||
def get_version(self):
|
||||
print(VERSION)
|
||||
|
||||
|
||||
class CementDevtoolsApp(CementApp):
|
||||
class Meta:
|
||||
label = 'cement-devtools'
|
||||
base_controller = CementDevtoolsController
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
app = CementDevtoolsApp('cement-devtools')
|
||||
try:
|
||||
app.setup()
|
||||
app.run()
|
||||
except AssertionError as e:
|
||||
print("AssertionError => %s" % e.args[0])
|
||||
finally:
|
||||
app.close()
|
||||
with CementDevtoolsApp() as app:
|
||||
try:
|
||||
app.run()
|
||||
except AssertionError as e:
|
||||
print("AssertionError => %s" % e.args[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
@ -281,12 +281,17 @@ class FoundationTestCase(test.CementCoreTestCase):
|
||||
self.eq({'foo':'bar'}, last_data)
|
||||
self.eq(output_text, last_output)
|
||||
|
||||
def test_with_operator(self):
|
||||
with self.app_class() as app:
|
||||
app.run()
|
||||
|
||||
@test.raises(SystemExit)
|
||||
def test_close_with_code(self):
|
||||
self.app.setup()
|
||||
self.app.run()
|
||||
app = self.make_app(APP, exit_on_close=True)
|
||||
app.setup()
|
||||
app.run()
|
||||
try:
|
||||
self.app.close(114)
|
||||
app.close(114)
|
||||
except SystemExit as e:
|
||||
self.eq(e.code, 114)
|
||||
raise
|
||||
|
||||
Loading…
Reference in New Issue
Block a user