##// END OF EJS Templates
ext-json: fixed tests and made the lib more consistent with orjson vs simplejson
super-admin -
r5012:2b0b3872 default
parent child Browse files
Show More
@@ -1,83 +1,86 b''
1 1 import datetime
2 2 import decimal
3 3 import functools
4 4 # we keep simplejson for having dump functionality still
5 5 # orjson doesn't support it
6 import simplejson as sjson
7
6 8 import orjson
7 import simplejson
8 9 import orjson as json
9 10
10 11
11 12 from rhodecode.lib.datelib import is_aware
12 13 from rhodecode.lib.str_utils import safe_str
13 14
14 15 try:
15 16 import rhodecode.translation
16 17 except ImportError:
17 18 rhodecode = None
18 19
19 20 __all__ = ['json']
20 21
21 22
22 23 def _obj_dump(obj):
23 24 """
24 25 Custom function for dumping objects to JSON, if obj has __json__ attribute
25 26 or method defined it will be used for serialization
26 27
27 28 :param obj:
28 29 """
29
30 30 # See "Date Time String Format" in the ECMA-262 specification.
31 31 # some code borrowed from django 1.4
32 32 if isinstance(obj, set):
33 33 return list(obj)
34 34 elif isinstance(obj, datetime.datetime):
35 35 r = obj.isoformat()
36 36 if isinstance(obj.microsecond, int):
37 37 r = r[:23] + r[26:]
38 38 if r.endswith('+00:00'):
39 39 r = r[:-6] + 'Z'
40 40 return r
41 41 elif isinstance(obj, datetime.date):
42 42 return obj.isoformat()
43 43 elif isinstance(obj, datetime.time):
44 44 if is_aware(obj):
45 45 raise TypeError("Time-zone aware times are not JSON serializable")
46 46 r = obj.isoformat()
47 47 if isinstance(obj.microsecond, int):
48 48 r = r[:12]
49 49 return r
50 50 elif hasattr(obj, '__json__'):
51 51 if callable(obj.__json__):
52 52 return obj.__json__()
53 53 else:
54 54 return obj.__json__
55 55 elif isinstance(obj, decimal.Decimal):
56 56 return str(obj)
57 57 elif isinstance(obj, complex):
58 58 return [obj.real, obj.imag]
59 59 elif rhodecode and isinstance(obj, rhodecode.translation._LazyString):
60 60 return obj.eval()
61 61 else:
62 62 raise TypeError(repr(obj) + " is not JSON serializable")
63 63
64 64
65 sjson.dumps = functools.partial(sjson.dumps, default=_obj_dump)
66 sjson.dump = functools.partial(sjson.dump, default=_obj_dump)
67
65 68 json.dumps = functools.partial(json.dumps, default=_obj_dump, option=orjson.OPT_NON_STR_KEYS)
66 json.dump = functools.partial(simplejson.dump, default=_obj_dump)
69 json.dump = functools.partial(sjson.dump, default=_obj_dump)
67 70
68 71
69 72 def formatted_json(*args, **kwargs):
70 73 # alias for formatted json
71 74 opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
72 75 return functools.partial(json.dumps, option=opts)(*args, **kwargs)
73 76
74 77
75 78 def formatted_str_json(*args, **kwargs):
76 79 opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS
77 80 closure = functools.partial(json.dumps, option=opts)
78 81 return safe_str(closure(*args, **kwargs))
79 82
80 83
81 84 def str_json(*args, **kwargs):
82 85 closure = functools.partial(json.dumps)
83 86 return safe_str(closure(*args, **kwargs))
@@ -1,166 +1,167 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import io
21 22 import datetime
22 23 import decimal
23 import io
24 24 import textwrap
25 25
26 26 import pytest
27 27
28 from rhodecode.lib.ext_json import json
29 from rhodecode.lib.ext_json import formatted_json
28 from rhodecode.lib import ext_json
30 29 from rhodecode.translation import _, _pluralize
31 30
32 31
33 32 class Timezone(datetime.tzinfo):
34 33 def __init__(self, hours):
35 34 self.hours = hours
36 35
37 36 def utcoffset(self, unused_dt):
38 37 return datetime.timedelta(hours=self.hours)
39 38
40 39
40 class SerializableObject(object):
41 def __json__(self):
42 return 'foo'
43
44
41 45 def test_dumps_set():
42 result = json.dumps(set((1, 2, 3)))
46 result = ext_json.json.dumps(set((1, 2, 3)))
43 47 # We cannot infer what the order of result is going to be
44 result = json.loads(result)
48 result = ext_json.json.loads(result)
45 49 assert isinstance(result, list)
46 50 assert [1, 2, 3] == sorted(result)
47 51
48 52
49 53 def test_dumps_decimal():
50 assert '"1.5"' == json.dumps(decimal.Decimal('1.5'))
54 assert b'"1.5"' == ext_json.json.dumps(decimal.Decimal('1.5'))
51 55
52 56
53 57 def test_dumps_complex():
54 assert "[0.0, 1.0]" == json.dumps(1j)
55 assert "[1.0, 0.0]" == json.dumps(1 + 0j)
56 assert "[1.1, 1.2]" == json.dumps(1.1 + 1.2j)
58 assert b"[0.0,1.0]" == ext_json.json.dumps(1j)
59 assert b"[1.0,0.0]" == ext_json.json.dumps(1 + 0j)
60 assert b"[1.1,1.2]" == ext_json.json.dumps(1.1 + 1.2j)
57 61
58 62
59 63 def test_dumps_object_with_json_method():
60 class SerializableObject(object):
61 def __json__(self):
62 return 'foo'
63
64 assert '"foo"' == json.dumps(SerializableObject())
64 assert '"foo"' == ext_json.str_json(SerializableObject())
65 65
66 66
67 67 def test_dumps_object_with_json_attribute():
68 class SerializableObject(object):
69 __json__ = 'foo'
70 68
71 assert '"foo"' == json.dumps(SerializableObject())
69 assert '"foo"' == ext_json.str_json(SerializableObject())
72 70
73 71
74 72 def test_dumps_time():
75 assert '"03:14:15.926"' == json.dumps(datetime.time(3, 14, 15, 926535))
73 assert '"03:14:15.926535"' == ext_json.str_json(datetime.time(3, 14, 15, 926535))
76 74
77 75
78 76 def test_dumps_time_no_microseconds():
79 assert '"03:14:15"' == json.dumps(datetime.time(3, 14, 15))
77 assert '"03:14:15"' == ext_json.str_json(datetime.time(3, 14, 15))
80 78
81 79
82 80 def test_dumps_time_with_timezone():
83 81 with pytest.raises(TypeError) as excinfo:
84 json.dumps(datetime.time(3, 14, 15, 926535, Timezone(0)))
82 ext_json.json.dumps(datetime.time(3, 14, 15, 926535, Timezone(0)))
85 83
86 84 error_msg = str(excinfo.value)
87 assert 'Time-zone aware times are not JSON serializable' in error_msg
85
86 assert 'timezone library is not supported' in error_msg
87 # only for simplejson
88 #assert 'Time-zone aware times are not JSON serializable' in error_msg
88 89
89 90
90 91 def test_dumps_date():
91 assert '"1969-07-20"' == json.dumps(datetime.date(1969, 7, 20))
92 assert b'"1969-07-20"' == ext_json.json.dumps(datetime.date(1969, 7, 20))
92 93
93 94
94 95 def test_dumps_datetime():
95 json_data = json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15, 926535))
96 assert '"1969-07-20T03:14:15.926"' == json_data
96 json_data = ext_json.json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15, 926535))
97 assert b'"1969-07-20T03:14:15.926535"' == json_data
97 98
98 99
99 100 def test_dumps_datetime_no_microseconds():
100 json_data = json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15))
101 assert '"1969-07-20T03:14:15"' == json_data
101 json_data = ext_json.json.dumps(datetime.datetime(1969, 7, 20, 3, 14, 15))
102 assert b'"1969-07-20T03:14:15"' == json_data
102 103
103 104
104 105 def test_dumps_datetime_with_utc_timezone():
105 json_data = json.dumps(
106 json_data = ext_json.json.dumps(
106 107 datetime.datetime(1969, 7, 20, 3, 14, 15, 926535, Timezone(0)))
107 assert '"1969-07-20T03:14:15.926Z"' == json_data
108 assert b'"1969-07-20T03:14:15.926535+00:00"' == json_data
108 109
109 110
110 111 def test_dumps_datetime_with_plus1_timezone():
111 json_data = json.dumps(
112 json_data = ext_json.json.dumps(
112 113 datetime.datetime(1969, 7, 20, 3, 14, 15, 926535, Timezone(1)))
113 assert '"1969-07-20T03:14:15.926+01:00"' == json_data
114 assert b'"1969-07-20T03:14:15.926535+01:00"' == json_data
114 115
115 116
116 117 def test_dumps_unserializable_class():
117 118 unserializable_obj = object()
118 119 with pytest.raises(TypeError) as excinfo:
119 json.dumps(unserializable_obj)
120 ext_json.json.dumps(unserializable_obj)
120 121
121 assert repr(unserializable_obj) in str(excinfo.value)
122 assert 'object' in str(excinfo.value)
122 123 assert 'is not JSON serializable' in str(excinfo.value)
123 124
124 125
125 126 def test_dump_is_like_dumps():
126 127 data = {
127 128 'decimal': decimal.Decimal('1.5'),
128 129 'set': set([1]), # Just one element to guarantee the order
129 130 'complex': 1 - 1j,
130 131 'datetime': datetime.datetime(1969, 7, 20, 3, 14, 15, 926535),
131 132 'time': datetime.time(3, 14, 15, 926535),
132 133 'date': datetime.date(1969, 7, 20),
133 134 }
134 json_buffer = io.BytesIO()
135 json.dump(data, json_buffer)
135 json_buffer = io.StringIO() # StringIO because dump uses simplejson not orjson
136 ext_json.json.dump(data, json_buffer)
136 137
137 assert json.dumps(data) == json_buffer.getvalue()
138 assert ext_json.sjson.dumps(data) == json_buffer.getvalue()
138 139
139 140
140 141 def test_formatted_json():
141 142 data = {
142 143 'b': {'2': 2, '1': 1},
143 144 'a': {'3': 3, '4': 4},
144 145 }
145 146
146 147 expected_data = textwrap.dedent('''
147 148 {
148 "a": {
149 "3": 3,
150 "4": 4
151 },
152 "b": {
153 "1": 1,
154 "2": 2
155 }
149 "a": {
150 "3": 3,
151 "4": 4
152 },
153 "b": {
154 "1": 1,
155 "2": 2
156 }
156 157 }''').strip()
157 158
158 assert formatted_json(data) == expected_data
159 assert expected_data == ext_json.formatted_str_json(data)
159 160
160 161
161 162 def test_lazy_translation_string(baseapp):
162 163 data = {'label': _('hello')}
163 164 data2 = {'label2': _pluralize('singular', 'plural', 1)}
164 165
165 assert json.dumps(data) == '{"label": "hello"}'
166 assert json.dumps(data2) == '{"label2": "singular"}'
166 assert b'{"label":"hello"}' == ext_json.json.dumps(data)
167 assert b'{"label2":"singular"}' == ext_json.json.dumps(data2)
General Comments 0
You need to be logged in to leave comments. Login now