As I'm testing the URL endpoint for a resource, I'm getting a 404 Not Found error. I don't understand why it cannot be found as the resource is added to the api instance and the blueprint is added to the flask app.
tests.py
def test_todo_collection_resource(self):
with app.test_client() as client:
http_response = client.get("/todos/")
json_data = http_response.get_json()
self.assertEqual(http_response.status_code, 200) <<<---FAILS
self.assertTrue(http_response.is_json)
self.assertTrue(all(
(instance['name'] in self.todo_resources.values()
for instance in json_data)
))
todos.py
from flask import Blueprint, jsonify
from flask_restful import Api, Resource, fields, marshal
from models import Todo
todo_api = Blueprint("resources.todos", __name__)
api = Api(todo_api)
todos_fields = {
'name': fields.String
}
class TodoList(Resource):
pass
api.add_resource(
TodoList,
''
'todos'
)
app.py
from flask import Flask, g, jsonify, render_template
from config import HOST, PORT, DEBUG
from peewee import *
import models
from resources.todos import todo_api
app = Flask(__name__)
app.register_blueprint(todo_api, url_prefix="/todos/")
models.DATABASE.init('todo_api.db')
models.initialize(models.User, models.Todo)
#app.route('/')
def my_todos():
return render_template('index.html')
if __name__ == '__main__':
app.run(host=HOST, port=PORT, debug=DEBUG)
You have not defined any methods in your TodoList class that is why there is a 404 error as it can't find any HTTP methods defined on that endpoint. The Flask-RESTful documentation specifies defining HTTP methods in your Resource class e.g.
class TodoList(Resource):
def get(self):
return TODOS
Related
I have a file that registers a blueprint and runs the app api/v1/app.py:
#!/usr/bin/python3
"""returns api status code"""
from api.v1.views import app_views
from flask import Flask
from models import storage
from os import getenv
app = Flask(__name__)
app.register_blueprint(app_views, url_prefix="/api/v1")
#app.teardown_appcontext
def close_session(session):
"""close a session"""
storage.close()
if __name__ == '__main__':
host = getenv('HBNB_API_HOST') if getenv('HBNB_API_HOST') else '0.0.0.0'
port = getenv('HBNB_API_PORT') if getenv('HBNB_API_PORT') else 5000
app.run(port=port, host=host, threaded=True)
a file that creates a blueprint api/v1/views/__init__.py:
#!/usr/bin/python3
"""create a blueprint"""
from flask import Blueprint
app_views = Blueprint("app_views", __name__)
and a file that creates a blueprint route api/v1/views/index.py:
#!/usr/bin/python3
"""returns api status code"""
from flask import jsonify
from api.v1.views import app_views
#app_views.route('/status')
def status():
"""return ok response"""
return jsonify({"status": "OK"})
i registered the blueprint with the url_prefix /api/v1, but when i try to access http://0.0.0.0:5000/api/v1/status, I get 404.
So what am I missing here ?
You defined the prefix correctly. The problem here is because the file api/v1/views/index.py was never interpreted. If you add import api/v1/views/index.py in the file api/v1/app.py it will probably work
I have an API that I am working on building the test_app.py file for, but it already works entirely and has been tested via Postman both locally & hosted externally.
I can't get even my health endpoint to work, which is keeping me (obviously) from making any more tests and moving forward. It's not failing the test, though, it's kicking back 404 when I print the variable I assign to the endpoint.
test_app.py:
import os
import unittest
import json
from flask_sqlalchemy import SQLAlchemy
from app import create_app
from models import setup_db, Actor, Movie
class CastingAgencyTestCase(unittest.TestCase):
def setUp(self):
self.app = create_app()
self.client = self.app.test_client
self.database_name = "casting_test"
self.database_path = "postgres://{}/{}".format("localhost:5432", self.database_name)
setup_db(self.app, self.database_path)
self.new_actor = {
"name": "Robert DOwney Jr.",
"age": 45,
"gender": "male"
}
self.new_movie = {
"title": "Iron Man",
"release_date": "02/03/2004"
}
with self.app.app_context():
self.db = SQLAlchemy()
self.db.init_app(self.app)
self.db.create_all()
def tearDown(self):
pass
def test_health(self):
res = self.client().get('/')
print(res)
data = json.loads(res.data)
self.assertEqual(res.status_code, 200)
self.assertIn('health', data)
self.assertEqual(data['health'], 'App is running.')
if __name__ == "__main__":
unittest.main()
The output of that print(res) is <Response streamed [404 NOT FOUND]>, and then the test fails because there is nothing in res.data, but of course not because the page isn't found.
app.py (up to where the health endpoint is created):
import os
from flask import Flask, request, abort, jsonify, render_template, \
redirect, url_for, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from models import Actor, Movie, setup_db, db_drop_and_create_all, db
from auth import AuthError, requires_auth
def create_app(test_config=None):
app = Flask(__name__)
setup_db(app)
CORS(app, resources={r"/*": {"origins": "*"}})
# db_drop_and_create_all()
return app
app = create_app()
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5000, debug=True)
# -----------
# #app.routes
# -----------
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Headers',
'Content-Type, Authorization')
response.headers.add('Access-Control-Allow-Methods',
'GET, POST, PATCH, DELETE, OPTIONS')
return response
#app.route('/')
def home():
return jsonify({
'success': True,
'health': 'App is running.'
}), 200
models.py (to the point where setup_db is created):
import os
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, String, Integer, ForeignKey, Float, Date, Table
from flask_migrate import Migrate
# ---------
# Config.
# ---------
database_path = os.environ.get('DATABASE_URL')
if not database_path:
database_name = "agency"
database_path = "postgres://{}:{}#{}/{}".format(
'postgres', 'root', 'localhost:5000', database_name)
db = SQLAlchemy()
def setup_db(app, database_path=database_path):
app.config["SQLALCHEMY_DATABASE_URI"] = database_path
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.app = app
db.init_app(app)
After finding some videos regarding this kind of testing, I can't figure out why the page might kick back 404.
Solution found.
It was that all of my endpoints (really, just the entire app.py file) needed to be within the create_app function, and at the end of the file is where app = create_app is called.
So I'm building an API with Flask-RestPlus and using blueprints to divide the code into smaller chunks, but when trying to register multiple API endpoint, providing same URL prefix, only one blueprint registers.
Blueprint Templates:
from flask import Blueprint
from flask_restplus import Api, Resource
tmpl_bp = Blueprint('templates_api', __name__)
api = Api(tmpl_bp)
ns_tmpl = api.namespace('templates', description='Templates operations')
#ns_tmpl.route('/')
class Templates(Resource):
def get(self):
return "All templates"
def post(self):
return "Added/updated template"
Blueprint Render:
from flask import Blueprint
from flask_restplus import Api, Resource
rend_bp = Blueprint('render_api', __name__)
api = Api(rend_bp)
ns_render = api.namespace('render', description='Render actions')
#ns_render.route('/')
class Render(Resource):
def post(self):
return "Rendering everything"
The main app code, where registering happens:
from flask import Flask, render_template
from api.templates import tmpl_bp
from api.render import rend_bp
app = Flask(__name__)
app.register_blueprint(tmpl_bp, url_prefix="/api/v1")
app.register_blueprint(rend_bp, url_prefix="/api/v1")
#app.route('/')
def home():
return "This is the main page"
The resulting Swagger API:
I was expecting both the Templates and the Render blueprints to be served on /api/v1/ as /api/v1/templates and /api/v1/render respectively. But only one registers every time.
How do I get both blueprints served under the same prefix?
You don't need to use Blueprint, it's enough to use flask_restplus Namespaces.
Flask-RESTPlus provides a way to use almost the same pattern as Flask's blueprint. The main idea is to split your app into reusable namespaces.
from flask import Flask, render_template
from flask_restplus import Resource, Api
from api.templates import tmpl_bp
from api.render import rend_bp
app = Flask(__name__)
api = Api(app)
api.add_namespace(tmpl_bp, path="/api/v1/templates")
api.add_namespace(rend_bp, path="/api/v1/render")
#api.route('/')
def home():
return "This is the main page"
You can find out more details here https://flask-restplus.readthedocs.io/en/stable/scaling.html
I have an API with 12 endpoints which was working fine, then the endpoints started failing and now all return 404 status code. Here are some of my files
My run.py
import os
from app import create_app
config = os.getenv('APP_SETTINGS')
app = create_app(config)
if __name__ == "__main__":
app.run(debug=True)
I register my endpoints in app.py like so
from flask import Flask
from .api.v2.views.userview import auth
def create_app(config):
'''Creates all Flask configurations and returns app.
Expects config name'''
app = Flask(__name__, instance_relative_config=True)
app.config['JSON_SORT_KEYS'] = False
app.config.from_object(app_config[config])
app.config.from_pyfile('config.py', silent=True)
app.url_map.strict_slashes = False
app.register_blueprint(auth)
return app
And finally my endpoint user.py
from flask import request, jsonify
from flask import Blueprint
from ..models.usermodel import UserModel
usr_obj = UserModel()
auth = Blueprint('auth', __name__, '/api/v2')
#auth.route('/auth/signup', methods=['POST'])
def user_signup():
fullname = request.json['fullname']
username = request.json['username']
email = request.json['email']
password = request.json['password']
data = usr_obj.inituser(fullname, username, email, password)
return jsonify(data), 201
When I try to run this endpoint or any other in Version 1 (/api/v1) I get a Not Found error. I have also tried THIS SOLUTION with no success.
I made a silly mistake the Blueprint declaration ought to be
auth = Blueprint('auth', __name__, url_prefix='/api/v2')
from flask import g, request, session, render_template, flash, redirect, url_for
from flask import current_app as app
#app.route('/')
def home():
return 'this works'
from flask_restful import Resource, Api
from app.extensions import api
class HelloWorld(Resource):
def get(self):
return {'Hello': 'World'}
api.add_resource(HelloWorld, '/test') # Getting a 404 for this route on http://127.0.0.1:5000/test
Extensions sets up the api variable:
api = Api()
api.init_app(app)
I cannot figure out why I get a 404 when trying to access an api resource?
Ok, the problem seems to be that the below ordering is wrong. I must add resources before I init the api.
api = Api()
api.init_app(app)
api.add_resource(HelloWorld, '/')
Fix:
api = Api()
api.add_resource(HelloWorld, '/')
api.init_app(app)
This is quite strange given that e.g. SQLAlchemy needs to call init_app before it is used...