marshmallow-code / flask-smorest

DB agnostic framework to build auto-documented REST APIs with Flask and marshmallow
https://flask-smorest.readthedocs.io
MIT License
653 stars 73 forks source link

Add url_prefix for each blueprint #152

Open hermannfrost opened 4 years ago

hermannfrost commented 4 years ago

Hello,

I'm considering using flask-smorest for our api. I especially like the interaction with the marshmallow library and the rather simple handling of flask-smorest. Nevertheless, while going through this library I noticed one thing: Since flask-smorest seems to be based on creating blueprints that are added to an app/api, I don't have the possibility to create a blueprint with a fixed url_prefix and assign many possible namespaces to that blueprint (as in flask-restx). Here in flask-smorest the blueprints are a kind of namespace under which I define different routes with endpoints. But I would like to have the possibility to define a url_prefix for all blueprints without having to write it into every blueprint by hand. How do I do this as effective as possible so that I have to modify as little internal code of the library as possible? How do I also get the url_prefix not to be displayed in swagger ui for each endpoint?

I don't want to add all blueprints to an app, but to a kind of layer that defines a prefix for all blueprints and then add this layer to the app. Furthermore, the prefix of this layer should not appear in every route in swagger, but should be present as a kind of base url for every route.

Many greetings

lafrech commented 4 years ago

I suppose you could just override Blueprint to change the init to pass your prefix. But it would probably appear in the spec.

I'm not sure I fully understand what you're trying to achieve. I generally expose my APIs under a subpath like https://domain.tld/myapi/v1.0/. I do it at webserver level, in my apache config file.

WSGIScriptAlias /myapi/v1.0/ /path/to/application.wsgi

I hope this helps.

azzamsa commented 4 years ago

How to achieve this in flask level?

putting /v1/ in every blueprint is not maintainable / hard to change

blp = Blueprint(
    "Health", __name__, url_prefix="/v1/health", description="Health operations"
)

tried passing base_prefix to register_blueprint but didn't work either

def register_blueprints(api):
    """Initialize application with all modules"""
    for module in MODULES:
        api.register_blueprint(module.blp, base_prefix="/v2")

Tried spec_kwargs={"basePath": "/v2"} and I get A name collision occurred between blueprints

def register_blueprints(app):
    """Register Flask blueprints."""
    api = extensions.create_api(app)
    api.init_app(app, spec_kwargs={"basePath": "/v2"})
    modules.register_blueprints(api)
svidela commented 4 years ago

Hi,

I don't know if it's the "official" way to do it, but I'm adding a common url prefix as follows:

def register_blueprints(api):
    """Initialize application with all modules"""
    for module in MODULES:
        api.register_blueprint(module.bp, url_prefix=f"/{common_prefix}/{module.bp.url_prefix}")
azzamsa commented 4 years ago

solution

My current solution is to skip url_prefix in each resources, and changing from @blp.route("/") to @blp.route("/health"). then assigning url_prefix when registering bluprint

blp = Blueprint("Health", __name__, description="Health operations")

@blp.route("/health")
class HealthCheck(MethodView):
    @blp.response(HealthSchema)
    def get(self):
    ...
def register_blueprints(api):
    """Initialize application with all modules"""
    for module in MODULES:
        api.register_blueprint(module.blp, url_prefix="/v2/")

I have write this reply 9 hours ago, but forgot to click "comment" :)

Btw, I see your approach @svidela is simpler.

sbigtree commented 2 years ago
class MyApi(Api):
    def register_blueprint(self, blp, **options):
        """Register a blueprint in the application

        Also registers documentation for the blueprint/resource

        :param Blueprint blp: Blueprint to register
        :param options: Keyword arguments overriding Blueprint defaults

        Must be called after app is initialized.
        """
        blp.url_prefix = f'{self._app.config.get("APPLICATION_ROOT")}{blp.url_prefix}'
        blp_name = blp.name if "name" not in options else options["name"]

        self._app.register_blueprint(blp, **options)

        # Register views in API documentation for this resource
        blp.register_views_in_doc(self, self._app, self.spec, name=blp_name)

        # Add tag relative to this resource to the global tag list
        self.spec.tag({"name": blp_name, "description": blp.description})