mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 13:56:49 +00:00
Resolves Issue #342
This commit is contained in:
parent
64ef916389
commit
53c4ef3862
@ -47,7 +47,8 @@ Features:
|
||||
* :issue:`322` - Added Tabulate Framework Extension (tabulatized output)
|
||||
* :issue:`336` - Added Support for ``CementApp.reload()`` (SIGHUP)
|
||||
* :issue:`337` - Added ``app.run_forever()`` alternative run method.
|
||||
|
||||
* :issue:`342` - Added Alarm/Timeout Support
|
||||
|
||||
Refactoring:
|
||||
|
||||
* :issue:`311` - Refactor Hooks/Handlers into CementApp
|
||||
|
||||
@ -757,8 +757,8 @@ class CementApp(meta.MetaMixin):
|
||||
for res in self.hook.run('pre_setup', self):
|
||||
pass
|
||||
|
||||
self._setup_signals()
|
||||
self._setup_extension_handler()
|
||||
self._setup_signals()
|
||||
self._setup_config_handler()
|
||||
self._setup_mail_handler()
|
||||
self._setup_cache_handler()
|
||||
@ -1073,16 +1073,26 @@ class CementApp(meta.MetaMixin):
|
||||
for res in self.hook.run('post_argument_parsing', self):
|
||||
pass
|
||||
|
||||
def catch_signal(self, signum):
|
||||
"""
|
||||
Add ``signum`` to the list of signals to catch and handle by Cement.
|
||||
|
||||
:param signum: The signal number to catch. See Python ``signal``
|
||||
library.
|
||||
"""
|
||||
|
||||
LOG.debug("adding signal handler %s for signal %s" % (
|
||||
self._meta.signal_handler, signum)
|
||||
)
|
||||
signal.signal(signum, self._meta.signal_handler)
|
||||
|
||||
def _setup_signals(self):
|
||||
if self._meta.catch_signals is None:
|
||||
LOG.debug("catch_signals=None... not handling any signals")
|
||||
return
|
||||
|
||||
for signum in self._meta.catch_signals:
|
||||
LOG.debug("adding signal handler %s for signal %s" % (
|
||||
self._meta.signal_handler, signum)
|
||||
)
|
||||
signal.signal(signum, self._meta.signal_handler)
|
||||
self.catch_signal(signum)
|
||||
|
||||
def _resolve_handler(self, handler_type, handler_def, raise_error=True):
|
||||
han = self.handler.resolve(handler_type, handler_def, raise_error)
|
||||
|
||||
93
cement/ext/ext_alarm.py
Normal file
93
cement/ext/ext_alarm.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""
|
||||
The Alarm Extension provides easy access to setting an application alarm to
|
||||
handle timing out operations. See the
|
||||
`Python Signal Library <https://docs.python.org/3.5/library/signal.html>`_.
|
||||
|
||||
Availability: Unix
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import time
|
||||
from cement.core.foundation import CementApp
|
||||
from cement.core.exc import CaughtSignal
|
||||
|
||||
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
exit_on_close = True
|
||||
extensions = ['alarm']
|
||||
|
||||
|
||||
with MyApp() as app:
|
||||
try:
|
||||
app.run()
|
||||
app.alarm.set(3, "The operation timed out after 3 seconds!")
|
||||
|
||||
# do something that takes time to operate
|
||||
time.sleep(5)
|
||||
|
||||
app.alarm.stop()
|
||||
|
||||
except CaughtSignal as e:
|
||||
print(e.msg)
|
||||
app.exit_code = 1
|
||||
|
||||
Looks like:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python myapp.py
|
||||
ERROR: The operation timed out after 3 seconds!
|
||||
Caught signal 14
|
||||
|
||||
"""
|
||||
|
||||
import signal
|
||||
from ..utils.misc import minimal_logger
|
||||
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
def alarm_handler(app, signum, frame):
|
||||
if signum == signal.SIGALRM:
|
||||
app.log.error(app.alarm.msg)
|
||||
|
||||
class AlarmManager(object):
|
||||
"""
|
||||
Lets the developer easily set and stop an alarm. If the
|
||||
alarm exceeds the given time it will raise ``signal.SIGALRM``.
|
||||
|
||||
"""
|
||||
def __init__(self, *args, **kw):
|
||||
super(AlarmManager, self).__init__(*args, **kw)
|
||||
self.msg = None
|
||||
|
||||
def set(self, time, msg):
|
||||
"""
|
||||
Set the application alarm to ``time`` seconds. If the time is
|
||||
exceeded ``signal.SIGALRM`` is raised.
|
||||
|
||||
:param time: The time in seconds to set the alarm to.
|
||||
:param msg: The message to display if the alarm is triggered.
|
||||
"""
|
||||
|
||||
LOG.debug('setting application alarm for %s seconds' % time)
|
||||
self.msg = msg
|
||||
signal.alarm(int(time))
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the application alarm.
|
||||
"""
|
||||
LOG.debug('stopping application alarm')
|
||||
signal.alarm(0)
|
||||
|
||||
|
||||
def load(app):
|
||||
app.catch_signal(signal.SIGALRM)
|
||||
app.extend('alarm', AlarmManager())
|
||||
app.hook.register('signal', alarm_handler)
|
||||
|
||||
9
doc/source/api/ext/ext_alarm.rst
Normal file
9
doc/source/api/ext/ext_alarm.rst
Normal file
@ -0,0 +1,9 @@
|
||||
.. _cement.ext.ext_alarm:
|
||||
|
||||
:mod:`cement.ext.ext_alarm`
|
||||
---------------------------
|
||||
|
||||
.. automodule:: cement.ext.ext_alarm
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
||||
@ -47,6 +47,7 @@ Cement Extension Modules
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
ext/ext_alarm
|
||||
ext/ext_argcomplete
|
||||
ext/ext_argparse
|
||||
ext/ext_colorlog
|
||||
|
||||
@ -11,6 +11,7 @@ from cement.core.controller import CementBaseController, expose
|
||||
from cement.core import log, output, hook, arg, controller
|
||||
from cement.core.interface import Interface
|
||||
from cement.utils import test
|
||||
from cement.core.exc import CaughtSignal
|
||||
from cement.utils.misc import init_defaults, rando, minimal_logger
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
@ -491,4 +492,4 @@ class FoundationTestCase(test.CementCoreTestCase):
|
||||
self.eq(e.args[0], 'It ran forever!')
|
||||
raise
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
signal.alarm(0)
|
||||
|
||||
35
tests/ext/alarm_tests.py
Normal file
35
tests/ext/alarm_tests.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Tests for cement.ext.ext_alarm."""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
from cement.core.exc import CaughtSignal
|
||||
from cement.utils import test
|
||||
|
||||
|
||||
class AlarmExtTestCase(test.CementExtTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.app = self.make_app('tests',
|
||||
extensions=['alarm'],
|
||||
argv=[]
|
||||
)
|
||||
|
||||
@test.raises(CaughtSignal)
|
||||
def test_alarm_timeout(self):
|
||||
global app
|
||||
app = self.app
|
||||
with app as app:
|
||||
try:
|
||||
app.alarm.set(1, "The Timer Works!")
|
||||
time.sleep(3)
|
||||
except CaughtSignal as e:
|
||||
self.eq(e.signum, signal.SIGALRM)
|
||||
raise
|
||||
|
||||
def test_alarm_no_timeout(self):
|
||||
with self.app as app:
|
||||
app.alarm.set(3, "The Timer Works!")
|
||||
time.sleep(1)
|
||||
app.alarm.stop()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user