1 """Error handling functions."""
2
3 __all__ = ['dispatch_error', 'dispatch_error_adaptor', 'try_call',
4 'run_with_errors', 'default', 'register_handler', 'FailsafeSchema',
5 'dispatch_failsafe', 'error_handler', 'exception_handler']
6
7 import sys
8 from itertools import izip, islice
9 from inspect import getargspec
10
11 import cherrypy
12 from peak.rules import abstract, when, around, NoApplicableMethods
13
14 from turbogears.util import (inject_args, adapt_call, call_on_stack, has_arg,
15 remove_keys, Enum)
16 from turbogears.decorator import func_id
17
18
19 @abstract()
20 -def dispatch_error(controller, tg_source,
21 tg_errors, tg_exceptions, *args, **kw):
22 """Dispatch error.
23
24 Error handler is a function registered via register_handler or if no
25 such decorator was applied, the method triggering the error.
26
27 """
28
29
30 dispatch_error.when = when.__get__(dispatch_error)
31 dispatch_error.around = around.__get__(dispatch_error)
36 """Register implicitly declared error handler and re-dispatch.
37
38 Any method declaring tg_errors parameter is considered an implicitly
39 declared error handler.
40
41 """
42 error_handler(tg_source)(tg_source)
43 return dispatch_error(controller, tg_source,
44 tg_errors, tg_exceptions, *args, **kw)
45
49 """Register implicitly declared exception handler and re-dispatch.
50
51 Any method declaring tg_exceptions parameter is considered an
52 implicitly declared exception handler.
53
54 """
55 exception_handler(tg_source)(tg_source)
56 return dispatch_error(controller, tg_source,
57 tg_errors, tg_exceptions, *args, **kw)
58
60 """Construct a signature isomorphic to dispatch_error.
61
62 The actual handler will receive only arguments explicitly
63 declared, and a possible tg_format parameter.
64
65 """
66 def adaptor(controller, tg_source,
67 tg_errors, tg_exceptions, *args, **kw):
68 tg_format = kw.pop('tg_format', None)
69 args, kw = inject_args(func, {'tg_source': tg_source,
70 'tg_errors': tg_errors, 'tg_exceptions': tg_exceptions},
71 args, kw, 1)
72 args, kw = adapt_call(func, args, kw, 1)
73 if tg_format is not None:
74 kw['tg_format'] = tg_format
75 return func(controller, *args, **kw)
76 return adaptor
77
79 """Call function, catch and dispatch any resulting exception."""
80
81 from turbogears.database import restart_transaction
82 try:
83 return func(self, *args, **kw)
84 except Exception, e:
85 if (isinstance(e, cherrypy.HTTPRedirect)
86 or call_on_stack('dispatch_error',
87 {'tg_source': func, 'tg_exception': e}, 4)):
88 raise
89 else:
90 exc_type, exc_value, exc_trace = sys.exc_info()
91 remove_keys(kw, ('tg_source', 'tg_errors', 'tg_exceptions'))
92 if 'tg_format' in cherrypy.request.params:
93 kw['tg_format'] = 'json'
94 if getattr(cherrypy.request, 'in_transaction', None):
95 restart_transaction(1)
96 try:
97 output = dispatch_error(self, func, None, e, *args, **kw)
98 except NoApplicableMethods:
99 raise exc_type, exc_value, exc_trace
100 else:
101 del exc_trace
102 return output
103
105 """Branch execution depending on presence of errors."""
106 if errors:
107 if hasattr(self, 'validation_error'):
108 import warnings
109 warnings.warn(
110 "Use decorator error_handler() on per-method base "
111 "rather than defining a validation_error() method.",
112 DeprecationWarning, 2)
113 return self.validation_error(func.__name__, kw, errors)
114 else:
115 remove_keys(kw, ('tg_source', 'tg_errors', 'tg_exceptions'))
116 if 'tg_format' in cherrypy.request.params:
117 kw['tg_format'] = 'json'
118 try:
119 return dispatch_error(self, func, errors, None, *args, **kw)
120 except NoApplicableMethods:
121 raise NotImplementedError("Method %s.%s() has no applicable "
122 "error handler." % (self.__class__.__name__, func.__name__))
123 else:
124 return func(self, *args, **kw)
125
127 """Register handler as an error handler for decorated method.
128
129 If handler is not given, method is considered its own error handler.
130
131 rules can be a string containing an arbitrary logical Python expression
132 to be used as dispatch rule allowing multiple error handlers for a
133 single method.
134
135 register_handler decorator is an invariant.
136
137 """
138 def register(func):
139 new_rules = "func_id(tg_source) == %d" % func_id(func)
140 if rules:
141 new_rules += " and (%s)" % rules
142 around(dispatch_error, new_rules)(
143 dispatch_error_adaptor(handler or func))
144 return func
145 return register
146
148 """Prepend rules to error handler specialisation."""
149 def registrant(handler=None, rules=None):
150 new_rules = pre_rules
151 if rules:
152 new_rules += " and (%s)" % rules
153 return register_handler(handler, new_rules)
154 return registrant
155
156 error_handler = bind_rules('tg_errors')
157 exception_handler = bind_rules('tg_exceptions')
158
159
160 FailsafeSchema = Enum('none', 'values', 'map_errors', 'defaults')
163 """Dispatch fail-safe mechanism for failed inputs."""
164 return kw
165
166 @when(dispatch_failsafe, "schema is FailsafeSchema.values"
167 " and isinstance(values, dict) and 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 @when(dispatch_failsafe,
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 @when(dispatch_failsafe,
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 @when(dispatch_failsafe,
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(
196 argnames, len(argnames) - len(defaultvals), None), defaultvals))
197 for key in errors:
198 if key in defaults:
199 kw[key] = defaults[key]
200 return kw
201