Ideas / TODO:
-* Hacer el protocolo completamente introspectivo, de manera que el cliente pueda
- ser muy simple y genérico y en caso de agregar funcionalidad no sea necesario
- modificarlo.
-
-* Evaluar hacer un RootHandler en vez de un diccionario con los handlers de la
- raiz para simplificar la introspección y tener un help/commands global.
-
-* Evaluar que el dispatcher vea si se llama a un HandlerContainer y de ser así
- que tire una ayuda en vez de un CommandNotFound (por ejemplo si se pone:
- "dhcp" solo que tire una ayuda). Y si hay un subcomando no encontrado, que
- tire un Command Not Found in handler (por ej "dhcp lala" -> Command "lala" not
- found in "dhcp").
-
* Agregar logging.
* Agregar validación con formencode.
command - is the command that raised the exception, expressed as a list of
paths (or subcommands).
"""
- pass
+
+ def __init__(self, message):
+ r"Initialize the Error object. See class documentation for more info."
+ self.message = message
+
+ def __unicode__(self):
+ return self.message
+
+ def __str__(self):
+ return unicode(self).encode('utf-8')
class HandlerError(Error):
r"""HandlerError(command) -> HandlerError instance :: Base handlers error.
"""
def __init__(self, command):
- r"""Initialize the object.
-
- See class documentation for more info.
- """
+ r"Initialize the object, see class documentation for more info."
self.command = command
- def __str__(self):
- return 'Command error: "%s"' % self.command
+ def __unicode__(self):
+ return u'Error in command "%s".' % u' '.join(self.command)
+
+class CommandNotSpecifiedError(CommandError):
+ r"""CommandNotSpecifiedError() -> CommandNotSpecifiedError instance.
+
+ This exception is raised when an empty command string is received.
+ """
+
+ def __init__(self):
+ r"Initialize the object, see class documentation for more info."
+ pass
+
+ def __unicode__(self):
+ return u'Command not specified.'
+
+class CommandIsAHandlerError(CommandError):
+ r"""CommandIsAHandlerError() -> CommandIsAHandlerError instance.
+
+ This exception is raised when a command is a handler containing commands
+ instead of a command itself.
+ """
+
+ def __unicode__(self):
+ command = ' '.join(self.command)
+ return u'"%s" is a handler, not a command (type "%s help" for help).' \
+ % (command, command)
+
+class CommandNotInHandlerError(CommandError):
+ r"""CommandNotInHandlerError() -> CommandNotInHandlerError instance.
+
+ This exception is raised when a command parent is a hanlder containing
+ commands, but the command itself is not found.
+ """
+
+ def __unicode__(self):
+ return u'Command "%(c)s" not found in handler "%(h)s" ' \
+ u'(type "%(h)s help" for help).' \
+ % dict(c=u' '.join(self.command[-1:]),
+ h=u' '.join(self.command[0:-1]))
class CommandNotFoundError(CommandError):
- r"""CommandNotFoundError(command) -> CommandNotFoundError instance.
+ r"""CommandNotFoundError(command[, handler]) -> CommandNotFoundError object.
This exception is raised when the command received can't be dispatched
because there is no handlers to process it.
"""
- def __str__(self):
- return 'Command not found: "%s"' % ' '.join(
- repr(c) for c in self.command)
+ def __unicode__(self):
+ return u'Command "%s" not found.' % u' '.join(self.command)
class ParseError(CommandError):
r"""ParseError(command[, desc]) -> ParseError instance
self.command = command
self.desc = desc
- def __str__(self):
- return 'Syntax error, %s: %s' % (self.desc, self.command)
+ def __unicode__(self):
+ return u'Syntax error, %s: %s' % (self.desc, self.command)
+
+class HelpNotFoundError(Error):
+ r"""HelpNotFoundError(command) -> HelpNotFoundError instance.
+
+ This exception is raised when a help command can't find the command
+ asked for help.
+ """
+
+ def __init__(self, command):
+ r"""Initialize the object.
+
+ See class documentation for more info.
+ """
+ self.command = command
+
+ def __unicode__(self):
+ return u"Can't get help for '%s', command not found." % self.command
+
def handler(help):
r"""handler(help) -> function wrapper :: Mark a callable as a handler.
def wrapper(f):
if not help:
raise TypeError("'help' should not be empty")
- f._dispatcher_help = help
+ f._dispatcher_handler = True
+ f.handler_help = help
return f
return wrapper
def is_handler(handler):
r"is_handler(handler) -> bool :: Tell if a object is a handler."
- return callable(handler) and hasattr(handler, '_dispatcher_help')
-
-def get_help(handler):
- r"get_help(handler) -> unicode :: Get a handler's help string."
- if not is_handler(handler):
- raise TypeError("'%s' should be a handler" % handler.__name__)
- return handler._dispatcher_help
+ return callable(handler) and hasattr(handler, '_dispatcher_handler') \
+ and handler._dispatcher_handler
class Handler:
r"""Handler() -> Handler instance :: Base class for all dispatcher handlers.
All dispatcher handlers should inherit from this class to have some extra
- commands, like help.
+ commands, like help. You should override the 'handler_help' attribute to a
+ nice help message describing the handler.
"""
- @handler(u'List available commands.')
+ handler_help = u'Undocumented handler'
+
+ @handler(u'List available commands')
def commands(self):
r"""commands() -> generator :: List the available commands."""
return (a for a in dir(self) if is_handler(getattr(self, a)))
- @handler(u'Show available commands with their help.')
+ @handler(u'Show available commands with their help')
def help(self, command=None):
r"""help([command]) -> unicode/dict :: Show help on available commands.
and values are the help strings.
"""
if command is None:
- return dict((a, get_help(getattr(self, a)))
- for a in dir(self) if is_handler(getattr(self, a)))
- if not hasattr(self, command):
- raise CommandNotFoundError(command)
- handler = getattr(self, command)
- if not is_handler(handler):
- raise CommandNotFoundError(command)
- return get_help(handler)
+ d = dict()
+ for a in dir(self):
+ h = getattr(self, a)
+ if is_handler(h) or isinstance(h, Handler):
+ d[a] = h.handler_help
+ return d
+ # A command was specified
+ if not hasattr(self, command.encode('utf-8')):
+ raise HelpNotFoundError(command)
+ handler = getattr(self, command.encode('utf-8'))
+ if not is_handler(handler) and not hasattr(handler):
+ raise HelpNotFoundError(command)
+ return handler.handler_help
def parse_command(command):
r"""parse_command(command) -> (args, kwargs) :: Parse a command.
'arg1', 'arg 2', arg=3) will be called. The handler "tree" can be as
complex and deep as you want.
- If some command can't be dispatched (because there is no root handler
- or there is no matching callable attribute), a CommandNotFoundError
- is raised.
+ If some command can't be dispatched, a CommandError subclass is raised.
"""
def __init__(self, root):
r"""dispatch(route) -> None :: Dispatch a command string.
This method searches for a suitable callable object in the routes
- "tree" and call it, or raises a CommandNotFoundError if the command
+ "tree" and call it, or raises a CommandError subclass if the command
can't be dispatched.
+
+ route - *unicode* string with the command route.
"""
command = list()
(route, kwargs) = parse_command(route)
if not route:
- raise CommandNotFoundError(command)
+ raise CommandNotSpecifiedError()
handler = self.root
while not is_handler(handler):
if len(route) is 0:
+ if isinstance(handler, Handler):
+ raise CommandIsAHandlerError(command)
raise CommandNotFoundError(command)
command.append(route[0])
- if not hasattr(handler, route[0]):
+ if not hasattr(handler, route[0].encode('utf-8')):
+ if isinstance(handler, Handler) and len(command) > 1:
+ raise CommandNotInHandlerError(command)
raise CommandNotFoundError(command)
- handler = getattr(handler, route[0])
+ handler = getattr(handler, route[0].encode('utf-8'))
route = route[1:]
return handler(*route, **kwargs)
if __name__ == '__main__':
- @handler(u"test: Print all the arguments, return nothing.")
+ @handler(u"test: Print all the arguments, return nothing")
def test_func(*args):
print 'func:', args
class TestClassSubHandler(Handler):
- @handler(u"subcmd: Print all the arguments, return nothing.")
+ @handler(u"subcmd: Print all the arguments, return nothing")
def subcmd(self, *args):
print 'class.subclass.subcmd:', args
class TestClass(Handler):
- @handler(u"cmd1: Print all the arguments, return nothing.")
+ @handler(u"cmd1: Print all the arguments, return nothing")
def cmd1(self, *args):
print 'class.cmd1:', args
- @handler(u"cmd2: Print all the arguments, return nothing.")
+ @handler(u"cmd2: Print all the arguments, return nothing")
def cmd2(self, *args):
print 'class.cmd2:', args
subclass = TestClassSubHandler()
d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
try:
d.dispatch('')
- except CommandNotFoundError, e:
+ except CommandNotSpecifiedError, e:
print 'Not found:', e
try:
d.dispatch('sucutrule piquete culete')
print 'Not found:', e
try:
d.dispatch('inst cmd3 arg1 arg2 arg3')
- except CommandNotFoundError, e:
+ except CommandNotInHandlerError, e:
+ print 'Not found:', e
+ try:
+ d.dispatch('inst')
+ except CommandIsAHandlerError, e:
print 'Not found:', e
print
print
# Create EventLoop
eventloop.EventLoop.__init__(self, sock)
# Create Dispatcher
+ #TODO root.pymin = PyminHandler()
self.dispatcher = dispatcher.Dispatcher(root)
# Signal handling
def quit(signum, frame):
r"handle() -> None :: Handle incoming events using the dispatcher."
(msg, addr) = self.file.recvfrom(65535)
try:
- result = self.dispatcher.dispatch(msg)
+ result = self.dispatcher.dispatch(unicode(msg, 'utf-8'))
if result is not None:
result = serializer.serialize(result)
response = u'OK '
response += u'0\n'
else:
response += u'%d\n%s' % (len(result), result)
- self.file.sendto(response, addr)
+ self.file.sendto(response.encode('utf-8'), addr)
def run(self):
r"run() -> None :: Run the event loop (shortcut to loop())"
message - A descriptive error message.
"""
-
- def __init__(self, message):
- r"Initialize the Error object. See class documentation for more info."
- self.message = message
-
- def __str__(self):
- return self.message
+ pass
class HostError(Error, KeyError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host error: "%s"' % hostname
+ self.message = u'Host error: "%s"' % hostname
class HostAlreadyExistsError(HostError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host already exists: "%s"' % hostname
+ self.message = u'Host already exists: "%s"' % hostname
class HostNotFoundError(HostError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host not found: "%s"' % hostname
+ self.message = u'Host not found: "%s"' % hostname
class Host(Sequence):
hosts - A dictionary with string keys (hostnames) and Host instances values.
"""
+ handler_help = u"Manage DHCP hosts"
+
def __init__(self, hosts):
r"Initialize HostHandler object, see class documentation for details."
self.hosts = hosts
- @handler(u'Add a new host.')
+ @handler(u'Add a new host')
def add(self, name, ip, mac):
r"add(name, ip, mac) -> None :: Add a host to the hosts list."
if name in self.hosts:
raise HostAlreadyExistsError(name)
self.hosts[name] = Host(name, ip, mac)
- @handler(u'Update a host.')
+ @handler(u'Update a host')
def update(self, name, ip=None, mac=None):
r"update(name[, ip[, mac]]) -> None :: Update a host of the hosts list."
if not name in self.hosts:
if mac is not None:
self.hosts[name].mac = mac
- @handler(u'Delete a host.')
+ @handler(u'Delete a host')
def delete(self, name):
r"delete(name) -> None :: Delete a host of the hosts list."
if not name in self.hosts:
raise HostNotFoundError(name)
del self.hosts[name]
- @handler(u'Get information about a host.')
+ @handler(u'Get information about a host')
def get(self, name):
r"get(name) -> Host :: List all the information of a host."
if not name in self.hosts:
raise HostNotFoundError(name)
return self.hosts[name]
- @handler(u'List hosts.')
+ @handler(u'List hosts')
def list(self):
r"list() -> tuple :: List all the hostnames."
return self.hosts.keys()
- @handler(u'Get information about all hosts.')
+ @handler(u'Get information about all hosts')
def show(self):
r"show() -> list of Hosts :: List all the complete hosts information."
return self.hosts.values()
Both defaults to the current working directory.
"""
+ handler_help = u"Manage DHCP service"
+
_initd_name = 'dhcpd'
_persistent_attrs = ('params', 'hosts')
message - A descriptive error message.
"""
-
- def __init__(self, message):
- r"Initialize the Error object. See class documentation for more info."
- self.message = message
-
- def __str__(self):
- return self.message
+ pass
class ZoneError(Error, KeyError):
r"""
def __init__(self, zonename):
r"Initialize the object. See class documentation for more info."
- self.message = 'Zone error: "%s"' % zonename
+ self.message = u'Zone error: "%s"' % zonename
class ZoneNotFoundError(ZoneError):
r"""
def __init__(self, zonename):
r"Initialize the object. See class documentation for more info."
- self.message = 'zone not found: "%s"' % zonename
+ self.message = u'zone not found: "%s"' % zonename
class ZoneAlreadyExistsError(ZoneError):
r"""
def __init__(self, zonename):
r"Initialize the object. See class documentation for more info."
- self.message = 'Zone already exists: "%s"' % zonename
+ self.message = u'Zone already exists: "%s"' % zonename
class HostError(Error, KeyError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host error: "%s"' % hostname
+ self.message = u'Host error: "%s"' % hostname
class HostAlreadyExistsError(HostError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host already exists: "%s"' % hostname
+ self.message = u'Host already exists: "%s"' % hostname
class HostNotFoundError(HostError):
r"""
- HostNotFoundError(hostname) -> HostNotFoundError instance
+ HostNotFoundError(hostname) -> HostNotFoundError instance.
This exception is raised when trying to operate on a hostname that doesn't
exists.
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host not found: "%s"' % hostname
+ self.message = u'Host not found: "%s"' % hostname
class MailExchangeError(Error, KeyError):
r"""
- MailExchangeError(hostname) -> MailExchangeError instance
+ MailExchangeError(hostname) -> MailExchangeError instance.
This is the base exception for all mail exchange related errors.
"""
def __init__(self, mx):
r"Initialize the object. See class documentation for more info."
- self.message = 'Mail Exchange error: "%s"' % mx
+ self.message = u'Mail Exchange error: "%s"' % mx
class MailExchangeAlreadyExistsError(MailExchangeError):
r"""
- MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError instance
+ MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError.
This exception is raised when trying to add a mail exchange that already exists.
"""
def __init__(self, mx):
r"Initialize the object. See class documentation for more info."
- self.message = 'Mail Exchange already exists: "%s"' % mx
+ self.message = u'Mail Exchange already exists: "%s"' % mx
class MailExchangeNotFoundError(MailExchangeError):
r"""
- MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance
+ MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance.
- This exception is raised when trying to operate on a mail exchange that doesn't
- exists.
+ This exception is raised when trying to operate on a mail exchange that
+ doesn't exists.
"""
def __init__(self, mx):
class NameServerAlreadyExistsError(NameServerError):
r"""
- NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError instance
+ NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError.
- This exception is raised when trying to add a name server that already exists.
+ This exception is raised when trying to add a name server that already
+ exists.
"""
def __init__(self, ns):
class NameServerNotFoundError(NameServerError):
r"""
- NameServerNotFoundError(hostname) -> NameServerNotFoundError instance
+ NameServerNotFoundError(hostname) -> NameServerNotFoundError instance.
- This exception is raised when trying to operate on a name server that doesn't
- exists.
+ This exception is raised when trying to operate on a name server that
+ doesn't exists.
"""
def __init__(self, ns):
return (self.name, self.ip)
class HostHandler(Handler):
+
+ handler_help = u"Manage DNS hosts"
+
def __init__(self,zones):
self.zones = zones
class MailExchangeHandler(Handler):
+ handler_help = u"Manage DNS mail exchangers (MX)"
+
def __init__(self, zones):
self.zones = zones
class NameServerHandler(Handler):
+ handler_help = u"Manage DNS name servers (NS)"
+
def __init__(self, zones):
self.zones = zones
return (self.name, self.hosts, self.mxs, self.nss)
class ZoneHandler(Handler):
-
r"""ZoneHandler(zones) -> ZoneHandler instance :: Handle a list of zones.
This class is a helper for DnsHandler to do all the work related to zone
zones - A dictionary with string keys (zone name) and Zone instances values.
"""
+
+ handler_help = u"Manage DNS zones"
+
def __init__(self, zones):
self.zones = zones
Both defaults to the current working directory.
"""
+ handler_help = u"Manage DNS service"
+
_initd_name = 'bind'
_persistent_attrs = ('params', 'zones')
message - A descriptive error message.
"""
-
- def __init__(self, message):
- r"Initialize the Error object. See class documentation for more info."
- self.message = message
-
- def __str__(self):
- return self.message
+ pass
class RuleError(Error, KeyError):
r"""
def __init__(self, rule):
r"Initialize the object. See class documentation for more info."
- self.message = 'Rule error: "%s"' % rule
+ self.message = u'Rule error: "%s"' % rule
class RuleAlreadyExistsError(RuleError):
r"""
def __init__(self, rule):
r"Initialize the object. See class documentation for more info."
- self.message = 'Rule already exists: "%s"' % rule
+ self.message = u'Rule already exists: "%s"' % rule
class RuleNotFoundError(RuleError):
r"""
def __init__(self, rule):
r"Initialize the object. See class documentation for more info."
- self.message = 'Rule not found: "%s"' % rule
+ self.message = u'Rule not found: "%s"' % rule
class Rule(Sequence):
rules - A list of Rule objects.
"""
+ handler_help = u"Manage firewall rules"
+
def __init__(self, rules):
r"Initialize the object, see class documentation for details."
self.rules = rules
- @handler(u'Add a new rule.')
+ @handler(u'Add a new rule')
def add(self, *args, **kwargs):
r"add(rule) -> None :: Add a rule to the rules list (see Rule doc)."
rule = Rule(*args, **kwargs)
raise RuleAlreadyExistsError(rule)
self.rules.append(rule)
- @handler(u'Update a rule.')
+ @handler(u'Update a rule')
def update(self, index, *args, **kwargs):
r"update(index, rule) -> None :: Update a rule (see Rule doc)."
# TODO check if the modified rule is the same of an existing one
except IndexError:
raise RuleNotFoundError(index)
- @handler(u'Delete a rule.')
+ @handler(u'Delete a rule')
def delete(self, index):
r"delete(index) -> Rule :: Delete a rule from the list returning it."
index = int(index) # TODO validation
except IndexError:
raise RuleNotFoundError(index)
- @handler(u'Get information about a rule.')
+ @handler(u'Get information about a rule')
def get(self, index):
r"get(rule) -> Rule :: Get all the information about a rule."
index = int(index) # TODO validation
except IndexError:
raise RuleNotFoundError(index)
- @handler(u'Get information about all rules.')
+ @handler(u'Get information about all rules')
def show(self):
r"show() -> list of Rules :: List all the complete rules information."
return self.rules
Both defaults to the current working directory.
"""
+ handler_help = u"Manage firewall service"
+
_persistent_attrs = 'rules'
_restorable_defaults = dict(rules=list())
message - A descriptive error message.
"""
-
- def __init__(self, message):
- r"Initialize the Error object. See class documentation for more info."
- self.message = message
-
- def __str__(self):
- return self.message
+ pass
class DeviceError(Error):
def __init__(self, device):
- self.message = 'Device error : "%s"' % device
+ self.message = u'Device error : "%s"' % device
class DeviceNotFoundError(DeviceError):
def __init__(self, device):
- self.message = 'Device not found : "%s"' % device
+ self.message = u'Device not found : "%s"' % device
class AddressError(Error):
def __init__(self, addr):
- self.message = 'Address error : "%s"' % addr
+ self.message = u'Address error : "%s"' % addr
class AddressNotFoundError(AddressError):
def __init__(self, address):
- self.message = 'Address not found : "%s"' % address
+ self.message = u'Address not found : "%s"' % address
class AddressAlreadyExistsError(AddressError):
def __init__(self, address):
- self.message = 'Address already exists : "%s"' % address
+ self.message = u'Address already exists : "%s"' % address
class RouteError(Error):
def __init__(self, route):
- self.message = 'Route error : "%s"' % route
+ self.message = u'Route error : "%s"' % route
class RouteNotFoundError(RouteError):
def __init__(self, route):
- self.message = 'Route not found : "%s"' % route
+ self.message = u'Route not found : "%s"' % route
class RouteAlreadyExistsError(RouteError):
def __init__(self, route):
- self.message = 'Route already exists : "%s"' % route
+ self.message = u'Route already exists : "%s"' % route
class Route(Sequence):
class RouteHandler(Handler):
+ handler_help = u"Manage IP routes"
+
def __init__(self, devices):
self.devices = devices
class AddressHandler(Handler):
+ handler_help = u"Manage IP addresses"
+
def __init__(self, devices):
self.devices = devices
class DeviceHandler(Handler):
+ handler_help = u"Manage network devices"
+
def __init__(self, devices):
# FIXME remove templates to execute commands
from mako.template import Template
class IpHandler(Restorable, ConfigWriter, TransactionalHandler):
+ handler_help = u"Manage IP devices, addresses and routes"
+
_persistent_attrs = 'devices'
_restorable_defaults = dict(devices=get_devices())
message - A descriptive error message.
"""
-
- def __init__(self, message):
- r"Initialize the Error object. See class documentation for more info."
- self.message = message
-
- def __str__(self):
- return self.message
+ pass
class HostError(Error, KeyError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host error: "%s"' % hostname
+ self.message = u'Host error: "%s"' % hostname
class HostAlreadyExistsError(HostError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host already exists: "%s"' % hostname
+ self.message = u'Host already exists: "%s"' % hostname
class HostNotFoundError(HostError):
r"""
def __init__(self, hostname):
r"Initialize the object. See class documentation for more info."
- self.message = 'Host not found: "%s"' % hostname
+ self.message = u'Host not found: "%s"' % hostname
class Host(Sequence):
class HostHandler(Handler):
+ handler_help = u"Manage proxy hosts"
+
def __init__(self, hosts):
self.hosts = hosts
class ProxyHandler(Restorable, ConfigWriter, InitdHandler,
TransactionalHandler, ParametersHandler):
+ handler_help = u"Manage proxy service"
+
_initd_name = 'squid'
_persistent_attrs = ('params', 'hosts')