Last active
December 22, 2021 16:36
-
-
Save neuroticnerd/a071fb3b341d03d4befe to your computer and use it in GitHub Desktop.
Unify the format of JSON returned from a REST API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Author: Bryce Eggleton | |
License: MIT | |
""" | |
import json | |
import traceback | |
def _get_container(): | |
""" | |
You can optionally ensure that the data also stays sorted with | |
`sortedcontainers <http://www.grantjenks.com/docs/sortedcontainers/>`_ | |
``pip install sortedcontainers`` | |
By default we at least try to use an OrderedDict to | |
maintain consistency in output and use dict as a last resort | |
""" | |
try: | |
import sortedcontainers | |
return sortedcontainers.SortedDict | |
except: | |
pass | |
try: | |
import collections | |
return collections.OrderedDict | |
except: | |
pass | |
return dict | |
class ResponseJSON(dict): | |
""" | |
This class provides an object to easily store response data | |
for most any WSGI Python framework that you can feed a dict | |
into to be serialized to JSON. This can be used transparently | |
as though it were a dict object, but also allows values to | |
be set or accessed as normal object attributes. | |
By subclassing and overriding the _setup method you can set | |
the default values that each object of that type will have | |
to easily unify the keys and format of all JSON responses | |
from a REST API. | |
Reference Information: | |
http://stackoverflow.com/questions/20855506/python-behavior-of-json-dumps-on-dict | |
http://stackoverflow.com/questions/3387691/python-how-to-perfectly-override-a-dict | |
I may move to subclassing from collections at some point, since | |
it is the most proper and pythonic way of going about this, but | |
for now, this provides sufficient abstraction and functionality. | |
Subclasses of ResponseJSON can also override :_container: to | |
customize the internal container used (must be dict-like!) | |
""" | |
_container = _get_container() | |
def __init__(self, *args, **kwargs): | |
""" | |
The normal dict.__init__ with pseudo-empty dictionary is | |
required, otherwise json.dumps will think the object is | |
empty and just output an empty JSON object. | |
We have to set our internal _data dict via super because | |
it prevents errors with having our own __setattr__ defined. | |
""" | |
dict.__init__(self, {None:None}) | |
super(ResponseJSON, self).__setattr__('_data', self._container()) | |
self._setup(*args, **kwargs) | |
self._data.update(dict(*args, **kwargs)) | |
def _setup(self, *args, **kwargs): | |
""" | |
This is the method to override if you wish to | |
alter the default properties of these objects. | |
""" | |
self.detail = '' | |
self.error = False | |
self.warning = False | |
def __setattr__(self, attr, value): | |
self._data[attr] = value | |
def __getattr__(self, attr): | |
return self._data.get(attr, None) | |
def __setitem__(self, key, value): | |
self._data[key] = value | |
def __getitem__(self, key): | |
return self._data[key] | |
def __delitem__(self, key): | |
del self._data[key] | |
def __iter__(self): | |
return iter(self._data) | |
def __len__(self): | |
return len(self._data) | |
def __unicode__(self): | |
return u'%s' % self._data | |
def __repr__(self): | |
return self.__unicode__() | |
def __nonzero__(self): | |
return self.__bool__() | |
def __bool__(self): | |
"""Python 3 compatibility""" | |
return bool(self._data) | |
@property | |
def json(self): | |
return json.dumps(self._data) | |
@property | |
def data(self): | |
return self._data | |
def update(self, data): | |
self._data.update(data) | |
def keys(self): | |
return self._data.keys() | |
def items(self): | |
return self._data.items() | |
def iteritems(self): | |
return self._data.iteritems() | |
def exc(self, errmsg, exc=None, trace=False): | |
""" | |
This is a convenience method for adding exception/error | |
info to the JSON response, with the option to include the | |
actual exception itself and a full traceback (False by | |
default since this is usually only information that should | |
be exposed in testing/debug situations; self if returned | |
to enable chaining method calls | |
""" | |
self.detail = errmsg | |
self.error = True | |
if exc: | |
self.error = "[%s] %s" % (exc.__class__.__name__, exc) | |
if trace: | |
self.trace = traceback.format_exc() | |
return self |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment