Move signals into moulinette core and uptade interfaces

* Replace ActionsMapSignals to MoulinetteSignals and make it available through 'msignal' global variable
* Update interfaces to support signals changes
* Add a new signal 'display' and implement it in the cli
This commit is contained in:
Jerome Lebleu 2014-03-27 02:48:35 +01:00
parent 13213025a1
commit 183ed78dc4
6 changed files with 143 additions and 123 deletions

View file

@ -50,8 +50,9 @@ def init(**kwargs):
"""
import sys
import __builtin__
from moulinette.core import Package, install_i18n
from moulinette.core import Package, MoulinetteSignals, install_i18n
__builtin__.__dict__['pkg'] = Package(**kwargs)
__builtin__.__dict__['msignals'] = MoulinetteSignals()
# Initialize internationalization
install_i18n()

View file

@ -11,89 +11,6 @@ from collections import OrderedDict
from moulinette.core import (MoulinetteError, MoulinetteLock)
from moulinette.interfaces import BaseActionsMapParser
## Actions map Signals -------------------------------------------------
class ActionsMapSignals(object):
"""Actions map's Signals interface
Allow to easily connect signals of the actions map to handlers. They
can be given as arguments in the form of { signal: handler }.
"""
def __init__(self, **kwargs):
# Initialize handlers
for s in self.signals:
self.clear_handler(s)
# Iterate over signals to connect
for s, h in kwargs.items():
self.set_handler(s, h)
def set_handler(self, signal, handler):
"""Set the handler for a signal"""
if signal not in self.signals:
raise ValueError("unknown signal '%s'" % signal)
setattr(self, '_%s' % signal, handler)
def clear_handler(self, signal):
"""Clear the handler of a signal"""
if signal not in self.signals:
raise ValueError("unknown signal '%s'" % signal)
setattr(self, '_%s' % signal, self._notimplemented)
## Signals definitions
"""The list of available signals"""
signals = { 'authenticate', 'prompt' }
def authenticate(self, authenticator, help):
"""Process the authentication
Attempt to authenticate to the given authenticator and return
it.
It is called when authentication is needed (e.g. to process an
action).
Keyword arguments:
- authenticator -- The authenticator object to use
- help -- A help message for the authenticator
Returns:
The authenticator object
"""
if authenticator.is_authenticated:
return authenticator
return self._authenticate(authenticator, help)
def prompt(self, message, is_password=False, confirm=False):
"""Prompt for a value
Prompt the interface for a parameter value which is a password
if 'is_password' and must be confirmed if 'confirm'.
Is is called when a parameter value is needed and when the
current interface should allow user interaction (e.g. to parse
extra parameter 'ask' in the cli).
Keyword arguments:
- message -- The message to display
- is_password -- True if the parameter is a password
- confirm -- True if the value must be confirmed
Returns:
The collected value
"""
return self._prompt(message, is_password, confirm)
@staticmethod
def _notimplemented(**kwargs):
raise NotImplementedError("this signal is not handled")
shandler = ActionsMapSignals()
## Extra parameters ----------------------------------------------------
# Extra parameters definition
@ -176,7 +93,7 @@ class AskParameter(_ExtraParameter):
try:
# Ask for the argument value
return shandler.prompt(message)
return msignals.prompt(message)
except NotImplementedError:
return arg_value
@ -208,7 +125,7 @@ class PasswordParameter(AskParameter):
try:
# Ask for the password
return shandler.prompt(message, True, True)
return msignals.prompt(message, True, True)
except NotImplementedError:
return arg_value
@ -398,20 +315,6 @@ class ActionsMap(object):
else:
return auth()
def connect(self, signal, handler):
"""Connect a signal to a handler
Connect a signal emitted by actions map while processing to a
handler. Note that some signals need a return value.
Keyword arguments:
- signal -- The name of the signal
- handler -- The method to handle the signal
"""
global shandler
shandler.set_handler(signal, handler)
def process(self, args, timeout=0, **kwargs):
"""
Parse arguments and process the proper action
@ -533,7 +436,7 @@ class ActionsMap(object):
parser.set_defaults(_extra=extras)
# Instantiate parser
top_parser = self._parser_class(shandler)
top_parser = self._parser_class()
# Iterate over actions map namespaces
for n, actionsmap in actionsmaps.items():

View file

@ -130,6 +130,109 @@ class Package(object):
True if mode[0] == 'w' else False)
return open('%s/%s' % (self.get_cachedir(**kwargs), filename), mode)
class MoulinetteSignals(object):
"""Signals connector for the moulinette
Allow to easily connect signals from the moulinette to handlers. A
signal is emitted by calling the relevant method which call the
handler.
For the moment, a return value can be requested by a signal to its
connected handler - make them not real-signals.
Keyword arguments:
- kwargs -- A dict of {signal: handler} to connect
"""
def __init__(self, **kwargs):
# Initialize handlers
for s in self.signals:
self.clear_handler(s)
# Iterate over signals to connect
for s, h in kwargs.items():
self.set_handler(s, h)
def set_handler(self, signal, handler):
"""Set the handler for a signal"""
if signal not in self.signals:
raise ValueError("unknown signal '%s'" % signal)
setattr(self, '_%s' % signal, handler)
def clear_handler(self, signal):
"""Clear the handler of a signal"""
if signal not in self.signals:
raise ValueError("unknown signal '%s'" % signal)
setattr(self, '_%s' % signal, self._notimplemented)
## Signals definitions
"""The list of available signals"""
signals = { 'authenticate', 'prompt', 'display' }
def authenticate(self, authenticator, help):
"""Process the authentication
Attempt to authenticate to the given authenticator and return
it.
It is called when authentication is needed (e.g. to process an
action).
Keyword arguments:
- authenticator -- The authenticator object to use
- help -- A help message for the authenticator
Returns:
The authenticator object
"""
if authenticator.is_authenticated:
return authenticator
return self._authenticate(authenticator, help)
def prompt(self, message, is_password=False, confirm=False):
"""Prompt for a value
Prompt the interface for a parameter value which is a password
if 'is_password' and must be confirmed if 'confirm'.
Is is called when a parameter value is needed and when the
current interface should allow user interaction (e.g. to parse
extra parameter 'ask' in the cli).
Keyword arguments:
- message -- The message to display
- is_password -- True if the parameter is a password
- confirm -- True if the value must be confirmed
Returns:
The collected value
"""
return self._prompt(message, is_password, confirm)
def display(self, message, style='info'):
"""Display a message
Display a message with a given style to the user.
It is called when a message should be printed to the user if the
current interface allows user interaction (e.g. print a success
message to the user).
Keyword arguments:
- message -- The message to display
- style -- The type of the message. Possible values are:
info, success, warning
"""
try:
self._display(message, style)
except NotImplementedError:
pass
@staticmethod
def _notimplemented(**kwargs):
raise NotImplementedError("this signal is not handled")
# Interfaces & Authenticators management -------------------------------

View file

@ -17,16 +17,13 @@ class BaseActionsMapParser(object):
the global and actions configuration.
Keyword arguments:
- shandler -- A actionsmap.ActionsMapSignals instance
- parent -- A parent BaseActionsMapParser derived object
"""
def __init__(self, shandler, parent=None):
def __init__(self, parent=None):
if parent:
self.shandler = parent.shandler
self._o = parent
else:
self.shandler = shandler
self._o = self
self._global_conf = {}
self._conf = {}

View file

@ -100,7 +100,7 @@ class _ActionsMapPlugin(object):
def __init__(self, actionsmap):
# Connect signals to handlers
actionsmap.connect('authenticate', self._do_authenticate)
msignals.set_handler('authenticate', self._do_authenticate)
self.actionsmap = actionsmap
# TODO: Save and load secrets?
@ -268,7 +268,7 @@ class _ActionsMapPlugin(object):
def _do_authenticate(self, authenticator, help):
"""Process the authentication
Handle the actionsmap._AMapSignals.authenticate signal.
Handle the core.MoulinetteSignals.authenticate signal.
"""
s_id = request.get_cookie('session.id')
@ -314,8 +314,8 @@ class ActionsMapParser(BaseActionsMapParser):
the arguments is represented by a argparse.ArgumentParser object.
"""
def __init__(self, shandler, parent=None):
super(ActionsMapParser, self).__init__(shandler, parent)
def __init__(self, parent=None):
super(ActionsMapParser, self).__init__(parent)
self._parsers = {} # dict({(method, path): _HTTPArgumentParser})
@ -398,7 +398,7 @@ class ActionsMapParser(BaseActionsMapParser):
auth_conf, klass = self.get_conf(tid, 'authenticator')
# TODO: Catch errors
auth = self.shandler.authenticate(klass(), **auth_conf)
auth = msignals.authenticate(klass(), **auth_conf)
if not auth.is_authenticated:
# TODO: Set proper error code
raise MoulinetteError(errno.EACCES, _("This action need authentication"))

View file

@ -13,8 +13,10 @@ colors_codes = {
'red' : 31,
'green' : 32,
'yellow': 33,
'cyan' : 34,
'purple': 35
'blue' : 34,
'purple': 35,
'cyan' : 36,
'white' : 37
}
def colorize(astr, color):
@ -73,8 +75,8 @@ class ActionsMapParser(BaseActionsMapParser):
- parser -- The argparse.ArgumentParser object to use
"""
def __init__(self, shandler, parent=None, parser=None):
super(ActionsMapParser, self).__init__(shandler, parent)
def __init__(self, parent=None, parser=None):
super(ActionsMapParser, self).__init__(parent)
self._parser = parser or argparse.ArgumentParser()
self._subparsers = self._parser.add_subparsers()
@ -107,7 +109,7 @@ class ActionsMapParser(BaseActionsMapParser):
"""
parser = self._subparsers.add_parser(name, help=category_help)
return self.__class__(None, self, parser)
return self.__class__(self, parser)
def add_action_parser(self, name, tid, action_help=None, **kwargs):
"""Add a parser for an action
@ -129,7 +131,7 @@ class ActionsMapParser(BaseActionsMapParser):
auth_conf, klass = self.get_conf(ret._tid, 'authenticator')
# TODO: Catch errors
auth = self.shandler.authenticate(klass(), **auth_conf)
auth = msignals.authenticate(klass(), **auth_conf)
if not auth.is_authenticated:
# TODO: Set proper error code
raise MoulinetteError(errno.EACCES, _("This action need authentication"))
@ -152,8 +154,9 @@ class Interface(BaseInterface):
"""
def __init__(self, actionsmap):
# Connect signals to handlers
actionsmap.connect('authenticate', self._do_authenticate)
actionsmap.connect('prompt', self._do_prompt)
msignals.set_handler('authenticate', self._do_authenticate)
msignals.set_handler('display', self._do_display)
msignals.set_handler('prompt', self._do_prompt)
self.actionsmap = actionsmap
@ -183,7 +186,7 @@ class Interface(BaseInterface):
def _do_authenticate(self, authenticator, help):
"""Process the authentication
Handle the actionsmap._AMapSignals.authenticate signal.
Handle the core.MoulinetteSignals.authenticate signal.
"""
# TODO: Allow token authentication?
@ -193,17 +196,30 @@ class Interface(BaseInterface):
def _do_prompt(self, message, is_password, confirm):
"""Prompt for a value
Handle the actionsmap._AMapSignals.prompt signal.
Handle the core.MoulinetteSignals.prompt signal.
"""
if is_password:
prompt = lambda m: getpass.getpass(colorize(_('%s: ') % m, 'cyan'))
prompt = lambda m: getpass.getpass(colorize(_('%s: ') % m, 'blue'))
else:
prompt = lambda m: raw_input(colorize(_('%s: ') % m, 'cyan'))
prompt = lambda m: raw_input(colorize(_('%s: ') % m, 'blue'))
value = prompt(message)
if confirm:
if prompt(_('Retype %s: ') % message) != value:
if prompt(_('Retype %s') % message) != value:
raise MoulinetteError(errno.EINVAL, _("Values don't match"))
return value
def _do_display(self, message, style):
"""Display a message
Handle the core.MoulinetteSignals.display signal.
"""
if style == 'success':
print('%s %s' % (colorize(_("Success!"), 'green'), message))
elif style == 'warning':
print('%s %s' % (colorize(_("Warning!"), 'yellow'), message))
else:
print(message)