1 import sys
2 from itertools import izip, islice
3 from inspect import getargspec
4
5 import cherrypy
6 from peak.rules import abstract, when, around, NoApplicableMethods
7
8 from turbogears.util import inject_args, adapt_call, call_on_stack, has_arg, \
9 remove_keys, Enum
10 from turbogears.decorator import func_id
11
12
13 @abstract()
14 -def dispatch_error(controller, tg_source,
15 tg_errors, tg_exceptions, *args, **kw):
16 """Dispatch error.
17
18 Error handler is a function registered via register_handler or if no
19 such decorator was applied, the method triggering the error.
20
21 """
22
23
24 dispatch_error.when = when.__get__(dispatch_error)
25 dispatch_error.around = around.__get__(dispatch_error)
30 """Register implicitly declared error handler and re-dispatch.
31
32 Any method declaring tg_errors parameter is considered an implicitly
33 declared error handler.
34
35 """
36 error_handler(tg_source)(tg_source)
37 return dispatch_error(controller, tg_source,
38 tg_errors, tg_exceptions, *args, **kw)
39
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,
51 tg_errors, tg_exceptions, *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 else:
84 exc_type, exc_value, exc_trace = sys.exc_info()
85 remove_keys(kw, ("tg_source", "tg_errors", "tg_exceptions"))
86 if 'tg_format' in cherrypy.request.params:
87 kw['tg_format'] = 'json'
88 if getattr(cherrypy.request, "in_transaction", None):
89 restart_transaction(1)
90 try:
91 output = dispatch_error(self, func, None, e, *args, **kw)
92 except NoApplicableMethods:
93 raise exc_type, exc_value, exc_trace
94 else:
95 del exc_trace
96 return output
97
99 """Branch execution depending on presence of errors."""
100 if errors:
101 if hasattr(self, "validation_error"):
102 import warnings
103 warnings.warn(
104 "Use decorator error_handler() on per-method base "
105 "rather than defining a validation_error() method.",
106 DeprecationWarning, 2)
107 return self.validation_error(func.__name__, kw, errors)
108 else:
109 remove_keys(kw, ("tg_source", "tg_errors", "tg_exceptions"))
110 if 'tg_format' in cherrypy.request.params:
111 kw['tg_format'] = 'json'
112 try:
113 return dispatch_error(self, func, errors, None, *args, **kw)
114 except NoApplicableMethods:
115 raise NotImplementedError("Method %s.%s() has no applicable "
116 "error handler." % (self.__class__.__name__, func.__name__))
117 else:
118 return func(self, *args, **kw)
119
121 """Register handler as an error handler for decorated method.
122
123 If handler is not given, method is considered its own error handler.
124
125 rules can be a string containing an arbitrary logical Python expression
126 to be used as dispatch rule allowing multiple error handlers for a
127 single method.
128
129 register_handler decorator is an invariant.
130
131 """
132 def register(func):
133 new_rules = "func_id(tg_source) == %d" % func_id(func)
134 if rules:
135 new_rules += " and (%s)" % rules
136 around(dispatch_error, new_rules)(
137 dispatch_error_adaptor(handler or func))
138 return func
139 return register
140
142 """Prepend rules to error handler specialisation."""
143 def registrant(handler=None, rules=None):
144 new_rules = pre_rules
145 if rules:
146 new_rules += " and (%s)" % rules
147 return register_handler(handler, new_rules)
148 return registrant
149
150 error_handler = bind_rules("tg_errors")
151 exception_handler = bind_rules("tg_exceptions")
152
153
154 FailsafeSchema = Enum("none", "values", "map_errors", "defaults")
157 """Dispatch fail-safe mechanism for failed inputs."""
158 return kw
159
160 @when(dispatch_failsafe, "schema is FailsafeSchema.values"
161 " and isinstance(values, dict) and isinstance(errors, dict)")
163 """Map erroneous inputs to values."""
164 for key in errors:
165 if key in values:
166 kw[key] = values[key]
167 return kw
168
169 @when(dispatch_failsafe,
170 "schema is FailsafeSchema.values and isinstance(errors, dict)")
172 """Map all erroneous inputs to a single value."""
173 for key in errors:
174 kw[key] = values
175 return kw
176
177 @when(dispatch_failsafe,
178 "schema is FailsafeSchema.map_errors and isinstance(errors, dict)")
180 """Map erroneous inputs to corresponding exceptions."""
181 kw.update(errors)
182 return kw
183
184 @when(dispatch_failsafe,
185 "schema is FailsafeSchema.defaults and isinstance(errors, dict)")
187 """Map erroneous inputs to method defaults."""
188 argnames, defaultvals = getargspec(source)[::3]
189 defaults = dict(izip(islice(argnames, len(argnames) - len(defaultvals),
190 None), defaultvals))
191 for key in errors:
192 if key in defaults:
193 kw[key] = defaults[key]
194 return kw
195
196 __all__ = ["dispatch_error", "dispatch_error_adaptor", "try_call",
197 "run_with_errors", "default", "register_handler", "FailsafeSchema",
198 "dispatch_failsafe", "error_handler", "exception_handler"]
199