flask-restplus icon indicating copy to clipboard operation
flask-restplus copied to clipboard

How to aggregate Swagger docs from multiple Blueprints?

Open Midnighter opened this issue 6 years ago • 15 comments

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.

Midnighter avatar Jun 06 '18 16:06 Midnighter

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

Fearxpc avatar Jun 11 '18 12:06 Fearxpc

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?

ethe avatar Apr 03 '19 02:04 ethe

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

Fearxpc avatar Apr 04 '19 18:04 Fearxpc

@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.

ethe avatar Apr 05 '19 03:04 ethe

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

Fearxpc avatar Apr 05 '19 06:04 Fearxpc

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

themmes avatar Nov 14 '19 09:11 themmes

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.

MyJoiT avatar Nov 23 '19 09:11 MyJoiT

Hope for the aggregation of all my blueprints.

+1

weldpua2008 avatar Nov 28 '19 12:11 weldpua2008

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.

yesthesoup avatar Feb 24 '20 15:02 yesthesoup

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.

james-powis avatar Mar 11 '20 15:03 james-powis

@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.

yesthesoup avatar Mar 11 '20 17:03 yesthesoup

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

james-powis avatar Mar 11 '20 17:03 james-powis

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

yesthesoup avatar Mar 11 '20 17:03 yesthesoup

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

NieRonghua avatar Mar 26 '21 10:03 NieRonghua

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!

SincerelyUnique avatar May 25 '21 15:05 SincerelyUnique