python-restx / flask-restx

Fork of Flask-RESTPlus: Fully featured framework for fast, easy and documented API development with Flask
https://flask-restx.readthedocs.io/en/latest/
Other
2.17k stars 336 forks source link

Unsure how to register API endpoints using namespaces #393

Closed mostermann96 closed 2 years ago

mostermann96 commented 3 years ago

I'm trying to get started with a more complex project structure in flask-restx and adding the endpoints with namespaces. However, after following the example, more complex tutorials or other basic tutorials, I still only receive 404s when calling get on /api or the endpoints I tried to define. My app.py looks as follows:

from flask import Flask, Blueprint, url_for, jsonify
from flask_login import LoginManager
import settings
from database import db
from BE.api.endpoints.images import ns as image_ns
from werkzeug.middleware.proxy_fix import ProxyFix
from flask_restx import Api

api = Api(version='0.1', title='Backend API', description='Backend API for sticker generation')
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)

#flask_restx API supports prior flask_restplus api
def configure_app(app):
    app.config['SWAGGER_UI_DOC_EXPANSION'] = settings.RESTPLUS_SWAGGER_EXPANSION
    app.config['RESTX_VALIDATE'] = settings.RESTPLUS_VAL
    app.config['RESTX_MASK_SWAGGER'] = settings.RESTPLUS_MASK_SWAGGER
    app.config['SQLALCHEMY_DATABASE_URI'] = settings.SQLALCHEMY_DATABASE_URI
    app.config['UPLOAD_FOLDER'] = settings.UPLOAD_FOLDER
    app.config['MAX_UPLOAD_LENGTH'] = 24 * 1024 * 1024  # limits filesize to 24 mb

def init_app(app):
    #db.reset()
    configure_app(app)
    api.add_namespace(image_ns)
    api_blueprint = Blueprint('api', __name__, url_prefix='/api')
    api.init_app(api_blueprint)
    api.init_app(api_blueprint)
    app.register_blueprint(api_blueprint)
    db.init_app(app)

[.....]
@app.route("/site-map")
def site_map():
    links = []
    for rule in app.url_map.iter_rules():
        # Filter out rules we can't navigate to in a browser
        # and rules that require parameters
        if "GET" in rule.methods and has_no_empty_params(rule):
            url = url_for(rule.endpoint, **(rule.defaults or {}))
            links.append((url, rule.endpoint))
    return jsonify(links)

if __name__ == '__main__':
    init_app(app)
    app.run(debug=True)

and the endpoint I tried to define:

from flask import request, flash, Blueprint, redirect, send_file
from flask_restx import Resource, Namespace
from BE.database import db as database
from BE.database.models import Image as dbImage
from BE.api.domain_logic import create_cutout
from flask_restx import fields

ns = Namespace('image', description='Available Ops')
image = ns.model('Image', {
    'id': fields.Integer(readOnly=True, description='The identifier of the image'),
    'username': fields.String(required=True, description='Username of uploader'),
})

@ns.route('/test')
class TestClass(Resource):
    def get(self):
        return "Hello World"

@ns.route('/<int:id>')
class Image(Resource):
    @ns.doc('Get single image by ID')
    @ns.marshal_with(image)
    def get(self):
        print("got here")
        try:
            db_image = dbImage.query.filter(dbImage.id == id).one()
        except Exception as e:
            return e, 500
        filename = db_image.filepath
        return send_file(filename, mimetype='image/png')

The /site-map works and shows what I expect, after only receiving 404s from /api or /api/test , that only /site-map is an available endpoint. A pointer to my mistake would be greatly appreciated.

fabioqcorreia commented 3 years ago

I like to define the main routes outside of the api docs definition itself and import it to be used by Swagger.

First you import your "main" method:

from routes.user import do_login

Then you define your namespace like:

users_namespace = api.namespace(name="Users",
                                path="/user",
                                uri="",
                                description="Users related actions")

Then you call your "main" routes like:

@users_namespace.route("/login")
class Login(Resource):

    @users_namespace.doc("")
    @users_namespace.response(200, "OK Call")
    @users_namespace.response(404, "Password error")
    @users_namespace.response(400, "Bad Request error")
    def post(self):
        return do_login()

    @users_namespace.response(200, "")
    def options(self):
        return ""
mostermann96 commented 2 years ago

Thanks for the advice. With help from the Flask Discord, I figured out that the issue was starting the Flask server from PyCharm with an incorrect project structure. This apparently caused init_app() to be skipped, resulting in my issues. Fixed it by following Flask's tutorial for project layouts.