mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 15:56:47 +00:00
Resolves Issue #190
This commit is contained in:
parent
89d4eeff2a
commit
727ac6be44
4
.coveragerc
Normal file
4
.coveragerc
Normal file
@ -0,0 +1,4 @@
|
||||
[report]
|
||||
|
||||
# These are problematic for coverage reporting
|
||||
omit = cement/ext/ext_daemon.py
|
||||
@ -29,11 +29,12 @@ Bugs:
|
||||
|
||||
Features:
|
||||
|
||||
* :issue:`190` - Merge daemon extension into core
|
||||
* :issue:`194` - Added pre_argument_parsing/post_argument_parsing hooks
|
||||
* :issue:`196` - Added utils.misc.wrap
|
||||
* :issue:`200` - Merge ext.mustache into mainline.
|
||||
* :issue:`203` - Added support for external template directory
|
||||
|
||||
|
||||
Incompatible:
|
||||
|
||||
* :issue:`173` - Deprecated 'has_key()' from configparser extension
|
||||
|
||||
@ -23,6 +23,7 @@ def argument_validator(klass, obj):
|
||||
|
||||
# pylint: disable=W0105,W0232,W0232,R0903,E0213,R0923
|
||||
class IArgument(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Argument Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -41,6 +42,7 @@ class IArgument(interface.Interface):
|
||||
|
||||
"""
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data options."""
|
||||
|
||||
label = 'argument'
|
||||
@ -101,9 +103,11 @@ class IArgument(interface.Interface):
|
||||
|
||||
# pylint: disable=W0105
|
||||
class CementArgumentHandler(handler.CementBaseHandler):
|
||||
|
||||
"""Base class that all Argument Handlers should sub-class from."""
|
||||
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -20,6 +20,7 @@ def cache_validator(klass, obj):
|
||||
|
||||
|
||||
class ICache(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Cache Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -42,6 +43,7 @@ class ICache(interface.Interface):
|
||||
"""
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
|
||||
label = 'cache'
|
||||
@ -109,11 +111,13 @@ class ICache(interface.Interface):
|
||||
|
||||
|
||||
class CementCacheHandler(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
Base class that all Cache Handlers should sub-class from.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -21,6 +21,7 @@ def config_validator(klass, obj):
|
||||
|
||||
|
||||
class IConfig(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Config Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -51,6 +52,7 @@ class IConfig(interface.Interface):
|
||||
"""
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
label = 'config'
|
||||
"""The string identifier of the interface."""
|
||||
@ -169,11 +171,13 @@ class IConfig(interface.Interface):
|
||||
|
||||
|
||||
class CementConfigHandler(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
Base class that all Config Handlers should sub-class from.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -51,6 +51,7 @@ def controller_validator(klass, obj):
|
||||
|
||||
|
||||
class IController(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Controller Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -71,6 +72,7 @@ class IController(interface.Interface):
|
||||
"""
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
|
||||
label = 'controller'
|
||||
@ -112,6 +114,7 @@ class IController(interface.Interface):
|
||||
|
||||
|
||||
class expose(object):
|
||||
|
||||
"""
|
||||
Used to expose controller functions to be listed as commands, and to
|
||||
decorate the function with Meta data for the argument parser.
|
||||
@ -143,6 +146,7 @@ class expose(object):
|
||||
|
||||
"""
|
||||
# pylint: disable=W0622
|
||||
|
||||
def __init__(self, help='', hide=False, aliases=[]):
|
||||
self.hide = hide
|
||||
self.help = help
|
||||
@ -163,6 +167,7 @@ class expose(object):
|
||||
|
||||
# pylint: disable=R0921
|
||||
class CementBaseController(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
This is an implementation of the
|
||||
`IControllerHandler <#cement.core.controller.IController>`_ interface, but
|
||||
@ -198,6 +203,7 @@ class CementBaseController(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Controller meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -2,12 +2,14 @@
|
||||
|
||||
|
||||
class FrameworkError(Exception):
|
||||
|
||||
"""
|
||||
General framework (non-application) related errors.
|
||||
|
||||
:param msg: The error message.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, msg):
|
||||
Exception.__init__(self)
|
||||
self.msg = msg
|
||||
@ -17,11 +19,13 @@ class FrameworkError(Exception):
|
||||
|
||||
|
||||
class InterfaceError(FrameworkError):
|
||||
|
||||
"""Interface related errors."""
|
||||
pass
|
||||
|
||||
|
||||
class CaughtSignal(FrameworkError):
|
||||
|
||||
"""
|
||||
Raised when a defined signal is caught. For more information regarding
|
||||
signals, reference the
|
||||
@ -31,6 +35,7 @@ class CaughtSignal(FrameworkError):
|
||||
:param frame: The signal frame.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, signum, frame):
|
||||
msg = 'Caught signal %s' % signum
|
||||
super(CaughtSignal, self).__init__(msg)
|
||||
|
||||
@ -25,6 +25,7 @@ def extension_validator(klass, obj):
|
||||
|
||||
|
||||
class IExtension(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Extension Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -48,6 +49,7 @@ class IExtension(interface.Interface):
|
||||
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
|
||||
label = 'extension'
|
||||
@ -93,7 +95,9 @@ class IExtension(interface.Interface):
|
||||
|
||||
|
||||
class CementExtensionHandler(handler.CementBaseHandler):
|
||||
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -19,6 +19,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class NullOut(object):
|
||||
|
||||
def write(self, s):
|
||||
pass
|
||||
|
||||
@ -45,6 +46,7 @@ def cement_signal_handler(signum, frame):
|
||||
|
||||
|
||||
class CementApp(meta.MetaMixin):
|
||||
|
||||
"""
|
||||
The primary class to build applications from.
|
||||
|
||||
@ -98,6 +100,7 @@ class CementApp(meta.MetaMixin):
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Application meta-data (can also be passed as keyword arguments to the
|
||||
parent class).
|
||||
@ -543,10 +546,10 @@ class CementApp(meta.MetaMixin):
|
||||
def get_last_rendered(self):
|
||||
"""
|
||||
DEPRECATION WARNING: This function is deprecated as of Cement 2.1.3
|
||||
in favor of the `self.last_rendered` property, and will be removed in
|
||||
in favor of the `self.last_rendered` property, and will be removed in
|
||||
future versions of Cement.
|
||||
|
||||
Return the (data, output_text) tuple of the last time self.render()
|
||||
|
||||
Return the (data, output_text) tuple of the last time self.render()
|
||||
was called.
|
||||
|
||||
:returns: tuple (data, output_text)
|
||||
@ -587,7 +590,7 @@ class CementApp(meta.MetaMixin):
|
||||
LOG.debug("laying cement for the '%s' application" %
|
||||
self._meta.label)
|
||||
|
||||
### overrides via command line
|
||||
# overrides via command line
|
||||
suppress_output = False
|
||||
|
||||
if '--debug' in self._meta.argv:
|
||||
|
||||
@ -11,9 +11,11 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class CementBaseHandler(meta.MetaMixin):
|
||||
|
||||
"""Base handler class that all Cement Handlers should subclass from."""
|
||||
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can also be passed as keyword arguments to the
|
||||
parent class).
|
||||
|
||||
@ -9,22 +9,26 @@ DEFAULT_META = ['interface', 'label', 'config_defaults', 'config_section']
|
||||
|
||||
|
||||
class Interface(object):
|
||||
|
||||
"""
|
||||
An interface definition class. All Interfaces should subclass from
|
||||
here. Note that this is not an implementation and should never be
|
||||
used directly.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
raise exc.InterfaceError("Interfaces can not be used directly.")
|
||||
|
||||
|
||||
class Attribute(object):
|
||||
|
||||
"""
|
||||
An interface attribute definition.
|
||||
|
||||
:param description: The description of the attribute.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, description):
|
||||
self.description = description
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ def log_validator(klass, obj):
|
||||
|
||||
|
||||
class ILog(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Log Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -46,6 +47,7 @@ class ILog(interface.Interface):
|
||||
"""
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
|
||||
label = 'log'
|
||||
@ -122,11 +124,13 @@ class ILog(interface.Interface):
|
||||
|
||||
|
||||
class CementLogHandler(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
Base class that all Log Handlers should sub-class from.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
class Meta(object):
|
||||
|
||||
"""
|
||||
Model that acts as a container class for a meta attributes for a larger
|
||||
class. It stuffs any kwarg it gets in it's init as an attribute of itself.
|
||||
@ -17,6 +18,7 @@ class Meta(object):
|
||||
|
||||
|
||||
class MetaMixin(object):
|
||||
|
||||
"""
|
||||
Mixin that provides the Meta class support to add settings to instances
|
||||
of slumber objects. Meta settings cannot start with a _.
|
||||
|
||||
@ -21,6 +21,7 @@ def output_validator(klass, obj):
|
||||
|
||||
|
||||
class IOutput(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Output Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -43,6 +44,7 @@ class IOutput(interface.Interface):
|
||||
"""
|
||||
# pylint: disable=W0232, C0111, R0903
|
||||
class IMeta:
|
||||
|
||||
"""Interface meta-data."""
|
||||
|
||||
label = 'output'
|
||||
@ -76,11 +78,13 @@ class IOutput(interface.Interface):
|
||||
|
||||
|
||||
class CementOutputHandler(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
Base class that all Output Handlers should sub-class from.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
@ -97,10 +101,12 @@ class CementOutputHandler(handler.CementBaseHandler):
|
||||
|
||||
|
||||
class TemplateOutputHandler(CementOutputHandler):
|
||||
|
||||
"""
|
||||
Base class for template base output handlers.
|
||||
|
||||
"""
|
||||
|
||||
def _load_template_from_file(self, template_path):
|
||||
template_prefix = self.app._meta.template_dir.rstrip('/')
|
||||
template_path = template_path.lstrip('/')
|
||||
|
||||
@ -21,6 +21,7 @@ def plugin_validator(klass, obj):
|
||||
|
||||
|
||||
class IPlugin(interface.Interface):
|
||||
|
||||
"""
|
||||
This class defines the Plugin Handler Interface. Classes that
|
||||
implement this handler must provide the methods and attributes defined
|
||||
@ -86,12 +87,14 @@ class IPlugin(interface.Interface):
|
||||
|
||||
|
||||
class CementPluginHandler(handler.CementBaseHandler):
|
||||
|
||||
"""
|
||||
Base class that all Plugin Handlers should sub-class from.
|
||||
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
|
||||
"""
|
||||
Handler meta-data (can be passed as keyword arguments to the parent
|
||||
class).
|
||||
|
||||
@ -8,6 +8,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class ArgParseArgumentHandler(arg.CementArgumentHandler, ArgumentParser):
|
||||
|
||||
"""
|
||||
This class implements the :ref:`IArgument <cement.core.arg>`
|
||||
interface, and sub-classes from `argparse.ArgumentParser
|
||||
@ -20,6 +21,7 @@ class ArgParseArgumentHandler(arg.CementArgumentHandler, ArgumentParser):
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data."""
|
||||
|
||||
interface = arg.IArgument
|
||||
|
||||
@ -14,6 +14,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
|
||||
|
||||
"""
|
||||
This class is an implementation of the :ref:`IConfig <cement.core.config>`
|
||||
interface. It handles configuration file parsing and the like by
|
||||
@ -26,6 +27,7 @@ class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
|
||||
RawConfigParser on initialization.
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data."""
|
||||
|
||||
interface = config.IConfig
|
||||
|
||||
266
cement/ext/ext_daemon.py
Normal file
266
cement/ext/ext_daemon.py
Normal file
@ -0,0 +1,266 @@
|
||||
"""Daemon Framework Extension"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
import pwd
|
||||
import grp
|
||||
from ..core import handler, hook, backend, exc
|
||||
from ..utils.misc import minimal_logger
|
||||
|
||||
Log = minimal_logger(__name__)
|
||||
Log = minimal_logger(__name__)
|
||||
CEMENT_DAEMON_ENV = None
|
||||
CEMENT_DAEMON_APP = None
|
||||
|
||||
|
||||
class Environment(object):
|
||||
"""
|
||||
This class provides a mechanism for altering the running processes
|
||||
environment.
|
||||
|
||||
Optional Arguments:
|
||||
|
||||
stdin
|
||||
A file to read STDIN from. Default: /dev/null
|
||||
|
||||
stdout
|
||||
A file to write STDOUT to. Default: /dev/null
|
||||
|
||||
stderr
|
||||
A file to write STDERR to. Default: /dev/null
|
||||
|
||||
dir
|
||||
The directory to run the process in.
|
||||
|
||||
pid_file
|
||||
The filesystem path to where the PID (Process ID) should be
|
||||
written to. Default: None
|
||||
|
||||
user
|
||||
The user name to run the process as. Default: os.environ['USER']
|
||||
|
||||
group
|
||||
The group name to run the process as. Default: The primary group
|
||||
of os.environ['USER'].
|
||||
|
||||
umask
|
||||
The umask to pass to os.umask(). Default: 0
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.stdin = kw.get('stdin', '/dev/null')
|
||||
self.stdout = kw.get('stdout', '/dev/null')
|
||||
self.stderr = kw.get('stderr', '/dev/null')
|
||||
self.dir = kw.get('dir', os.curdir)
|
||||
self.pid_file = kw.get('pid_file', None)
|
||||
self.umask = kw.get('umask', 0)
|
||||
self.user = kw.get('user', os.environ['USER'])
|
||||
|
||||
# clean up
|
||||
self.dir = os.path.abspath(os.path.expanduser(self.dir))
|
||||
if self.pid_file:
|
||||
self.pid_file = os.path.abspath(os.path.expanduser(self.pid_file))
|
||||
|
||||
try:
|
||||
self.user = pwd.getpwnam(self.user)
|
||||
except KeyError as e:
|
||||
raise exc.FrameworkError("Daemon user '%s' doesn't exist." %
|
||||
self.user)
|
||||
|
||||
try:
|
||||
self.group = kw.get('group',
|
||||
grp.getgrgid(self.user.pw_gid).gr_name)
|
||||
self.group = grp.getgrnam(self.group)
|
||||
except KeyError as e:
|
||||
raise exc.FrameworkError("Daemon group '%s' doesn't exist." %
|
||||
self.group)
|
||||
|
||||
def _write_pid_file(self):
|
||||
"""
|
||||
Writes os.getpid() out to self.pid_file.
|
||||
"""
|
||||
pid = str(os.getpid())
|
||||
Log.debug('writing pid (%s) out to %s' % (pid, self.pid_file))
|
||||
|
||||
# setup pid
|
||||
if self.pid_file:
|
||||
f = open(self.pid_file, 'w')
|
||||
f.write(pid)
|
||||
f.close()
|
||||
os.chown(os.path.dirname(self.pid_file),
|
||||
self.user.pw_uid, self.group.gr_gid)
|
||||
|
||||
def switch(self):
|
||||
"""
|
||||
Switch the current process's user/group to self.user, and
|
||||
self.group. Change directory to self.dir, and write the
|
||||
current pid out to self.pid_file.
|
||||
"""
|
||||
# set the running uid/gid
|
||||
Log.debug('setting process uid(%s) and gid(%s)' %
|
||||
(self.user.pw_uid, self.group.gr_gid))
|
||||
os.setgid(self.group.gr_gid)
|
||||
os.setuid(self.user.pw_uid)
|
||||
os.environ['HOME'] = self.user.pw_dir
|
||||
os.chdir(self.dir)
|
||||
if self.pid_file and os.path.exists(self.pid_file):
|
||||
raise exc.FrameworkError("Process already running (%s)" %
|
||||
self.pid_file)
|
||||
else:
|
||||
self._write_pid_file()
|
||||
|
||||
def daemonize(self):
|
||||
"""
|
||||
Fork the current process into a daemon.
|
||||
|
||||
References:
|
||||
|
||||
UNIX Programming FAQ
|
||||
1.7 How do I get my program to act like a daemon?
|
||||
http://www.unixguide.net/unix/programming/1.7.shtml
|
||||
http://www.faqs.org/faqs/unix-faq/programmer/faq/
|
||||
|
||||
Advanced Programming in the Unix Environment
|
||||
W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
|
||||
|
||||
"""
|
||||
Log.debug('attempting to daemonize the current process')
|
||||
# Do first fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
Log.debug('successfully detached from first parent')
|
||||
os._exit(os.EX_OK)
|
||||
except OSError as e:
|
||||
sys.stderr.write("Fork #1 failed: (%d) %s\n" %
|
||||
(e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Decouple from parent environment.
|
||||
os.chdir(self.dir)
|
||||
os.umask(int(self.umask))
|
||||
os.setsid()
|
||||
|
||||
# Do second fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
Log.debug('successfully detached from second parent')
|
||||
os._exit(os.EX_OK)
|
||||
except OSError as e:
|
||||
sys.stderr.write("Fork #2 failed: (%d) %s\n" %
|
||||
(e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Redirect standard file descriptors.
|
||||
stdin = open(self.stdin, 'r')
|
||||
stdout = open(self.stdout, 'a+')
|
||||
stderr = open(self.stderr, 'a+')
|
||||
|
||||
if hasattr(sys.stdin, 'fileno'):
|
||||
try:
|
||||
os.dup2(stdin.fileno(), sys.stdin.fileno())
|
||||
except io.UnsupportedOperation as e:
|
||||
# FIXME: ?
|
||||
pass
|
||||
if hasattr(sys.stdout, 'fileno'):
|
||||
try:
|
||||
os.dup2(stdout.fileno(), sys.stdout.fileno())
|
||||
except io.UnsupportedOperation as e:
|
||||
# FIXME: ?
|
||||
pass
|
||||
if hasattr(sys.stderr, 'fileno'):
|
||||
try:
|
||||
os.dup2(stderr.fileno(), sys.stderr.fileno())
|
||||
except io.UnsupportedOperation as e:
|
||||
# FIXME: ?
|
||||
pass
|
||||
|
||||
# Update our pid file
|
||||
self._write_pid_file()
|
||||
|
||||
|
||||
def daemonize():
|
||||
"""
|
||||
This function switches the running user/group to that configured in
|
||||
config['daemon']['user'] and config['daemon']['group']. The default user
|
||||
is os.environ['USER'] and the default group is that user's primary group.
|
||||
A pid_file and directory to run in is also passed to the environment.
|
||||
|
||||
It is important to note that with the daemon extension enabled, the
|
||||
environment will switch user/group/set pid/etc regardless of whether
|
||||
the --daemon option was passed at command line or not. However, the
|
||||
process will only 'daemonize' if the option is passed to do so. This
|
||||
allows the program to run exactly the same in forground or background.
|
||||
|
||||
"""
|
||||
# We want to honor the runtime user/group/etc even if --daemon is not
|
||||
# passed... but only daemonize if it is.
|
||||
global CEMENT_DAEMON_ENV
|
||||
global CEMENT_DAEMON_APP
|
||||
|
||||
app = CEMENT_DAEMON_APP
|
||||
CEMENT_DAEMON_ENV = Environment(
|
||||
user=app.config.get('daemon', 'user'),
|
||||
group=app.config.get('daemon', 'group'),
|
||||
pid_file=app.config.get('daemon', 'pid_file'),
|
||||
dir=app.config.get('daemon', 'dir'),
|
||||
umask=app.config.get('daemon', 'umask'),
|
||||
)
|
||||
|
||||
CEMENT_DAEMON_ENV.switch()
|
||||
|
||||
if '--daemon' in app.argv:
|
||||
CEMENT_DAEMON_ENV.daemonize()
|
||||
|
||||
|
||||
def extend_app(app):
|
||||
"""
|
||||
Adds the '--daemon' argument to the argument object, and sets the default
|
||||
[daemon] config section options.
|
||||
|
||||
"""
|
||||
global CEMENT_DAEMON_APP
|
||||
CEMENT_DAEMON_APP = app
|
||||
|
||||
app.args.add_argument('--daemon', dest='daemon',
|
||||
action='store_true', help='daemonize the process')
|
||||
|
||||
# Add default config
|
||||
user = pwd.getpwnam(os.environ['USER'])
|
||||
group = grp.getgrgid(user.pw_gid)
|
||||
|
||||
defaults = dict()
|
||||
defaults['daemon'] = dict()
|
||||
defaults['daemon']['user'] = user.pw_name
|
||||
defaults['daemon']['group'] = group.gr_name
|
||||
defaults['daemon']['pid_file'] = None
|
||||
defaults['daemon']['dir'] = '/'
|
||||
defaults['daemon']['umask'] = 0
|
||||
app.config.merge(defaults, override=False)
|
||||
app.extend('daemonize', daemonize)
|
||||
|
||||
|
||||
def cleanup(app):
|
||||
"""
|
||||
After application run time, this hook just attempts to clean up the
|
||||
pid_file if one was set, and exists.
|
||||
|
||||
"""
|
||||
global CEMENT_DAEMON_ENV
|
||||
|
||||
if CEMENT_DAEMON_ENV and CEMENT_DAEMON_ENV.pid_file:
|
||||
if os.path.exists(CEMENT_DAEMON_ENV.pid_file):
|
||||
Log.debug('Cleaning up pid_file...')
|
||||
pid = open(CEMENT_DAEMON_ENV.pid_file, 'r').read().strip()
|
||||
|
||||
# only remove it if we created it.
|
||||
if int(pid) == int(os.getpid()):
|
||||
os.remove(CEMENT_DAEMON_ENV.pid_file)
|
||||
|
||||
|
||||
def load():
|
||||
hook.register('post_setup', extend_app)
|
||||
hook.register('pre_close', cleanup)
|
||||
@ -9,6 +9,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class JsonOutputHandler(output.CementOutputHandler):
|
||||
|
||||
"""
|
||||
This class implements the :ref:`IOutput <cement.core.output>`
|
||||
interface. It provides JSON output from a data dictionary using the
|
||||
@ -22,6 +23,7 @@ class JsonOutputHandler(output.CementOutputHandler):
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data"""
|
||||
|
||||
interface = output.IOutput
|
||||
|
||||
@ -11,6 +11,7 @@ try: # pragma: no cover
|
||||
except AttributeError as e: # pragma: no cover
|
||||
# Not supported on Python < 3.1/2.7 # pragma: no cover
|
||||
class NullHandler(logging.Handler): # pragma: no cover
|
||||
|
||||
def handle(self, record): # pragma: no cover
|
||||
pass # pragma: no cover
|
||||
# pragma: no cover
|
||||
@ -24,6 +25,7 @@ except AttributeError as e: # pragma: no cover
|
||||
|
||||
|
||||
class LoggingLogHandler(log.CementLogHandler):
|
||||
|
||||
"""
|
||||
This class is an implementation of the :ref:`ILog <cement.core.log>`
|
||||
interface, and sets up the logging facility using the standard Python
|
||||
@ -61,6 +63,7 @@ class LoggingLogHandler(log.CementLogHandler):
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data."""
|
||||
|
||||
interface = log.ILog
|
||||
|
||||
@ -9,6 +9,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class MustacheOutputHandler(output.TemplateOutputHandler):
|
||||
|
||||
"""
|
||||
This class implements the :ref:`IOutput <cement.core.output>`
|
||||
interface. It provides text output from template and uses the
|
||||
|
||||
@ -7,6 +7,7 @@ LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class NullOutputHandler(output.CementOutputHandler):
|
||||
|
||||
"""
|
||||
This class is an internal implementation of the
|
||||
:ref:`IOutput <cement.core.output>` interface. It does not take any
|
||||
@ -14,6 +15,7 @@ class NullOutputHandler(output.CementOutputHandler):
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data"""
|
||||
|
||||
interface = output.IOutput
|
||||
|
||||
@ -10,10 +10,11 @@ from ..utils.fs import abspath
|
||||
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
### FIX ME: This is a redundant name... ?
|
||||
# FIX ME: This is a redundant name... ?
|
||||
|
||||
|
||||
class CementPluginHandler(plugin.CementPluginHandler):
|
||||
|
||||
"""
|
||||
This class is an internal implementation of the
|
||||
:ref:`IPlugin <cement.core.plugin>` interface. It does not take any
|
||||
@ -22,6 +23,7 @@ class CementPluginHandler(plugin.CementPluginHandler):
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
|
||||
"""Handler meta-data."""
|
||||
|
||||
interface = plugin.IPlugin
|
||||
|
||||
@ -4,13 +4,15 @@ import unittest
|
||||
from ..core import backend, foundation
|
||||
|
||||
# shortcuts
|
||||
from nose import SkipTest
|
||||
from nose.tools import ok_ as ok
|
||||
from nose.tools import eq_ as eq
|
||||
from nose.tools import raises
|
||||
from nose import SkipTest
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
|
||||
class TestApp(foundation.CementApp):
|
||||
|
||||
"""
|
||||
Basic CementApp for generic testing.
|
||||
|
||||
@ -24,6 +26,7 @@ class TestApp(foundation.CementApp):
|
||||
|
||||
|
||||
class CementTestCase(unittest.TestCase):
|
||||
|
||||
"""
|
||||
A sub-class of unittest.TestCase.
|
||||
|
||||
@ -69,3 +72,15 @@ class CementTestCase(unittest.TestCase):
|
||||
def eq(self, a, b, msg=None):
|
||||
"""Shorthand for 'assert a == b, "%r != %r" % (a, b)'. """
|
||||
return eq(a, b, msg)
|
||||
|
||||
# The following are for internal, Cement unit testing only
|
||||
|
||||
|
||||
@attr('core')
|
||||
class CementCoreTestCase(CementTestCase):
|
||||
pass
|
||||
|
||||
|
||||
@attr('ext')
|
||||
class CementExtTestCase(CementTestCase):
|
||||
pass
|
||||
|
||||
10
doc/source/api/ext/ext_daemon.rst
Normal file
10
doc/source/api/ext/ext_daemon.rst
Normal file
@ -0,0 +1,10 @@
|
||||
.. _cement.ext.ext_daemon:
|
||||
|
||||
:mod:`cement.ext.ext_daemon`
|
||||
------------------------------
|
||||
|
||||
.. automodule:: cement.ext.ext_daemon
|
||||
:members:
|
||||
:undoc-members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
||||
@ -48,6 +48,7 @@ Cement Extension Modules
|
||||
|
||||
ext/ext_argparse
|
||||
ext/ext_configparser
|
||||
ext/ext_daemon
|
||||
ext/ext_json
|
||||
ext/ext_logging
|
||||
ext/ext_mustache
|
||||
|
||||
@ -4,6 +4,7 @@ debug=0
|
||||
detailed-errors=1
|
||||
with-coverage=1
|
||||
cover-package=cement
|
||||
cover-inclusive=1
|
||||
cover-erase=1
|
||||
cover-html=1
|
||||
cover-html-dir=coverage_report/
|
||||
|
||||
@ -19,7 +19,8 @@ class MyCacheHandler(cache.CementCacheHandler):
|
||||
def purge(self):
|
||||
pass
|
||||
|
||||
class CacheTestCase(test.CementTestCase):
|
||||
@test.attr('core')
|
||||
class CacheTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
super(CacheTestCase, self).setUp()
|
||||
self.app = self.make_app(cache_handler=MyCacheHandler)
|
||||
|
||||
@ -14,7 +14,7 @@ class BogusConfigHandler(config.CementConfigHandler):
|
||||
class Meta:
|
||||
label = 'bogus'
|
||||
|
||||
class ConfigTestCase(test.CementTestCase):
|
||||
class ConfigTestCase(test.CementCoreTestCase):
|
||||
@test.raises(exc.InterfaceError)
|
||||
def test_invalid_config_handler(self):
|
||||
handler.register(BogusConfigHandler)
|
||||
|
||||
@ -87,7 +87,7 @@ class ArgumentConflict(controller.CementBaseController):
|
||||
stacked_type = 'embedded'
|
||||
arguments = [(['-f', '--foo'], dict())]
|
||||
|
||||
class ControllerTestCase(test.CementTestCase):
|
||||
class ControllerTestCase(test.CementCoreTestCase):
|
||||
def test_default(self):
|
||||
app = self.make_app(base_controller=TestController)
|
||||
app.setup()
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
from cement.core import exc
|
||||
from cement.utils import test
|
||||
|
||||
class ExceptionTestCase(test.CementTestCase):
|
||||
class ExceptionTestCase(test.CementCoreTestCase):
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_cement_runtime_error(self):
|
||||
try:
|
||||
|
||||
@ -12,7 +12,7 @@ class BogusExtensionHandler(extension.CementExtensionHandler):
|
||||
interface = IBogus
|
||||
label = 'bogus'
|
||||
|
||||
class ExtensionTestCase(test.CementTestCase):
|
||||
class ExtensionTestCase(test.CementCoreTestCase):
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_invalid_extension_handler(self):
|
||||
# the handler type bogus doesn't exist
|
||||
|
||||
@ -40,7 +40,7 @@ def my_hook_two(app):
|
||||
def my_hook_three(app):
|
||||
return 3
|
||||
|
||||
class FoundationTestCase(test.CementTestCase):
|
||||
class FoundationTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app('my_app')
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ class TestHandler(meta.MetaMixin):
|
||||
interface = TestInterface
|
||||
label = 'test'
|
||||
|
||||
class HandlerTestCase(test.CementTestCase):
|
||||
class HandlerTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ def nosetests_hook(*args, **kw):
|
||||
def cement_hook_five(app, data):
|
||||
return data
|
||||
|
||||
class HookTestCase(test.CementTestCase):
|
||||
class HookTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
hook.define('nosetests_hook')
|
||||
|
||||
@ -20,7 +20,7 @@ class TestHandler2(handler.CementBaseHandler):
|
||||
class TestHandler3():
|
||||
pass
|
||||
|
||||
class InterfaceTestCase(test.CementTestCase):
|
||||
class InterfaceTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ class BogusHandler1(log.CementLogHandler):
|
||||
interface = log.ILog
|
||||
label = 'bogus'
|
||||
|
||||
class LogTestCase(test.CementTestCase):
|
||||
class LogTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ class TestMeta(meta.MetaMixin):
|
||||
super(TestMeta, self).__init__(**kw)
|
||||
self.option_three = kw.get('option_three', None)
|
||||
|
||||
class MetaTestCase(test.CementTestCase):
|
||||
class MetaTestCase(test.CementCoreTestCase):
|
||||
def test_passed_kwargs(self):
|
||||
t = TestMeta(option_two='some other value', option_three='value three')
|
||||
self.eq(t._meta.option_one, 'value one')
|
||||
|
||||
@ -16,7 +16,7 @@ class TestOutputHandler(output.TemplateOutputHandler):
|
||||
|
||||
TEST_TEMPLATE = "%(foo)s"
|
||||
|
||||
class OutputTestCase(test.CementTestCase):
|
||||
class OutputTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ def load():
|
||||
|
||||
"""
|
||||
|
||||
class PluginTestCase(test.CementTestCase):
|
||||
class PluginTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
|
||||
@ -2,5 +2,6 @@
|
||||
from cement.utils import test
|
||||
from cement.utils.misc import init_defaults
|
||||
|
||||
class ConfigParserConfigHandlerTestCase(test.CementTestCase):
|
||||
@test.attr('core')
|
||||
class ConfigParserConfigHandlerTestCase(test.CementExtTestCase):
|
||||
pass
|
||||
98
tests/ext/daemon_tests.py
Normal file
98
tests/ext/daemon_tests.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""Tests for cement.ext.ext_daemon."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from random import random
|
||||
from cement.core import handler, backend, log, hook, exc
|
||||
from cement.utils import test
|
||||
from cement.ext import ext_daemon
|
||||
|
||||
class DaemonExtTestCase(test.CementExtTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app()
|
||||
|
||||
def test_switch(self):
|
||||
env = ext_daemon.Environment()
|
||||
env.switch()
|
||||
|
||||
def test_switch_with_pid(self):
|
||||
(_, tmpfile) = tempfile.mkstemp()
|
||||
os.remove(tmpfile)
|
||||
env = ext_daemon.Environment(pid_file=tmpfile)
|
||||
env.switch()
|
||||
|
||||
try:
|
||||
self.ok(os.path.exists(tmpfile))
|
||||
finally:
|
||||
os.remove(tmpfile)
|
||||
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_pid_exists(self):
|
||||
(_, tmpfile) = tempfile.mkstemp()
|
||||
|
||||
env = ext_daemon.Environment(pid_file=tmpfile)
|
||||
env.switch()
|
||||
|
||||
try:
|
||||
self.ok(os.path.exists(tmpfile))
|
||||
except exc.FrameworkError as e:
|
||||
self.ok(e.msg.startswith('Process already running'))
|
||||
raise
|
||||
finally:
|
||||
env = ext_daemon.Environment()
|
||||
env.switch()
|
||||
os.remove(tmpfile)
|
||||
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_bogus_user(self):
|
||||
rand = random()
|
||||
|
||||
try:
|
||||
env = ext_daemon.Environment(user='cement_test_user%s' % rand)
|
||||
except exc.FrameworkError as e:
|
||||
self.ok(e.msg.startswith('Daemon user'))
|
||||
raise
|
||||
finally:
|
||||
env = ext_daemon.Environment()
|
||||
env.switch()
|
||||
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_bogus_group(self):
|
||||
rand = random()
|
||||
|
||||
try:
|
||||
env = ext_daemon.Environment(group='cement_test_group%s' % rand)
|
||||
except exc.FrameworkError as e:
|
||||
self.ok(e.msg.startswith('Daemon group'))
|
||||
raise
|
||||
finally:
|
||||
env = ext_daemon.Environment()
|
||||
env.switch()
|
||||
|
||||
def test_daemon(self):
|
||||
(_, tmpfile) = tempfile.mkstemp()
|
||||
os.remove(tmpfile)
|
||||
|
||||
app = self.make_app('test', argv=['--daemon'], extensions=['daemon'])
|
||||
|
||||
app.setup()
|
||||
app.config.set('daemon', 'pid_file', tmpfile)
|
||||
|
||||
try:
|
||||
### FIX ME: Can't daemonize, because nose loses sight of it
|
||||
#app.daemonize()
|
||||
app.run()
|
||||
finally:
|
||||
app.close()
|
||||
ext_daemon.cleanup(app)
|
||||
|
||||
def test_daemon_not_passed(self):
|
||||
app = self.make_app('myapp', extensions=['daemon'])
|
||||
|
||||
app.setup()
|
||||
app.config.set('daemon', 'pid_file', None)
|
||||
|
||||
try:
|
||||
app.run()
|
||||
finally:
|
||||
ext_daemon.cleanup(app)
|
||||
@ -5,7 +5,7 @@ import sys
|
||||
from cement.core import handler, backend, hook
|
||||
from cement.utils import test
|
||||
|
||||
class JsonExtTestCase(test.CementTestCase):
|
||||
class JsonExtTestCase(test.CementExtTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app('tests',
|
||||
extensions=['json'],
|
||||
|
||||
@ -16,7 +16,8 @@ class MyLog(ext_logging.LoggingLogHandler):
|
||||
def __init__(self, *args, **kw):
|
||||
super(MyLog, self).__init__(*args, **kw)
|
||||
|
||||
class LoggingExtTestCase(test.CementTestCase):
|
||||
@test.attr('core')
|
||||
class LoggingExtTestCase(test.CementExtTestCase):
|
||||
def test_alternate_namespaces(self):
|
||||
defaults = init_defaults('myapp', 'log')
|
||||
defaults['log']['to_console'] = False
|
||||
|
||||
@ -7,7 +7,7 @@ from cement.core import exc, foundation, handler, backend, controller
|
||||
from cement.utils import test
|
||||
|
||||
|
||||
class MustacheExtTestCase(test.CementTestCase):
|
||||
class MustacheExtTestCase(test.CementExtTestCase):
|
||||
def setUp(self):
|
||||
self.app = self.make_app('tests',
|
||||
extensions=['mustache'],
|
||||
|
||||
@ -4,7 +4,7 @@ import os
|
||||
import tempfile
|
||||
from cement.utils import fs, test
|
||||
|
||||
class FsUtilsTestCase(test.CementTestCase):
|
||||
class FsUtilsTestCase(test.CementCoreTestCase):
|
||||
def test_abspath(self):
|
||||
path = fs.abspath('.')
|
||||
self.ok(path.startswith('/'))
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from cement.utils import test, misc
|
||||
|
||||
class BackendTestCase(test.CementTestCase):
|
||||
class BackendTestCase(test.CementCoreTestCase):
|
||||
def test_defaults(self):
|
||||
defaults = misc.init_defaults('myapp', 'section2', 'section3')
|
||||
defaults['myapp']['debug'] = True
|
||||
|
||||
@ -6,7 +6,7 @@ from cement.utils import shell, test
|
||||
def add(a, b):
|
||||
return a + b
|
||||
|
||||
class ShellUtilsTestCase(test.CementTestCase):
|
||||
class ShellUtilsTestCase(test.CementCoreTestCase):
|
||||
def test_exec_cmd(self):
|
||||
out, err, ret = shell.exec_cmd(['echo', 'KAPLA!'])
|
||||
self.eq(ret, 0)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from cement.utils import version, test
|
||||
|
||||
class VersionUtilsTestCase(test.CementTestCase):
|
||||
class VersionUtilsTestCase(test.CementCoreTestCase):
|
||||
def test_get_version(self):
|
||||
ver = version.get_version()
|
||||
self.ok(ver.startswith('2.1'))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user