1 import sys
2 from itertools import izip, islice
3 from inspect import getargspec
4
5 import cherrypy
6 from dispatch import generic, NoApplicableMethods, strategy
7
8 from turbogears.util import inject_args, adapt_call, call_on_stack, has_arg, \
9 remove_keys, Enum, combine_contexts
10 from turbogears.decorator import func_eq
11 from turbogears.genericfunctions import MultiorderGenericFunction
12
13 default = strategy.default
14
15 [generic(MultiorderGenericFunction)]
16 -def dispatch_error(controller, tg_source, tg_errors, tg_exceptions,
17 *args, **kw):
18 """Dispatch error.
19
20 Error handler is a function registered via register_handler or if no
21 such decorator was applied, the method triggering the error.
22
23 """
24
25 [dispatch_error.when(
26 "(tg_errors and has_arg(tg_source, 'tg_errors'))", order=3)]
29 """Register implicitly declared error handler and re-dispatch.
30
31 Any method declaring tg_errors parameter is considered an implicitly
32 declared error handler.
33
34 """
35 error_handler(tg_source)(tg_source)
36 return dispatch_error(controller, tg_source, tg_errors, tg_exceptions,
37 *args, **kw)
38
39 [dispatch_error.when(
40 "(tg_exceptions and has_arg(tg_source, 'tg_exceptions'))", order=3)]
43 """Register implicitly declared exception handler and re-dispatch.
44
45 Any method declaring tg_exceptions parameter is considered an
46 implicitly declared exception handler.
47
48 """
49 exception_handler(tg_source)(tg_source)
50 return dispatch_error(controller, tg_source, tg_errors, tg_exceptions,
51 *args, **kw)
52
54 """Construct a signature isomorphic to dispatch_error.
55
56 The actual handler will receive only arguments explicitly
57 declared, and a possible tg_format parameter.
58
59 """
60 def adaptor(controller, tg_source,
61 tg_errors, tg_exceptions, *args, **kw):
62 tg_format = kw.pop('tg_format', None)
63 args, kw = inject_args(func, {"tg_source":tg_source,
64 "tg_errors":tg_errors, "tg_exceptions":tg_exceptions},
65 args, kw, 1)
66 args, kw = adapt_call(func, args, kw, 1)
67 if tg_format is not None:
68 kw['tg_format'] = tg_format
69 return func(controller, *args, **kw)
70 return adaptor
71
73 """Call function, catch and dispatch any resulting exception."""
74
75 from turbogears.database import restart_transaction
76 try:
77 return func(self, *args, **kw)
78 except Exception, e:
79 if isinstance(e, cherrypy.HTTPRedirect) or \
80 call_on_stack("dispatch_error",
81 {"tg_source":func, "tg_exception":e}, 4):
82 raise
83
84 else:
85 exc_type, exc_value, exc_trace = sys.exc_info()
86 remove_keys(kw, ("tg_source", "tg_errors", "tg_exceptions"))
87 if 'tg_format' in cherrypy.request.params:
88 kw['tg_format'] = 'json'
89 if getattr(cherrypy.request, "in_transaction", None):
90 restart_transaction(1)
91 try:
92 output = dispatch_error(self, func, None, e, *args, **kw)
93 except NoApplicableMethods:
94 raise exc_type, exc_value, exc_trace
95 else:
96 del exc_trace
97 return output
98
100 """Branch execution depending on presence of errors."""
101 if errors:
102 if hasattr(self, "validation_error"):
103 import warnings
104 warnings.warn(
105 "Use decorator error_handler() on per-method base "
106 "rather than defining a validation_error() method.",
107 DeprecationWarning, 2)
108 return self.validation_error(func.__name__, kw, errors)
109 else:
110 remove_keys(kw, ("tg_source", "tg_errors", "tg_exceptions"))
111 if 'tg_format' in cherrypy.request.params:
112 kw['tg_format'] = 'json'
113 try:
114 return dispatch_error(self, func, errors, None, *args, **kw)
115 except NoApplicableMethods:
116 raise NotImplementedError("Method %s.%s() has no applicable "
117 "error handler." % (self.__class__.__name__, func.__name__))
118 else:
119 return func(self, *args, **kw)
120
122 """Register handler as an error handler for decorated method.
123
124 If handler is not given, method is considered it's own error handler.
125
126 rules can be a string containing an arbitrary logical Python expression
127 to be used as dispatch rule allowing multiple error handlers for a
128 single method.
129
130 register_handler decorator is an invariant.
131
132 """
133 def register(func):
134 when = "func_eq(tg_source, func)"
135 if rules:
136 when += " and (%s)" % rules
137 dispatch_error.when(dispatch_error.parse(when, *combine_contexts(
138 depth=[0, 1])), order=1)(dispatch_error_adaptor(handler or func))
139 return func
140 return register
141
143 """Prepend rules to error handler specialisation."""
144 def registrant(handler=None, rules=None):
145 when = pre_rules
146 if rules:
147 when += " and (%s)" % rules
148 return register_handler(handler, when)
149 return registrant
150
151 error_handler = bind_rules("tg_errors")
152 exception_handler = bind_rules("tg_exceptions")
153
154 FailsafeSchema = Enum("none", "values", "map_errors", "defaults")
155
156 [generic()]
158 """Dispatch fail-safe mechanism for failed inputs."""
159
160 [dispatch_failsafe.when(strategy.default)]
162 """No fail-safe values."""
163 return kw
164
165 [dispatch_failsafe.when(
166 "schema is FailsafeSchema.values and isinstance(values, dict) and "
167 "isinstance(errors, dict)")]
169 """Map erroneous inputs to values."""
170 for key in errors:
171 if key in values:
172 kw[key] = values[key]
173 return kw
174
175 [dispatch_failsafe.when(
176 "schema is FailsafeSchema.values and isinstance(errors, dict)")]
178 """Map all erroneous inputs to a single value."""
179 for key in errors:
180 kw[key] = values
181 return kw
182
183 [dispatch_failsafe.when(
184 "schema is FailsafeSchema.map_errors and isinstance(errors, dict)")]
186 """Map erroneous inputs to corresponding exceptions."""
187 kw.update(errors)
188 return kw
189
190 [dispatch_failsafe.when(
191 "schema is FailsafeSchema.defaults and isinstance(errors, dict)")]
193 """Map erroneous inputs to method defaults."""
194 argnames, defaultvals = getargspec(source)[::3]
195 defaults = dict(izip(islice(argnames, len(argnames) - len(defaultvals),
196 None), defaultvals))
197 for key in errors:
198 if key in defaults:
199 kw[key] = defaults[key]
200 return kw
201
202 __all__ = ["dispatch_error", "dispatch_error_adaptor", "try_call",
203 "run_with_errors", "default", "register_handler", "FailsafeSchema",
204 "dispatch_failsafe", "error_handler", "exception_handler"]
205