1 # vim: set et sts=4 sw=4 encoding=utf-8 :
6 This module provides a convenient and extensible command dispatching mechanism.
7 It's based on Zope or Cherrypy dispatching (but implemented from the scratch)
8 and translates commands to functions/objects/methods.
11 class Error(RuntimeError):
13 Error(command) -> Error instance :: Base dispatching exceptions class.
15 All exceptions raised by the Dispatcher inherits from this one, so you can
16 easily catch any dispatching exception.
18 command - is the command that raised the exception, expressed as a list of
19 paths (or subcommands).
23 class HandlerError(Error):
25 HandlerError(command) -> HandlerError instance :: Base handlers exception.
27 All exceptions raised by the handlers should inherit from this one, so
28 dispatching errors could be separated from real programming errors (bugs).
33 class CommandNotFoundError(Error):
35 CommandNotFoundError(command) -> CommandNotFoundError instance
37 This exception is raised when the command received can't be dispatched
38 because there is no handlers to process it.
41 def __init__(self, command):
42 r"""Initialize the Error object.
44 See Error class documentation for more info.
46 self.command = command
49 return 'Command not found: "%s"' % ' '.join(self.command)
52 f._dispatcher_handler = True
55 def is_handler(handler):
56 return callable(handler) and hasattr(handler, '_dispatcher_handler') \
57 and handler._dispatcher_handler
60 r"""Dispatcher([routes]) -> Dispatcher instance :: Command dispatcher
62 This class provides a modular and extensible dispatching mechanism. You
63 can specify root 'routes' (as a dict where the key is the string of the
64 root command and the value is a callable object to handle that command,
65 or a subcommand if the callable is an instance and the command can be
68 The command can have arguments, separated by (any number of) spaces.
70 The dispatcher tries to route the command as deeply as it can, passing
71 the other "path" components as arguments to the callable. To route the
72 command it inspects the callable attributes to find a suitable callable
73 attribute to handle the command in a more specific way, and so on.
76 >>> d = Dispatcher(dict(handler=some_handler))
77 >>> d.dispatch('handler attribute method arg1 arg2')
79 If 'some_handler' is an object with an 'attribute' that is another
80 object which has a method named 'method', then
81 some_handler.attribute.method('arg1', 'arg2') will be called. If
82 some_handler is a function, then some_handler('attribute', 'method',
83 'arg1', 'arg2') will be called. The handler "tree" can be as complex
86 If some command can't be dispatched (because there is no root handler or
87 there is no matching callable attribute), a CommandNotFoundError is raised.
90 def __init__(self, routes=dict()):
91 r"""Initialize the Dispatcher object.
93 See Dispatcher class documentation for more info.
97 def dispatch(self, route):
98 r"""dispatch(route) -> None :: Dispatch a command string.
100 This method searches for a suitable callable object in the routes
101 "tree" and call it, or raises a CommandNotFoundError if the command
105 route = route.split() # TODO support "" and keyword arguments
107 raise CommandNotFoundError(command)
108 command.append(route[0])
109 handler = self.routes.get(route[0], None)
111 raise CommandNotFoundError(command)
113 while not is_handler(handler):
115 raise CommandNotFoundError(command)
116 command.append(route[0])
117 if not hasattr(handler, route[0]):
118 raise CommandNotFoundError(command)
119 handler = getattr(handler, route[0])
121 return handler(*route)
124 if __name__ == '__main__':
127 def test_func(*args):
130 class TestClassSubHandler:
132 def subcmd(self, *args):
133 print 'class.subclass.subcmd:', args
137 def cmd1(self, *args):
138 print 'class.cmd1:', args
140 def cmd2(self, *args):
141 print 'class.cmd2:', args
142 subclass = TestClassSubHandler()
149 d.dispatch('func arg1 arg2 arg3')
150 d.dispatch('inst cmd1 arg1 arg2 arg3 arg4')
151 d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
154 except CommandNotFoundError, e:
155 print 'Not found:', e
157 d.dispatch('sucutrule piquete culete')
158 except CommandNotFoundError, e:
159 print 'Not found:', e
161 d.dispatch('inst cmd3 arg1 arg2 arg3')
162 except CommandNotFoundError, e:
163 print 'Not found:', e