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).
22 def __init__(self, command):
23 r"""Initialize the Error object.
25 See Error class documentation for more info.
27 self.command = command
30 return 'Command not found: "%s"' % ' '.join(self.command)
32 class CommandNotFoundError(Error):
34 CommandNotFoundError(command) -> CommandNotFoundError instance
36 This exception is raised when the command received can't be dispatched
37 because there is no handlers to process it.
42 f._dispatcher_handler = True
45 def is_handler(handler):
46 return callable(handler) and hasattr(handler, '_dispatcher_handler') \
47 and handler._dispatcher_handler
50 r"""Dispatcher([routes]) -> Dispatcher instance :: Command dispatcher
52 This class provides a modular and extensible dispatching mechanism. You
53 can specify root 'routes' (as a dict where the key is the string of the
54 root command and the value is a callable object to handle that command,
55 or a subcommand if the callable is an instance and the command can be
58 The command can have arguments, separated by (any number of) spaces.
60 The dispatcher tries to route the command as deeply as it can, passing
61 the other "path" components as arguments to the callable. To route the
62 command it inspects the callable attributes to find a suitable callable
63 attribute to handle the command in a more specific way, and so on.
66 >>> d = Dispatcher(dict(handler=some_handler))
67 >>> d.dispatch('handler attribute method arg1 arg2')
69 If 'some_handler' is an object with an 'attribute' that is another
70 object which has a method named 'method', then
71 some_handler.attribute.method('arg1', 'arg2') will be called. If
72 some_handler is a function, then some_handler('attribute', 'method',
73 'arg1', 'arg2') will be called. The handler "tree" can be as complex
76 If some command can't be dispatched (because there is no root handler or
77 there is no matching callable attribute), a CommandNotFoundError is raised.
80 def __init__(self, routes=dict()):
81 r"""Initialize the Dispatcher object.
83 See Dispatcher class documentation for more info.
87 def dispatch(self, route):
88 r"""dispatch(route) -> None :: Dispatch a command string.
90 This method searches for a suitable callable object in the routes
91 "tree" and call it, or raises a CommandNotFoundError if the command
95 route = route.split() # TODO support "" and keyword arguments
97 raise CommandNotFoundError(command)
98 command.append(route[0])
99 handler = self.routes.get(route[0], None)
101 raise CommandNotFoundError(command)
103 while not is_handler(handler):
105 raise CommandNotFoundError(command)
106 command.append(route[0])
107 if not hasattr(handler, route[0]):
108 raise CommandNotFoundError(command)
109 handler = getattr(handler, route[0])
111 return handler(*route)
114 if __name__ == '__main__':
117 def test_func(*args):
120 class TestClassSubHandler:
122 def subcmd(self, *args):
123 print 'class.subclass.subcmd:', args
127 def cmd1(self, *args):
128 print 'class.cmd1:', args
130 def cmd2(self, *args):
131 print 'class.cmd2:', args
132 subclass = TestClassSubHandler()
139 d.dispatch('func arg1 arg2 arg3')
140 d.dispatch('inst cmd1 arg1 arg2 arg3 arg4')
141 d.dispatch('inst subclass subcmd arg1 arg2 arg3 arg4 arg5')
144 except CommandNotFoundError, e:
145 print 'Not found:', e
147 d.dispatch('sucutrule piquete culete')
148 except CommandNotFoundError, e:
149 print 'Not found:', e
151 d.dispatch('inst cmd3 arg1 arg2 arg3')
152 except CommandNotFoundError, e:
153 print 'Not found:', e