Skip to content

Instantly share code, notes, and snippets.

@neuroticnerd
Last active December 22, 2021 16:36
Show Gist options
  • Save neuroticnerd/a071fb3b341d03d4befe to your computer and use it in GitHub Desktop.
Save neuroticnerd/a071fb3b341d03d4befe to your computer and use it in GitHub Desktop.
Unify the format of JSON returned from a REST API
"""
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