comparison env/lib/python3.9/site-packages/galaxy/util/json.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import json
2 import logging
3 import math
4 import random
5 import string
6 from collections.abc import (
7 Iterable,
8 Mapping,
9 Sequence,
10 )
11
12
13 from ..util import unicodify
14
15 __all__ = ("safe_dumps", "validate_jsonrpc_request", "validate_jsonrpc_response", "jsonrpc_request", "jsonrpc_response")
16
17 log = logging.getLogger(__name__)
18
19 to_json_string = json.dumps
20 from_json_string = json.loads
21
22
23 def swap_inf_nan(val):
24 """
25 This takes an arbitrary object and preps it for jsonifying safely, templating Inf/NaN.
26 """
27 if isinstance(val, str):
28 # basestring first, because it's a sequence and would otherwise get caught below.
29 return val
30 elif isinstance(val, Sequence):
31 return [swap_inf_nan(v) for v in val]
32 elif isinstance(val, Mapping):
33 return {swap_inf_nan(k): swap_inf_nan(v) for (k, v) in val.items()}
34 elif isinstance(val, float):
35 if math.isnan(val):
36 return "__NaN__"
37 elif val == float("inf"):
38 return "__Infinity__"
39 elif val == float("-inf"):
40 return "__-Infinity__"
41 else:
42 return val
43 else:
44 return val
45
46
47 def safe_loads(arg):
48 """
49 This is a wrapper around loads that returns the parsed value instead of
50 raising a value error. It also avoids autoconversion of non-iterables
51 i.e numeric and boolean values.
52 """
53 try:
54 loaded = json.loads(arg)
55 if loaded is not None and not isinstance(loaded, Iterable):
56 loaded = arg
57 except (TypeError, ValueError):
58 loaded = arg
59 return loaded
60
61
62 def safe_dumps(*args, **kwargs):
63 """
64 This is a wrapper around dumps that encodes Infinity and NaN values. It's a
65 fairly rare case (which will be low in request volume). Basically, we tell
66 json.dumps to blow up if it encounters Infinity/NaN, and we 'fix' it before
67 re-encoding.
68 """
69 try:
70 dumped = json.dumps(*args, allow_nan=False, **kwargs)
71 except ValueError:
72 obj = swap_inf_nan(args[0])
73 dumped = json.dumps(obj, allow_nan=False, **kwargs)
74 if kwargs.get('escape_closing_tags', True):
75 return dumped.replace('</', '<\\/')
76 return dumped
77
78
79 def safe_dumps_formatted(obj):
80 """Attempt to format an object for display.
81
82 If serialization fails, the object's string representation will be returned instead.
83 """
84 try:
85 return safe_dumps(obj, sort_keys=True, indent=4, separators=(',', ': '))
86 except TypeError:
87 log.warning("JSON serialization failed for object: %s", str(obj))
88 return str(obj)
89
90
91 # Methods for handling JSON-RPC
92
93 def validate_jsonrpc_request(request, regular_methods, notification_methods):
94 try:
95 request = json.loads(request)
96 except Exception as e:
97 return False, request, jsonrpc_response(id=None,
98 error=dict(code=-32700,
99 message='Parse error',
100 data=unicodify(e)))
101 try:
102 assert 'jsonrpc' in request, \
103 'This server requires JSON-RPC 2.0 and no "jsonrpc" member was sent with the Request object as per the JSON-RPC 2.0 Specification.'
104 assert request['jsonrpc'] == '2.0', \
105 'Requested JSON-RPC version "%s" != required version "2.0".' % request['jsonrpc']
106 assert 'method' in request, 'No "method" member was sent with the Request object'
107 except AssertionError as e:
108 return False, request, jsonrpc_response(request=request,
109 error=dict(code=-32600,
110 message='Invalid Request',
111 data=unicodify(e)))
112 try:
113 assert request['method'] in (regular_methods + notification_methods)
114 except AssertionError:
115 return False, request, jsonrpc_response(request=request,
116 error=dict(code=-32601,
117 message='Method not found',
118 data='Valid methods are: %s' % ', '.join(regular_methods + notification_methods)))
119 try:
120 if request['method'] in regular_methods:
121 assert 'id' in request, 'No "id" member was sent with the Request object and the requested method "%s" is not a notification method' % request['method']
122 except AssertionError as e:
123 return False, request, jsonrpc_response(request=request,
124 error=dict(code=-32600,
125 message='Invalid Request',
126 data=unicodify(e)))
127 return True, request, None
128
129
130 def validate_jsonrpc_response(response, id=None):
131 try:
132 response = json.loads(response)
133 except Exception as e:
134 log.error('Response was not valid JSON: %s', unicodify(e))
135 log.debug('Response was: %s', response)
136 return False, response
137 try:
138 assert 'jsonrpc' in response, \
139 'This server requires JSON-RPC 2.0 and no "jsonrpc" member was sent with the Response object as per the JSON-RPC 2.0 Specification.'
140 assert ('result' in response or 'error' in response), \
141 'Neither of "result" or "error" members were sent with the Response object.'
142 if 'error' in response:
143 assert int(response['error']['code']), \
144 'The "code" member of the "error" object in the Response is missing or not an integer.'
145 assert 'message' in response, \
146 'The "message" member of the "error" object in the Response is missing.'
147 except Exception:
148 log.exception('Response was not valid JSON-RPC')
149 log.debug('Response was: %s' % response)
150 return False, response
151 if id is not None:
152 try:
153 assert 'id' in response and response['id'] == id
154 except Exception:
155 log.error('The response id "{}" does not match the request id "{}"'.format(response['id'], id))
156 return False, response
157 return True, response
158
159
160 def jsonrpc_request(method, params=None, id=None, jsonrpc='2.0'):
161 if method is None:
162 log.error('jsonrpc_request(): "method" parameter cannot be None')
163 return None
164 request = dict(jsonrpc=jsonrpc, method=method)
165 if params:
166 request['params'] = params
167 if id is not None and id is True:
168 request['id'] = ''.join(random.choice(string.hexdigits) for i in range(16))
169 elif id is not None:
170 request['id'] = id
171 return request
172
173
174 def jsonrpc_response(request=None, id=None, result=None, error=None, jsonrpc='2.0'):
175 if result:
176 rval = dict(jsonrpc=jsonrpc, result=result)
177 elif error:
178 rval = dict(jsonrpc=jsonrpc, error=error)
179 else:
180 msg = 'jsonrpc_response() called with out a "result" or "error" parameter'
181 log.error(msg)
182 rval = dict(jsonrpc=jsonrpc, error=msg)
183 if id is not None:
184 rval['id'] = id
185 elif request is not None and 'id' in request:
186 rval['id'] = request['id']
187 return rval