Standardised JSON response from views - python

When my page POSTs a form to my Django view, the view returns a response with some data but soon I ran into the issue that my views returned data in different formats and different sets of information. I've thought of using JSON as a standard format to return my data.
There are two types of statuses, success and failure. When the POST was successful, it just returns success but when it has failed, it returns a sub group called errors which contains a of fields and that field's error.
Here's a sample failure response:
{"failure": {
"errors":[
{"fieldname": "<fieldname>",
"fielderror": "<fielderror>"
},
{"fieldname": "<fieldname>",
"fielderror": "<fielderror>"
}]
}}
Here's a sample success response:
{"success": {
"data":[
{"fieldname": "<fieldname>",
"fielddata": "<fielddata>"
},
{"fieldname": "<fieldname>",
"fielddata": "<fielddata>"
}]
}}
(The success response has data fields because quite often you like to return some data i.e. key of newly created DB record.)
This is what I've come up with but so many people using Django out there, I'm wondering whether there is a standard way/more robust of doing this or some module to help with this.
Thanks.

been there. I wrote the solution my own (since it's simple. I dont know if theres a module out there for this). This is just a json response wrapper
from django.utils import simplejson
from django.http import HttpResponse
class JsonResponse(HttpResponse):
def __init__(self, data):
content = simplejson.dumps(data)
super(JsonResponse, self).__init__(content=content,
mimetype='application/json')
class Success(JsonResponse):
def __init__(self, something):
x = something # set your data here
content = {'success': {'data': x}}
super(Success, self).__init__(content)
class Failure(JsonResponse):
def __init__(self, something):
x = something # set your data
content = {'failures': {'errors': x}}
super(Failure, self).__init__(content)
something like that. In my case, I make Success and Failure accept a string and a dictionary for arguments (like Success(data)) to make it even easier
If your structure is quite complex (or youre too lazy too write them manually), write your own wrapper for that complicated structure (so you can create the response with even less typing), and make the Success/Failure accepts that as argument.

Do you know how to use Python dicts? If so then there are libraries which convert your Python dict into valid JSON. Django's built-in is for example is simplejson. Here an example:
from django.utils import simplejson
def my_view(request):
....
my_json = simplejson.dumps(my_dict) #dumps a JSON string from a dict
my_dict_again = simplejson.loads(my_json) #loads a Python dict from a JSON string
return HttpResponse(json, mimetype="application/json")

Well, I don't know how much this will help you out, but Python does have a module json.
>>> import json
>>> json.dumps({'a':'b', 'c':'d'})
'{"a": "b", "c": "d"}'

I think the front end is going to make a stronger case for how you should format your data then some "standard", Different client side frameworks expect different things, and adapt to differences from that expectations better or worse than that.
One way I try to make my own life easier is to have the 'shape' of the response always roughly the same, for example, the response will always have a 'status' property, which always has an 'errors' property, and if there were no errors, then the value of result.status.errors is null. If there were errors, it'd be a list of strings explaining what went wrong. even if there were errors, there'd be a 'value' property, which would be either null if the request couldn't be serviced, or the requested resource if it could.

Related

Mocking requests.post and requests.json decoder python

I'm creating a test suite for my module that uses the requests library quite a bit. However, I'm trying to mock several different return values for a specific request, and I'm having trouble doing so. Here is my code snippet that doesn't work:
class MyTests(unittest.TestCase):
#patch('mypackage.mymodule.requests.post')
def test_change_nested_dict_function(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json = nested_dictionary
modified_dict = mymodule.change_nested_dict()
self.assertEqual(modified_dict['key1']['key2'][0]['key3'], 'replaced_value')
The function I am attempting to mock:
import requests
def change_nested_dict():
uri = 'http://this_is_the_endpoint/I/am/hitting'
payload = {'param1': 'foo', 'param2': 'bar'}
r = requests.post(uri, params=payload)
# This function checks to make sure the response is giving the
# correct status code, hence why I need to mock the status code above
raise_error_if_bad_status_code(r)
dict_to_be_changed = r.json()
def _internal_fxn_to_change_nested_value(dict):
''' This goes through the dict and finds the correct key to change the value.
This is the actual function I am trying to test above'''
return changed_dict
modified_dict = _internal_fxn_to_change_nested_value(dict_to_be_changed)
return modified_dict
I know a simple way of doing this would be to not have a nested function, but I am only showing you part of the entire function's code. Trust me, the nested function is necessary and I really do not want to change that part of it.
My issue is, I don't understand how to mock requests.post and then set the return value for both the status code and the internal json decoder. I also can't seem to find a way around this issue since I can't seem to patch the internal function either, which also would solve this problem. Does anyone have any suggestions/ideas? Thanks a bunch.
I bumped here and although I agree that possibly using special purpose libraries is a better solution, I ended up doing the following
from mock import patch, Mock
#patch('requests.post')
def test_something_awesome(mocked_post):
mocked_post.return_value = Mock(status_code=201, json=lambda : {"data": {"id": "test"}})
This worked for me for both getting the status_code and the json() at the receiver end while doing the unit-test.
Wrote it here thinking that someone may find it helpful.
When you mock a class each child method is set up as a new MagicMock that in turn needs to be configured. So in this case you need to set the return_value for mock_post to bring the child attribute into being, and one to actually return something, i.e:
mock_post.return_value.status_code.return_value = 200
mock_post.return_value.json.return_value = nested_dictionary
You can see this by looking at the type of everything:
print(type(mock_post))
print(type(mock_post.json))
In both cases the type is <class 'unittest.mock.MagicMock'>
Probably it is better for you to look at some specialized libraries for requests testing:
responses
requests-mock
requests-testing
They provide clean way to mock responses in unittests.
An alternate approach is to just create an actual Response object and then do a configure_mock() on the original mock.
from requests import Response
class MyTests(unittest.TestCase):
#patch('mypackage.mymodule.requests.post')
def test_change_nested_dict_function(self, mock_post):
resp = Response()
resp.status_code = 200
resp.json = nested_dictionary
mock_post.configure_mock(return_value=resp)
...

Class that returns json, python

I have a python class that should return a json, it looks something like this:
class ScanThis():
def__init__(self, test):
data={}
if test>5:
data["amount"] = test
self.json_data = json.dumps(data)
else:
data["fail"] = test
self.json_data = json.dumps(data)
def __str__(self):
return self.json_data
and I'm trying to call it like so:
output= json.loads(ScanThis(8))
print(output["command"])
But I get this error:
TypeError: the JSON object must be str, bytes or bytearray, not 'ScanThis'
I believe my earlier clas returns an object of type ScanThis() rather than a JSon like I wanted. I just wanted to now how I'd fix this
Thank you
PS: I apologise if this code is rough or invalid, it's not the actual code, just something similar I made up
Update: Again, this isn't the real code, it's just a small basic fragment of the actual code. There's a good reason I'm using a class, and a json is used cause data transfer over the internet is involved
Use str(..)
You can't call json.loads on a ScanThis object directly. So that won't work. Like the error says, json.loads expects a str, bytes or bytearray object.
You can however use str(..) to invoke the __str__(self) method, and thus obtain the JSON data:
output = json.loads(str(ScanThis(8)))
# ^ get the __str__ result
Use another method
That being said, it is usually a better idea to define a method, for instance to_json to obtain the JSON data, since now you have made str(..) return a JSON object. So perhaps a more elegant way to do this is the following:
class ScanThis():
def__init__(self, test):
data={}
if test>5:
data["amount"] = test
self.json_data = json.dumps(data)
else:
data["fail"] = test
self.json_data = json.dumps(data)
def to_json(self):
return self.json_data
and call it with:
output = json.loads(ScanThis(8).to_json())
Now you can still use __str__ for another purpose. Furthermore by using to_json you make it explicit that the result will be a JSON string. Using str for JSON conversion is of course not forbidden, but str(..) as a name, does not provide much guarantees about the format of the result whereas to_json (or another similar name) strongly hints that you will obtain JSON data.
I don't think you are wanting to use a class there at all.
Instead, try using a function that returns a string. For example:
def scan_this(test):
data={}
if test>5:
data["amount"] = test
json_data = json.dumps(data)
else:
data["fail"] = test
json_data = json.dumps(data)
return json_data
output = json.loads(scan_this(8))
However!! Now you are just doing extra work for nothing? Why do you need to serialize a python dictionary as a json formatted string, and then load it back into a python dictionary straight away? While you are working with data in python, it's best to keep it as native data types, and only using the json module to either load from a string/file you already have, or serialize to a string/file for storage or transfer (eg sending over the internet).

Pyramid invoking a sub request

I'm trying to implement a batch request method in pyramid. I see in the docs that it's done with
subrequest = Request.blank('/relative/url')
request.invoke_subrequest(subrequest)
I'm just wondering how do you pass along the headers and cookies? Is it already done for you or is it
request.invoke_subrequest(subrequest, cookies=request.cookies, headers=request.headers)
What about parameters for different methods? The docs only have a POST keyword arg.
I feel like the docs are a little vague, or I can't find the correct docs on how to do this. Thanks
I'm just wondering how do you pass along the headers and cookies?
From http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/subrequest.html#subrequest-chapter :
The pyramid.request.Request.invoke_subrequest() API accepts two
arguments: a positional argument request that must be provided, and
use_tweens keyword argument that is optional; it defaults to False.
This tells us that the constructor only wants a Request object, and optionally a value for use_tweens, so no, this
request.invoke_subrequest(subrequest, cookies=request.cookies, headers=request.headers)
will not work.
Then, from http://docs.pylonsproject.org/projects/pyramid/en/1.5-branch/narr/subrequest.html
It's a poor idea to use the original request object as an argument to
invoke_subrequest(). You should construct a new request instead as
demonstrated in the above example, using
pyramid.request.Request.blank(). Once you've constructed a request
object, you'll need to massage it to match the view callable you'd
like to be executed during the subrequest. This can be done by
adjusting the subrequest's URL, its headers, its request method, and
other attributes. The documentation for pyramid.request.Request
exposes the methods you should call and attributes you should set on
the request you create to massage it into something that will actually
match the view you'd like to call via a subrequest.
So basically, you need to configure your request before you pass it to invoke_subrequest().
Luckily there is an entire page that documents the Request class. There we can find a whole lot options to configure it, etc.
What about parameters for different methods? The docs only have a POST keyword arg.
Also on the Request class documentation page, there is this
method
Gets and sets the REQUEST_METHOD key in the environment.
And on http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/viewconfig.html I found
request_method This value can be a string (typically "GET", "POST",
"PUT", "DELETE", or "HEAD") representing an HTTP REQUEST_METHOD
I must agree with you that the documentation is a little vague here and there, but I assume you can use it like this
request.method = 'POST'
# Or
request.method = 'GET'
# Etc.
Summary
You'll want to do it like this
subrequest = Request.blank('/relative/url')
# Configure the subrequest object
# set headers and other options you need.
request.invoke_subrequest(subrequest)
Note
I am aware this is not a 100% complete answer with some code that you can copy paste and it'll just work (especially regarding configuring the request object), but I think this answer contains some information that, at the very least, will get you on the right track and I hope it will be of some help to you.
The following code worked for me. It copies all (headers, cookies, query string, post parameters, etc.):
def subrequest(request, path):
subreq = request.copy()
subreq.path_info = path
response = request.invoke_subrequest(subreq)
return response
Somewhat late, but based on the above two answers here is how I did this. I didn't quite like the above answer to just copy everything. Looking at the documentation of the blank() method there is a kw argument and it says
All necessary keys will be added to the environ, but the values you pass in will take precedence. If you pass in base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will be filled in from that value.
Assuming that the view's request contains the right header information and cookies that you need for your subrequest, you can use the following code:
#view_config( ... )
def something(request):
...
kwargs = { "cookies": request.cookies,
"host" : request.host,
}
req = Request.blank("/relative/url", **kwargs)
resp = request.invoke_subrequest(req)
Other header information (e.g. accept, accept_encoding, etc.) are properties of pyramid.request objects, and can be added to the kwargs dictionary like shown in the code snippet above.
The object returned by invoke_subrequest() is a Response object documented here.

Best-practice: automated web API testing

I've written a program in Python, which works with two distinct API to get the data from two different services (CKAN and MediaWiki).
In particular, there is a class Resource, which requests the data from the above mentioned services and process it.
At some point I've come to conclusion, that there is a need for tests for my app.
And the problem is that all examples I've found on web and in books do not deal with such cases.
For example, inside Resource class I've got a method:
def load_from_ckan(self):
"""
Get the resource
specified by self.id
from config.ckan_api_url
"""
data = json.dumps({'id': self.id})
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
url = config.ckan_api_url + '/action/resource_show'
r = requests.post(url, timeout=config.ckan_request_timeout, data=data, headers=headers)
assert r.ok, r
resource = json.loads(r.content)
resource = resource["result"]
for key in resource:
setattr(self, key, resource[key])
The load_from_ckan method get the data about resource from the CKAN API and assign it to the object. It is simple, but...
My question is: how to test the methods like this? OR What should I test here?
I thought about the possibility to pickle (save) results to HDD. Then I could load it in the test and compare with the object initialized with load_from_ckan(). But CKAN is community-driven platform and such behavior of such tests will be unpredictable.
If there exist any books on philosophy of automated tests (like what to test, what not to test, how to make tests meaningful etc.), please, give me a link to it.
With any testing, the key question really is - what could go wrong?
In your case, it looks like the three risks are:
The web API in question could stop working. But you check for this already, with assert r.ok.
You, or someone else, could make a mistaken change to the code in future (e.g. mistyping a variable) which breaks it.
The API could change, so that it no longer returns the fields or the format you need.
It feels like you could write a fairly simple test for the latter two, depending on what data from this API you actually rely on: for example, if you're expecting the JSON to have a field called "temperature" which is a floating-point Celsius number, you could write a test which calls your function, then checks that self.temperature is an instance of 'float' and is within a sensible range of values (-30 to 50?). That should leave you confident that both the API and your function are working as designed.
Typically if you want to test against some external service like this you will need to use a mock/dummy object to fake the api of the external service. This must be configurable at run-time either via the method's arguments or the class's constructor or another type of indirection. Another more complex option would be to monkey patch globals during testing, like "import requests; request.post = fake_post", but that can create more problems.
So for example your method could take an argument like so:
def load_from_ckan(self, post=requests.post):
# ...
r = post(url, timeout=config.ckan_request_timeout, data=data,
headers=headers)
# ...
Then during testing your would write your own post function that returned json results you'd see coming back from ckan. For example:
def mock_post(url, timeout=30, data='', headers=None):
# ... probably check input arguments
class DummyResponse:
pass
r = DummyResponse()
r.ok = True
r.content = json.dumps({'result': {'attr1': 1, 'attr2': 2}})
return r
Constructing the result in your test gives you a lot more flexibility than pickling results and returning them because you can fabricate error conditions or focus in on specific formats your code might not expect but you know could exist.
Overall you can see how complicated this could become so I would only start adding this sort of testing if you are experiencing repeated errors or other difficulties. This will just more code you have to maintain.
At this point, you can test that the response from CKAN is properly parsed. So you can pull the JSON from CKAN and ensure that it's returning data with the attributes you're interested in.

json.dumps vs flask.jsonify

I am not sure I understand the purpose of the flask.jsonify method. I try to make a JSON string from this:
data = {"id": str(album.id), "title": album.title}
but what I get with json.dumps differs from what I get with flask.jsonify.
json.dumps(data): [{"id": "4ea856fd6506ae0db42702dd", "title": "Business"}]
flask.jsonify(data): {"id":…, "title":…}
Obviously I need to get a result that looks more like what json.dumps returns. What am I doing wrong?
The jsonify() function in flask returns a flask.Response() object that already has the appropriate content-type header 'application/json' for use with json responses. Whereas, the json.dumps() method will just return an encoded string, which would require manually adding the MIME type header.
See more about the jsonify() function here for full reference.
Edit:
Also, I've noticed that jsonify() handles kwargs or dictionaries, while json.dumps() additionally supports lists and others.
You can do:
flask.jsonify(**data)
or
flask.jsonify(id=str(album.id), title=album.title)
This is flask.jsonify()
def jsonify(*args, **kwargs):
if __debug__:
_assert_have_json()
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
The json module used is either simplejson or json in that order. current_app is a reference to the Flask() object i.e. your application. response_class() is a reference to the Response() class.
The choice of one or another depends on what you intend to do.
From what I do understand:
jsonify would be useful when you are building an API someone would query and expect json in return. E.g: The REST github API could use this method to answer your request.
dumps, is more about formating data/python object into json and work on it inside your application. For instance, I need to pass an object to my representation layer where some javascript will display graph. You'll feed javascript with the Json generated by dumps.
consider
data={'fld':'hello'}
now
jsonify(data)
will yield {'fld':'hello'} and
json.dumps(data)
gives
"<html><body><p>{'fld':'hello'}</p></body></html>"

Categories

Resources