File: __init__.py

package info (click to toggle)
python-mujson 1.4-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,068 kB
  • sloc: python: 308; makefile: 17
file content (229 lines) | stat: -rw-r--r-- 7,939 bytes parent folder | download | duplicates (2)
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# -*- coding: utf-8 -*-
import collections
import inspect
import json
import sys


__version__ = '1.4'


SUPPORTED_FUNCS = ['loads', 'load', 'dumps', 'dump']


json_module = collections.namedtuple('json_module', ' '.join(SUPPORTED_FUNCS))


try:
    import ujson  # https://github.com/esnme/ultrajson
except ImportError:
    ujson = False

try:
    import rapidjson  # https://github.com/python-rapidjson/python-rapidjson
except ImportError:
    rapidjson = False

try:
    import simplejson  # https://github.com/simplejson/simplejson
    simplejson_slow = False
    if not simplejson.encoder.c_make_encoder:
        simplejson, simplejson_slow = simplejson_slow, simplejson
except ImportError:
    simplejson = simplejson_slow = False

try:
    import nssjson  # https://github.com/lelit/nssjson
    nssjson_slow = False
    if not nssjson.encoder.c_make_encoder:
        nssjson, nssjson_slow = nssjson_slow, nssjson
except ImportError:
    nssjson = nssjson_slow = False

try:
    import yajl  # https://github.com/rtyler/py-yajl
except ImportError:
    yajl = False

try:
    import cjson  # https://github.com/AGProjects/python-cjson
    cjson = json_module(
        dump=None, dumps=cjson.encode, load=None, loads=cjson.decode)
except ImportError:
    cjson = None

try:
    import metamagic.json as mjson  # https://github.com/sprymix/metamagic.json
except ImportError:
    mjson = False

try:
    import orjson  # https://github.com/ijl/orjson
except ImportError:
    orjson = False

try:
    pypy = sys.pypy_version_info is not None
except AttributeError:
    pypy = False

try:
    assert basestring is not None
except NameError:
    basestring = str

if pypy:
    # NOTE(mattgiles): in benchmarks, using pypy, the standard library's json
    # module outperforms all third party libraries.
    DEFAULT_RANKINGS = {
        'dump': [],
        'dumps': [],
        'load': [],
        'loads': []}

elif sys.version_info.major == 3:
    DEFAULT_RANKINGS = {
        'dump': [rapidjson, ujson, yajl, json, nssjson, simplejson, nssjson_slow, simplejson_slow],
        'dumps': [orjson, mjson, rapidjson, ujson, yajl, json, nssjson, simplejson, nssjson_slow, simplejson_slow],
        'load': [ujson, simplejson, rapidjson, json, nssjson, yajl, nssjson_slow, simplejson_slow],
        'loads': [orjson, ujson, simplejson, rapidjson, json, nssjson, yajl, nssjson_slow, simplejson_slow]}

else:
    DEFAULT_RANKINGS = {
        'dump': [ujson, yajl, json, nssjson, simplejson, nssjson_slow, simplejson_slow],
        'dumps': [ujson, yajl, json, cjson, nssjson, simplejson, nssjson_slow, simplejson_slow],
        'load': [ujson, simplejson, nssjson, yajl, json, simplejson_slow, nssjson_slow],
        'loads': [cjson, ujson, simplejson, nssjson, yajl, json, simplejson_slow, nssjson_slow]}


def _get_kwarg_names(func):
    try:
        # NOTE(mattgiles): there are compatibility issues between Python2 and
        # Python3 when using `inspect.getargspec`. Here we elect to try
        # Python27-compliant code first, falling back to Python36. This order
        # is important because of the behavior of intermediate Python versions.
        return inspect.getargspec(func)[0]
    except (AttributeError, TypeError, ValueError):
        return inspect.getfullargspec(func).kwonlyargs


def _best_available_json_func(func_name, ranking, **kwargs):
    _available = []
    for module in ranking:
        if isinstance(module, basestring):
            if module not in globals():
                try:
                    globals()[module] = __import__(module)
                except ModuleNotFoundError:
                    globals()[module] = False
            if globals()[module]:
                _available.append(globals()[module])
        else:
            if module:
                _available.append(module)

    if not kwargs:
        return getattr(_available[0], func_name)

    for module in _available:
        try:
            func = getattr(module, func_name)
            if set(kwargs.keys()).issubset(_get_kwarg_names(func)):
                return func
        except (AttributeError, TypeError, ValueError):
            continue

    raise TypeError('mujson {}() got an unexpected kwarg'.format(func_name))


def mujson_function(name, alias_for=None, ranking=None):
    """Return the "best" available version of some JSON function.

    The true performance ranking of different JSON libraries varies widely
    based on the actual JSON data being encoded or decoded. Therefore, it may
    be desirable to pass your own `ranking` based on your knowledge of the
    common shape or characteristics of the JSON data relevant to your project.

    Args:
        name (str): the global name of your mujson function. Must have the same
          string value as the variable to which you assign the output of
          `mujson_function()`. mujson will infer the underlying json function
          from the name if that function is in the name.
        alias_for (str): the json function for which your mujson function is
          an alias. Must be one of ["load", "loads", "dump", "dumps"].
        ranking (list): if a list, will use ranking when evaluating
          the best module available. If not passed, the default rankings will
          be used.

    NOTE(mattgiles): the returned function behaves differently the first time
    it is invoked, compared to subsequent times. On first invocation, before
    any output is returned, the "best" implementation of the desired json
    function that is available for import is retrieved and made to replace the
    temporary function returned by `mujson_function` in the global namespace.
    The reason for this hackery is because, given the implicit desire for
    speed, extra function calls are are needlessly slow.

    """
    if alias_for is None:
        for func in SUPPORTED_FUNCS:
            if name.find(func) >= 0:
                alias_for = func
                break

    if alias_for is None:
        raise ValueError(
            'mujson_function requires that either `name` contains a substring '
            'in {} or that `alias_for` is specified.'.format(SUPPORTED_FUNCS))

    if alias_for not in SUPPORTED_FUNCS:
        raise ValueError(
            '`alias_for` must be one of: {}'.format(SUPPORTED_FUNCS))

    if ranking is None:
        ranking = DEFAULT_RANKINGS[alias_for]

    if 'json' not in ranking:
        ranking.append('json')

    def temp_json_func(*args, **kwargs):
        func = _best_available_json_func(alias_for, ranking, **kwargs)
        globals()[name] = func
        return func(*args, **kwargs)

    return temp_json_func


dump = mujson_function('dump')

dumps = mujson_function('dumps')

load = mujson_function('load')

loads = mujson_function('loads')


# NOTE(mattgiles): programmers can elect to explicitly import `compliant_*`
# versions of the standard functions to avoid run time errors that depend on
# what concrete uses of e.g. `mujson.dumps` hit `mujson_function:temp_json_func`
# first. Although `mujson_function` guarantees that the first time it is called
# it protects against choosing a JSON library which does not support invoked
# kwargs, this dynamic behavior can lead to NON-DETERMINISTIC behavior in
# larger or more complex libraries where mujson is used multiple places with
# varying signatures.
NON_COMPLIANT = [ujson, cjson, mjson, orjson]

compliant_dump = mujson_function(
    'compliant_dump',
    ranking=[m for m in DEFAULT_RANKINGS['dump'] if m not in NON_COMPLIANT])

compliant_dumps = mujson_function(
    'compliant_dumps',
    ranking=[m for m in DEFAULT_RANKINGS['dumps'] if m not in NON_COMPLIANT])

compliant_load = mujson_function(
    'compliant_load',
    ranking=[m for m in DEFAULT_RANKINGS['load'] if m not in NON_COMPLIANT])

compliant_loads = mujson_function(
    'compliant_loads',
    ranking=[m for m in DEFAULT_RANKINGS['loads'] if m not in NON_COMPLIANT])