webpy: How to serve JSON - python

Is it possible to use webpy to serve JSON?
I built my website and I need to serve some information in JSON to interact with the Javascript on some pages.
I try to look for answers in the documentation, but I'm not able to find anything.
Thanks,
Giovanni

I wouldn't think you'd have to do any thing overly "special" for web.py to serve JSON.
import web
import json
class index:
def GET(self):
pyDict = {'one':1,'two':2}
web.header('Content-Type', 'application/json')
return json.dumps(pyDict)

It is certainly possible to serve JSON from webpy, But if you and choosing a framework, I would look at starlight and my fork twilight (for documentation).
It has a JSON wrapper for fixing the http headers for your json response.
it uses either the json or simplejson libraries for json handling the conversions to and from other objects.
I am using it right now and it is great.
https://bitbucket.org/marchon/twilight
in it you will find an example called ShowMeTheJson.py
that uses simple json
from starlight import *
from werkzeug.routing import Map
from werkzeug.routing import RuleFactory
import simplejson
class ShowMeTheResponses(App):
####################################################################
#
# Sample URLS to Test Responses
#
# http://localhost:8080/ root
#
# http://localhost:8080/json return JSON Mime Type Doc
#
###################################################################
#default
def hello(self):
return 'Hello, world!'
#dispatch('/')
def index(self):
return 'Hello Root!'
#dispatch('/html')
def indexhtml(self):
return HTML('Hello HTML')
#dispatch('/json')
def indexjson(self):
directions = {'N' : 'North', 'S' : 'South', 'E':'East', 'W' : 'West'}
return JSON(simplejson.dumps(directions))
if __name__ == '__main__':
from werkzeug import run_simple
run_simple('localhost', 8080, ShowMeTheResponses())

Related

flask-apispec not populating kwargs with values from GET query (implementation of example code from documentation)

I am using flask-apispec with webargs to define the types of a simple API. I have produced a minimal example, below, that reproduces the issue, which is that the kwargs object is empty.
Server code:
import flask
from flask_apispec import use_kwargs
from webargs import fields
app = flask.Flask(__name__)
#app.route('/pets', methods=["GET"])
#use_kwargs({'species': fields.Str()})
def list_pets(**kwargs):
assert False, kwargs # NO DATA HERE
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Simple client script:
import requests
requests.get("http://localhost:5000/pets", params={"species": "cat"})
Why is the kwargs dict empty? I don't see how the above is at all different from the example in the flask-apispec documentation.
I had the same problem and suffered a little with the documentation.
Referencing webargs docs (that is what I understood flask-apispec uses under the hood) I found some examples and was able to implement it.
The location parameter is what was missing.
In your example it would be:
#app.route('/pets', methods=["GET"])
#use_kwargs({'species': fields.Str()}, location="query")
def list_pets(**kwargs):
print(kwargs) # Now "species" should be here if sent through the query
I'm using flask-apispec v0.11.1, which installed webargs v8.1.0 on my system.

Parse result into JSON using Python and Flask

Now, I managed to successfully pull basic information from my smart device onto the terminal using pyHS100 on python (v3.6) using the following code
from pyHS100 import SmartPlug
from pprint import pformat as pf
plug = SmartPlug("10.xxx.xxx.xxx")
print("Hardware: %s" % pf(plug.hw_info))
which results in the following:
but I can't parse the data into json format and display it on the local server for my RESTful API purpose if I done it this way:
from flask import Flask, jsonify
from flask_restful import Resource, Api
from pyHS100 import SmartPlug
app = Flask(__name__)
#app.route('/api')
def get():
plug = SmartPlug("10.xxx.xxx.xxx")
sys = plug.hw_info
return jsonify({'data':sys})
if __name__ == '__main__':
app.run(host='0.0.0.0')
app.run(debug=True)
All I need is for the information to be presented into something like this:
What did I do wrong and how do I this fix? Thanks
I believe the best way to solving this is by using json.dumps

Flask returning namedtuples as dicts, but not all the time

I have a very small Flask app that looks very much like this:
Point = namedtuple('Point', ['lat', 'lng', 'alt'])
p1 = Point(38.897741, -77.036450, 20)
def create_app():
app = flask.Flask(__name__)
#app.route('/position')
def position():
return flask.jsonify({
'vehicle': p1,
})
return app
It exists only to feed position data to a web ui. I was expecting that the Point namedtuple would be rendered as a JSON array, but to my surprise I was getting:
{
"vehicle": {
"alt": 20,
"lat": 38.897741,
"lng": -77.03645
}
}
...which, you know, that's fine. I can work with that. But then I was writing some unit tests, which look something like this:
from unittest import TestCase
import json
import tupletest
class TupleTestTest(TestCase):
def setUp(self):
_app = tupletest.create_app()
_app.config['TESTING'] = True
self.app = _app.test_client()
def test_position(self):
rv = self.app.get('/position')
assert rv.status_code == 200
assert rv.mimetype == 'application/json'
data = json.loads(rv.get_data())
assert data['vehicle']['lat'] = 38.897741
...and they failed, because suddenly I wasn't get dictionaries:
> assert data['vehicle']['lat'] == 38.897741
E TypeError: list indices must be integers, not str
And indeed, if in the test I wrote the return value out to a file I had:
{
"vehicle": [
38.897741,
-77.03645,
20
]
}
What.
What is going on here? I can't even reproduce this for the purposes of this question; the unit test above renders dictionaries. As does my actual webapp, when it is running, but not when it's being tested. But on another system I appear to be getting arrays from the actual app.
Looking at the sourcecode, this is in the flask's jsonify.py:
# Use the same json implementation as itsdangerous on which we
# depend anyways.
from itsdangerous import json as _json
and in the itstangerous.py there is:
try:
import simplejson as json
except ImportError:
import json
The simplejson library has an option namedtuple_as_object which is enabled by default.
So when the 3rd party simplejson is installed, the app uses it and serializes a namedtuple to a JSON object (a dict in Python).
On systems where that library is not installed, the app falls back to standard json and serializes a namedtuple to an array (list).
But if the simplejson is installed and imported by flask, the test program imports directly the standard json overwriting it and thus changing the behaviour between running and testing.

Unit testing a python app that uses the requests library

I am writing an application that performs REST operations using Kenneth Reitz's requests library and I'm struggling to find a nice way to unit test these applications, because requests provides its methods via module-level methods.
What I want is the ability to synthesize the conversation between the two sides; provide a series of request assertions and responses.
It is in fact a little strange that the library has a blank page about end-user unit testing, while targeting user-friendliness and ease of use. There's however an easy-to-use library by Dropbox, unsurprisingly called responses. Here is its intro post. It says they've failed to employ httpretty, while stating no reason of the fail, and written a library with similar API.
import unittest
import requests
import responses
class TestCase(unittest.TestCase):
#responses.activate
def testExample(self):
responses.add(**{
'method' : responses.GET,
'url' : 'http://example.com/api/123',
'body' : '{"error": "reason"}',
'status' : 404,
'content_type' : 'application/json',
'adding_headers' : {'X-Foo': 'Bar'}
})
response = requests.get('http://example.com/api/123')
self.assertEqual({'error': 'reason'}, response.json())
self.assertEqual(404, response.status_code)
If you use specifically requests try httmock. It's wonderfully simple and elegant:
from httmock import urlmatch, HTTMock
import requests
# define matcher:
#urlmatch(netloc=r'(.*\.)?google\.com$')
def google_mock(url, request):
return 'Feeling lucky, punk?'
# open context to patch
with HTTMock(google_mock):
# call requests
r = requests.get('http://google.com/')
print r.content # 'Feeling lucky, punk?'
If you want something more generic (e.g. to mock any library making http calls) go for httpretty.
Almost as elegant:
import requests
import httpretty
#httpretty.activate
def test_one():
# define your patch:
httpretty.register_uri(httpretty.GET, "http://yipit.com/",
body="Find the best daily deals")
# use!
response = requests.get('http://yipit.com')
assert response.text == "Find the best daily deals"
HTTPretty is far more feature-rich - it offers also mocking status code, streaming responses, rotating responses, dynamic responses (with a callback).
You could use a mocking library such as Mocker to intercept the calls to the requests library and return specified results.
As a very simple example, consider this class which uses the requests library:
class MyReq(object):
def doSomething(self):
r = requests.get('https://api.github.com', auth=('user', 'pass'))
return r.headers['content-type']
Here's a unit test that intercepts the call to requests.get and returns a specified result for testing:
import unittest
import requests
import myreq
from mocker import Mocker, MockerTestCase
class MyReqTests(MockerTestCase):
def testSomething(self):
# Create a mock result for the requests.get call
result = self.mocker.mock()
result.headers
self.mocker.result({'content-type': 'mytest/pass'})
# Use mocker to intercept the call to requests.get
myget = self.mocker.replace("requests.get")
myget('https://api.github.com', auth=('user', 'pass'))
self.mocker.result(result)
self.mocker.replay()
# Now execute my code
r = myreq.MyReq()
v = r.doSomething()
# and verify the results
self.assertEqual(v, 'mytest/pass')
self.mocker.verify()
if __name__ == '__main__':
unittest.main()
When I run this unit test I get the following result:
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Missing from these answers is requests-mock.
From their page:
>>> import requests
>>> import requests_mock
As a context manager:
>>> with requests_mock.mock() as m:
... m.get('http://test.com', text='data')
... requests.get('http://test.com').text
...
'data'
Or as a decorator:
>>> #requests_mock.mock()
... def test_func(m):
... m.get('http://test.com', text='data')
... return requests.get('http://test.com').text
...
>>> test_func()
'data'
using mocker like in srgerg's answer:
def replacer(method, endpoint, json_string):
from mocker import Mocker, ANY, CONTAINS
mocker = Mocker()
result = mocker.mock()
result.json()
mocker.count(1, None)
mocker.result(json_string)
replacement = mocker.replace("requests." + method)
replacement(CONTAINS(endpoint), params=ANY)
self.mocker.result(result)
self.mocker.replay()
For the requests library, this would intercept the request by method and endpoint you're hitting and replace the .json() on the response with the json_string passed in.
If you break out your response handler/parser into a separate function, you can work with requests.Response objects directly, without needing to mock the client-server interaction.
Code under test
from xml.dom import minidom
from requests.models import Response
def function_under_test(s3_response: Response):
doc = minidom.parseString(s3_response.text)
return (
s3_response.status_code,
doc.getElementsByTagName('Code').item(0).firstChild.data,
)
Test code
import unittest
from io import BytesIO
class Test(unittest.TestCase):
def test_it(self):
s3_response = Response()
s3_response.status_code = 404
s3_response.raw = BytesIO(b"""<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>
""")
parsed_response = function_under_test(s3_response)
self.assertEqual(404, parsed_response[0])
self.assertEqual("NoSuchKey", parsed_response[1])
There's a library for this, if you want to write your test server with Flask: requests-flask-adaptor
You just have to be careful with the order of imports when monkeypatching.

Accept parameters only from POST request in python

Is there a way to accept parameters only from POST request?
If I use cgi.FieldStorage() from cgi module, it accepts parameters from both GET and POST request.
By default, most things in the cgi module merge os.environ['QUERY_STRING'] and sys.stdin (in the format suggested by os.environ['CONTENT_TYPE']). So the simple solution would be to modify os.environ, or rather, provide an alternative, with no query string.
# make a COPY of the environment
environ = dict(os.environ)
# remove the query string from it
del environ['QUERY_STRING']
# parse the environment
form = cgi.FieldStorage(environ=environ)
# form contains no arguments from the query string!
Ignacio Vazquez-Abrams suggests avoiding the cgi module altogether; modern python web apps should usually adhere to the WSGI interface. That might instead look like:
import webob
def application(environ, start_response):
req = webob.Request(environ)
if req.method == 'POST':
# do something with req.POST
# still a CGI application:
if __name__ == '__main__':
import wsgiref.handlers
wsgiref.handlers.CGIHandler().run(application)
From the documentation, I think you can do the following:
form = cgi.FieldStorage()
if isinstance(form["key"], cgi.FieldStorage):
pass #handle field
This code is untested.

Categories