1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
#
# Copyright 2015 Rackspace, Inc
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
from http import client as http_client
import json
import sys
import traceback
from oslo_config import cfg
from oslo_log import log
import pecan
LOG = log.getLogger(__name__)
pecan_json_decorate = pecan.expose(
content_type='application/json',
generic=False)
def expose(status_code=None):
def decorate(f):
@functools.wraps(f)
def callfunction(self, *args, **kwargs):
try:
result = f(self, *args, **kwargs)
if status_code:
pecan.response.status = status_code
except Exception:
try:
exception_info = sys.exc_info()
orig_exception = exception_info[1]
orig_code = getattr(orig_exception, 'code', None)
result = format_exception(
exception_info,
cfg.CONF.debug_tracebacks_in_api
)
finally:
del exception_info
if orig_code and orig_code in http_client.responses:
pecan.response.status = orig_code
else:
pecan.response.status = 500
def _empty():
# This is for a pecan workaround originally in WSME,
# but the original issue description is in an issue tracker
# that is now offline
pecan.request.pecan['content_type'] = None
pecan.response.content_type = None
# never return content for NO_CONTENT
if pecan.response.status_code == 204:
return _empty()
# don't encode None for ACCEPTED responses
if result is None and pecan.response.status_code == 202:
return _empty()
return json.dumps(result)
pecan_json_decorate(callfunction)
return callfunction
return decorate
def body(body_arg):
"""Decorator which places HTTP request body JSON into a method argument
:param body_arg: Name of argument to populate with body JSON
"""
def inner_function(function):
@functools.wraps(function)
def inner_body(*args, **kwargs):
if pecan.request.body:
data = pecan.request.json
else:
data = {}
if isinstance(data, dict):
# remove any keyword arguments which pecan has
# extracted from the body
for field in data.keys():
kwargs.pop(field, None)
kwargs[body_arg] = data
return function(*args, **kwargs)
return inner_body
return inner_function
def format_exception(excinfo, debug=False):
"""Extract informations that can be sent to the client."""
error = excinfo[1]
code = getattr(error, 'code', None)
if code and code in http_client.responses and (400 <= code < 500):
faultstring = (error.faultstring if hasattr(error, 'faultstring')
else str(error))
faultcode = getattr(error, 'faultcode', 'Client')
r = dict(faultcode=faultcode,
faultstring=faultstring)
LOG.debug("Client-side error: %s", r['faultstring'])
r['debuginfo'] = None
return r
else:
faultstring = str(error)
debuginfo = "\n".join(traceback.format_exception(*excinfo))
LOG.error('Server-side error: "%s". Detail: \n%s',
faultstring, debuginfo)
faultcode = getattr(error, 'faultcode', 'Server')
r = dict(faultcode=faultcode, faultstring=faultstring)
if debug:
r['debuginfo'] = debuginfo
else:
r['debuginfo'] = None
return r
|