noirbizarre / flask-restplus

Fully featured framework for fast, easy and documented API development with Flask
http://flask-restplus.readthedocs.org
Other
2.73k stars 507 forks source link

How to aggregate Swagger docs from multiple Blueprints? #468

Open Midnighter opened 6 years ago

Midnighter commented 6 years ago

Maybe my design choices are flawed so feel free to correct me. My goal was to be able to assign URLs to several resources while keeping them in the same namespace. Thus I used blueprints which work great for me. My only problem is that now the docs are served at each individual blueprint URL rather than having an aggregated one. Pseudocode for my setup:

Resource status.py:

from flask import Blueprint
from flask_restplus import Resource, Api

status = Blueprint("status", __name__)
api = Api(status, description="Query status.")

@api.route("/<string:uuid>")
@api.doc(...)
class Status(Resource):
    ...

In the main app.py:

from flask import Flask

from .status import status

app = Flask(__name__)
app.register_blueprint(status, url_prefix="/status")

I have multiple such blueprints and would like to see all of their docs under one endpoint.

Fearxpc commented 6 years ago

Hi,

you can try

status.py

from flask_restplus import Namespace
api = Namespace('status', description='Status related stuff')

@api.route("/<string:uuid>")
@api.doc(...)
class Status(Resource):
    ...

api.py

from flask import Blueprint
from flask_restplus import Api
blueprint = Blueprint('api', __name__, url_prefix='/myapi')
api = Api(blueprint,
    title='API',
    description='API'
)

from status import api as ns1
api.add_namespace(ns1)

app.py

from flask import Flask

app = Flask(__name__)
from api import blueprint as blueprint1
app.register_blueprint(blueprint1)

So if you add more namepsaces to the api, they will be added to the same doc. URL/myapi shows the doc URL/myapi/status ==> status routes URL/myapi/namespace2 ==> namespace2 routes

ethe commented 5 years ago

As @Fearxpc 's advice, we get a single blueprint app with multiple namespace. But if there are already multiple blueprints exist, how can we solve this problem?

Fearxpc commented 5 years ago

@ethe You should be able to reigster multiple blueprints with app.register_blueprint() - that's why I used the var name blueprint1.

ethe commented 5 years ago

@Fearxpc

blueprint1 's endpoint is '\myapi' so I can '/myapi' to show the blueprint1 swagger, that's right. If I register another blueprint (maybe call the blueprint2 and it's endpoint is '/myapi2'), I can find the doc in '/myapi2'. But there are not a aggregation of blueprint1 and blueprint2, I can not find a page list all of the api from all blueprints. And the issue title is 'How to aggregate Swagger docs from multiple Blueprints?'.

Hope for your reply, thank you.

Fearxpc commented 5 years ago

Okay got it - but I think this is not possible or I don't know how to at the moment.

themmes commented 5 years ago

My way around this was to use Namespaces instead of Blueprint.

https://flask-restplus.readthedocs.io/en/stable/scaling.html#multiple-apis-with-reusable-namespaces

MyJoiT commented 5 years ago

In summary, we can only use it like this:

1. Code

from flask import Flask, Blueprint
from flask_restplus import Api, Namespace, Resource

app = Flask(__name__)

cat_ns = Namespace('cat_ns')

@cat_ns.route('/cat')
class Cat(Resource):
    def get(self):
        return 'hello cat'

cat_bp = Blueprint('cat_bp', __name__, url_prefix='/cat_api')
cat_api = Api(cat_bp, version='0.1')
cat_api.add_namespace(cat_ns)

dog_ns = Namespace('dog_ns')

@dog_ns.route('/dog')
class Dog(Resource):
    def get(self):
        return 'dog'

dog_bp = Blueprint('dog_bp', __name__, url_prefix='/dog_api')
dog_api = Api(dog_bp, version='2')
dog_api.add_namespace(dog_ns)

app.register_blueprint(cat_bp)
app.register_blueprint(dog_bp)

app.run(debug=True)

2. Swagger for cat_api

Route: http://host:port/cat_api

Swagger in browser:

catapi

3. Swagger for dog_api

Route: http://host:port/dog_api

Swagger in browser:

dogapi

❤️

Hope for the aggregation of all my blueprints.

weldpua2008 commented 5 years ago

Hope for the aggregation of all my blueprints.

+1

yesthesoup commented 4 years ago

I ended up being able to accomplish the original goal of two blueprints with only one namespace the following way:

In my_project.routes.ui

api = Namespace('ui', description='User Interface API')

In my_project.routes.service

service = Blueprint('service', __name__)
api = Namespace('service', description='Service API')

In my_project.__init__.py, where I am using the application factory pattern

def create_app():
    ...
    from my_project.routes.ui import api as ui_ns
    from my_project.routes.service import service, api as service_ns

    blueprint = Blueprint('api', __name__)
    api = Api(blueprint)
    app.register_blueprint(blueprint, url_prefix='/api')
    app.register_blueprint(service)

    api.add_namespace(service_ns)
    api.add_namespace(ui_ns)
    ...

This results in the following app.url_map output:

...
 <Rule '/api/service/endpoint1' (POST, OPTIONS) -> api.service_endpoint1>,
 <Rule '/api/ui/endpoint1' (GET, HEAD, OPTIONS) -> api.ui_endpoint1>,
 <Rule '/api/swagger.json' (GET, HEAD, OPTIONS) -> api.specs>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.doc>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.root>,
... # etc

Both /ui and /service are under the same url prefix/base path /api.

james-powis commented 4 years ago

I had a significant difficulty figuring out how to how to accomplish this, using the information provided by other users and the lack of an example in the docs which was explicit.

The piece of information I was missing was here: https://github.com/noirbizarre/flask-restplus/blob/master/examples/zoo/cat.py

In short every blueprint needs the following:

from flask_restplus import Api

api = Namespace('cats', description='Cats related operations')
@api.route('/')
class Cats(Resource):
    def get(self):
        return {}

then that is imported and added as a namespace:

from zoo.cat import api as cat
from zoo.dog import api as dog
app = Flask(__name__)
blueprint = Blueprint('api', __name__, url_prefix='/api/1')
api = Api(blueprint,
              title="My API",
              description="My Cool API")
api.add_namespace(cat)
api.add_namespace(dog)
app.register_blueprint(blueprint)
app.run(debug=True)

Hopefully, this provides the missing pieces I had a hard time finding. I will see about getting a PR to improve the documentation.

yesthesoup commented 4 years ago

@james-powis I would suggest opening up your PR in https://github.com/python-restx/flask-restx instead, which is where all the updates for this code base have moved to.

james-powis commented 4 years ago

@yesthesoup well that is embarrassing, thanks for the pointer... https://github.com/python-restx/flask-restx/pull/78 for posterity sake.

yesthesoup commented 4 years ago

Not at all! Thanks for making this change to make it clearer for everyone.

NieRonghua commented 3 years ago

I ended up being able to accomplish the original goal of two blueprints with only one namespace the following way:

In my_project.routes.ui

api = Namespace('ui', description='User Interface API')

In my_project.routes.service

service = Blueprint('service', __name__)
api = Namespace('service', description='Service API')

In my_project.__init__.py, where I am using the application factory pattern

def create_app():
    ...
    from my_project.routes.ui import api as ui_ns
    from my_project.routes.service import service, api as service_ns

    blueprint = Blueprint('api', __name__)
    api = Api(blueprint)
    app.register_blueprint(blueprint, url_prefix='/api')
    app.register_blueprint(service)

    api.add_namespace(service_ns)
    api.add_namespace(ui_ns)
    ...

This results in the following app.url_map output:

...
 <Rule '/api/service/endpoint1' (POST, OPTIONS) -> api.service_endpoint1>,
 <Rule '/api/ui/endpoint1' (GET, HEAD, OPTIONS) -> api.ui_endpoint1>,
 <Rule '/api/swagger.json' (GET, HEAD, OPTIONS) -> api.specs>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.doc>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.root>,
... # etc

Both /ui and /service are under the same url prefix/base path /api.

good method

SincerelyUnique commented 3 years ago

I ended up being able to accomplish the original goal of two blueprints with only one namespace the following way:

In my_project.routes.ui

api = Namespace('ui', description='User Interface API')

In my_project.routes.service

service = Blueprint('service', __name__)
api = Namespace('service', description='Service API')

In my_project.__init__.py, where I am using the application factory pattern

def create_app():
    ...
    from my_project.routes.ui import api as ui_ns
    from my_project.routes.service import service, api as service_ns

    blueprint = Blueprint('api', __name__)
    api = Api(blueprint)
    app.register_blueprint(blueprint, url_prefix='/api')
    app.register_blueprint(service)

    api.add_namespace(service_ns)
    api.add_namespace(ui_ns)
    ...

This results in the following app.url_map output:

...
 <Rule '/api/service/endpoint1' (POST, OPTIONS) -> api.service_endpoint1>,
 <Rule '/api/ui/endpoint1' (GET, HEAD, OPTIONS) -> api.ui_endpoint1>,
 <Rule '/api/swagger.json' (GET, HEAD, OPTIONS) -> api.specs>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.doc>,
 <Rule '/api/' (GET, HEAD, OPTIONS) -> api.root>,
... # etc

Both /ui and /service are under the same url prefix/base path /api.

Cool, thanks, it works for me!