Reputation: 1913
I recently refactored my flask app to separate the views from the init file.
my structure is like this:
api
__init__.py
views.py
My app was initially like this:
import os
from flask import Flask
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__)
setup_db(app)
CORS(app)
# CORS Headers
@app.after_request
def after_request(response):
"""Docstring for my function"""
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,true')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
# my views were all contained below
@app.route('/hello')
def hello():
return 'Hello, World!'
return app
And then I refactored to be defined elsewhere and referenced in create_app
:
from .views import hello
def create_app():
...
app.route('/hello')(hello)
app.route('/hello', methods=['POST'])(create_hello)
def hello():
return 'Hello, World!'
def create_hello():
return 'something else'
I have a pytest file that tests the creation of a hello:
@pytest.fixture()
def set_up():
app = create_app()
client = app.test_client
database_path = "postgresql://{}:{}@{}/{}".format(username, password,'localhost:5432', test_db_name)
setup_db(app, database_path)
with app.app_context():
db = SQLAlchemy()
db.init_app(app)
db.create_all()
return client
def test_create_hello(set_up):
client = set_up()
res = client.post('/hello', json={"name":"jim-bob")
data = json.loads(res.data)
assert res.status_code == 200
assert data['success'] == True
When I run pytest
, I get a similar error for each of my methods:
def test_create_hello(set_up):
client = set_up()
res = client.post('/hello', json={'name':'jim-bob')
> data = json.loads(res.data)
test_api.py:128:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/lib/python3.6/json/__init__.py:354: in loads
return _default_decoder.decode(s)
/usr/lib/python3.6/json/decoder.py:339: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <json.decoder.JSONDecoder object at 0x7f069b43dc18>
s = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>405 Method Not Allowed</title>\n<h1>Method Not Allowed</h1>\n<p>The method is not allowed for the requested URL.</p>\n'
idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
My api still works in the browser or using curl, but the tests no longer work for some reason.
I've only had this problem since I refactored my views outside of my create_app function. Can anyone see what could be causing this?
Upvotes: 2
Views: 969
Reputation: 21285
@app.route('/hello')
this only binds the GET
method to the handler.
Yet here:
res = client.post('/hello', json={'name': 'jim-bob'})
you are doing a POST
To make the same handler handle POST
requests as well, do:
@app.route('/hello', methods=['GET', 'POST'])
or add a different handler for POST
requests. See documentation.
Upvotes: 1
Reputation: 12420
you need to rewrite your code like
@app.route('/hello', methods=['GET', 'POST'])
def hello():
if flask.request.method == 'POST':
...
else: # GET
...
Upvotes: 0
Reputation: 1522
As @hoefing pointed out the response returned from the "/hello" route method is just a string. That response would be wrapped in the flask base Response class which by default has content-type as text/HTML. Hence your response caught and loaded as JSON by the test function faces an error in decoding.
The test you have added should handle the response according to the content-type of response returned which in this case is not a JSON.
#solutions to fix the issue
(solution 1) Update your response for the "/hello" route. For this, you can either initialize flask Response class yourself with the content-type header or use flask jsonify which does that automatically.
from flask import make_response, jsonify
def hello():
#headers = {'Content-Type': 'application/json'}
#return make_response('Hello, World!', 200, headers)
return jsonify('Hello, World!')
(solution 2) Update the test logic to handle the text/HTML response.
def test_create_hello(set_up):
client = set_up()
res = client.post('/hello', json={'name':'jim-bob')
data = res.data
# handle this html text data
Upvotes: 0