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

Multiple separate apis per-application ?

Open ddorian opened this issue 3 years ago • 11 comments

Looks like some configs like OPENAPI_URL_PREFIX are fixed to the application and this prevents the ability to have multiple apis for 1 app. The simplest case I'd think 1 Api() per major-version. So you'd want a completely separate api between /v1/ and /v2/.

ddorian avatar Jun 10 '21 15:06 ddorian

To support several API versions, we run multiple versions of the code at different URLs.

I never tried to have multiple API versions in the same application version. I don't know how I'd achieve this with flask-smorest. It is not only about the documentation. How do you separate the views from v1 and v2?

I guess you could do it by nesting blueprints in a v1 and a v2 blueprint, using the Flask 2 blueprint nesting feature. But limiting duplication would require reusing blueprints, therefore parametrizing them for the v1 vs. v2 differences. And I'm not sure about the support of nested blueprints in flask-smorest. This is a new Flask feature I haven't tested.

As of today, multiple APIs per app is not supported, and I don't deem the benefit/cost high enough to try to do it.

Anyone feeling differently, please comment.

lafrech avatar Jun 14 '21 09:06 lafrech

I don't know how I'd achieve this with flask-smorest.

Change all the settings to per-app to be per-api object. You have 1 swagger-ui,redoc,etc per-api instead for the whole application.

How do you separate the views from v1 and v2?

You just need a way to connect a blueprint to an Api() and that's it. Think everything that currently is global, gets connected to Api(). When you register a blueprint, you also register it with an Api() and that's how you can distinguish things from each other.

ddorian avatar Jun 14 '21 10:06 ddorian

Oh, right. 1 Flask app with multiple flask-smorest Apis.

How would you prefix the parameters to distinguish the Apis in the app config context? The Api object has no name attribute. Shall we add one?

lafrech avatar Jun 14 '21 11:06 lafrech

The Api object has no name attribute. Shall we add one?

Yes, name can be used to distinguish them.

How would you prefix the parameters to distinguish the Apis in the app config context?

Instead of doing:

app.config["OPENAPI_URL_PREFIX"] = ""

I personally would use my own keys:

Api(OPENAPI_URL_PREFIX=app.config["API_V1_URL_PREFIX"])

If you want to make it dynamic, then something like:

Api(api_name="api_v1")
app.config["SMOREST"] = {"api_v1": {"OPENAPI_URL_PREFIX": ""}}

Another way would be to concat SMOREST with Api().name and config_name:

Api(api_name="api_v1")
app.config["SMOREST_API_V1_OPENAPI_URL_PREFIX"] = ""

ddorian avatar Jun 14 '21 11:06 ddorian

I didn't see this issue and ended up supporting multiple APIs just by passing kwargs down to the DocBlueprintMixin. See #259

This lets you set up the APIs like this:

api1 = Api(
    app,
    spec_kwargs={
        "title": "API 1",
        "version": "v1",
        "openapi_version": "3.0.2",
    },
    doc_kwargs={
        "name": "api-docs-1",
        "url_prefix": "/api1",
        "redoc_url": "https://cdn.jsdelivr.net/npm/[email protected]/bundles/redoc.standalone.js",
        "redoc_path": "/docs",
    },
)

api2 = Api(
    app,
    spec_kwargs={
        "title": "API 2",
        "version": "v0.0.1",
        "openapi_version": "3.0.2",
    },
    doc_kwargs={
        "name": "api-docs-2",
        "url_prefix": "/api2",
        "swagger_ui_url": "https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.24.2/",
        "swagger_ui_path": "/docs",
    },
)

The minor drawback is that it doesn't support configuration through the Flask app.config using namespacing as you have discussed, but given it's an advanced feature it may be acceptable

sjhewitt avatar Jun 30 '21 05:06 sjhewitt

I like @sjhewitt approach. @lafrech what do you think: https://github.com/marshmallow-code/flask-smorest/pull/259

ddorian avatar Jul 02 '21 07:07 ddorian

Thank you guys for your thoughts on this and sorry about the delay.

Both approaches are interesting. I still need to get my head around this to get it right the first time.

Thinking APi-wise rather than app-wise might have other consequences. Are there other parameters you think would be more suited to Api than app?

lafrech avatar Sep 27 '21 21:09 lafrech

This issue and #259 are old, did the PR had any problems or why it wasn't merged ? And it looks backwards compatible

kr4xkan avatar Jul 05 '22 14:07 kr4xkan

I'm more than happy to rebase the PR if there's a chance of getting it merged...

sjhewitt avatar Jul 05 '22 21:07 sjhewitt

Sorry about the delay (again).

I'd like this kind of configuration to be done in a config file rather than from the code. Hence my preference for the approaches in https://github.com/marshmallow-code/flask-smorest/issues/252#issuecomment-860613959 over the code approach in #259.


Answering https://github.com/marshmallow-code/flask-smorest/issues/252#issuecomment-860613959.

Flask behaves kind of strangely with config files. The softwares I know of tend to either use flat .ini text files while other softwares, for instance PHP softwares have their config in code files, allowing the use of language features in the config. Flask allows the use of any text file, but parses it as a Python code file. I always found that a bit dodgy and I've always been using key/value pairs in my Flask config files. But that's a personal preference and unless the config file has to be shared (which turned out never to be the case in my experience) there is no real reason to stay away from Python features such as dicts in config files. So while I was reluctant to add features that require a dict in config, I'm beginning to open my mind to it. This is totally opinionated and opinions about this welcome but I tend to follow the choices of the clever guys at pallets so why not do it here as well?

What this means here is that while I used to favor flat names such as API_V1_URL_PREFIX, perhaps a dict-based solution would be acceptable.

This said, I don't like the idea of adding a SMOREST prefix (or namespace if using dicts).

What we could do, which wouldn't be a breaking change, would be to add a config prefix parameter defaulting to an empty string and look for config variables prefixed with it. Pretty much what is shown above.

Api(config_prefix="API_V1_")  # We could add the trailing underscore automagically in the join
app.config["API_V1_OPENAPI_URL_PREFIX"] = ""

I think this does the job and is backwards compatible.

What do you think?

lafrech avatar Jul 27 '22 19:07 lafrech

I think that is fine, dict would be as well. It would be very nice to add that feature either way though, in our case we have a few different APIs and would like specific doc pages/auth schemes. Different openapi.json is nice as well to easily generate types for a given api.

Keats avatar Aug 09 '22 14:08 Keats

@lafrech would you take a PR for the config approach? I'd prefer the dict version rather than the string one but will be fine with whatever you prefer.

Keats avatar Sep 27 '22 12:09 Keats

Hi. Yes, PR welcome. Thanks.

The change described in https://github.com/marshmallow-code/flask-smorest/issues/252#issuecomment-1197287924 is fine to me.

We could try to achieve this with a dict by doing the same with a namespace (a key) instead of a prefix, but I don't know what default value we'd use. An empty string would be kind of strange (it works but it is strange). And the config would require using it even in the usual case of a single API. Unless I'm mistaken, this is a downside of the dict approach. In this case, I prefer the string prefix approach.

lafrech avatar Oct 14 '22 22:10 lafrech

@lafrech I've created a PR to solve this issue https://github.com/marshmallow-code/flask-smorest/pull/422 .

petraszd avatar Nov 28 '22 12:11 petraszd

Good things come to those who wait.

This is achieved in flask-smorest 0.42.

lafrech avatar May 15 '23 10:05 lafrech