Giter Site home page Giter Site logo

marshmallow-code / flask-smorest Goto Github PK

View Code? Open in Web Editor NEW
619.0 18.0 70.0 1.17 MB

DB agnostic framework to build auto-documented REST APIs with Flask and marshmallow

Home Page: https://flask-smorest.readthedocs.io

License: MIT License

Python 99.40% HTML 0.60%
rest-api flask marshmallow python python3 openapi apispec webargs

flask-smorest's Introduction

flask-smorest

Latest version

Python versions

marshmallow 3 only

OpenAPI Specification 2/3 compatible

License

Build status

Code coverage

pre-commit.ci status

Documentation

'cause everybody wants s'more

flask-smorest (formerly known as flask-rest-api) is a REST API framework built upon Flask and marshmallow.

  • Serialization, deserialization and validation using marshmallow Schema
  • Explicit validation error messages returned in response
  • Database-agnostic
  • OpenAPI (Swagger) specification automatically generated and exposed with ReDoc, Swagger UI or RapiDoc
  • Pagination
  • ETag

Install

pip install flask-smorest

Documentation

Full documentation is available at http://flask-smorest.readthedocs.io/.

Support flask-smorest

If you'd like to support the future of the project, please consider contributing to marshmallow's Open Collective:

Donate to our collective

License

MIT licensed. See the LICENSE file for more details.

flask-smorest's People

Contributors

cnoor0171 avatar davidfrederique avatar ddorian avatar dependabot[bot] avatar derlikh-smart avatar dougthor42 avatar drcpu-github avatar greyli avatar jtait avatar juur avatar lafrech avatar lindycoder avatar mjpieters avatar nonnib avatar playpauseandstop avatar pre-commit-ci[bot] avatar ryan4yin avatar sirosen avatar sloria avatar tgoddessana avatar timdiekmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flask-smorest's Issues

Error response schema

I've seen issue #19 and the recomendation instead of having more than one response is to use @blp.doc

The issue with this a approach is that it doesn't support any Schema information.

If we add schema as the object it doesn't work because it's not being replaced by it's json-schema's $ref

@blp.doc(responses={
        '401': {'schema': GenericErrorSchema, 'description': 'Invalid credentials'}
    })

On the other hand when setting more than one @blp.response the openapispec is ok because it's using the autogenerated definition. The only issue is on the response serialization wrapper

Lot's of ideas come to my mind to fix this, but what do you think?

  • Allow @blp.response with some extra param like only_doc=True
  • Add @blp.response_doc to the ResponseMixin where wrapper just returns result_raw
  • Allow using @blp.doc(responses=...) with GenericErrorSchema
  • Make my GenericErrorSchema json encode into a $ref dict by calling something like OpenAPIConverter.get_ref_dict

[RFC] Deprecate Blueprint.doc decorator

The Blueprint.doc decorator is meant to add documentation that can't be inferred from the code or added by dedicated decorators. It allows the user to pass manual documentation that will be deep-merged with the automatically generated documentation. Unfortunately, it is not the silver bullet one would expect. It suffers from a few intrinsic issues.

We could try to play smart and improve the way the structure received by doc is used, but I'd rather add dedicated decorators or other explicit means to allow the user to pass documentation in a structured fashion, with a clear and thin abstraction layer.

I'd like to use this issue to list known issues about Blueprint.doc and use cases that should be covered by other means. The goal being to make it useless, and to deprecate it if possible.

Known issues

  • It can't really document arguments as deep-merging lists doesn't work so it conflicts with auto-generated documentation.
  • When adding extra documentation for a response, the user must use the same type for the status code than the one used in the response decorator (#60 (comment)).
  • It doesn't handle the OASv2 / v3 transparently, while the other decorators are pretty good at it, allowing the user to change the OAS version with a configuration parameter using the same codebase. For instance, it doesn't add ['content']['application/json'] when using v3 which makes it more verbose to the user.

Use cases

  • Add summary/description: Those can be passed in the view function docstring.
  • Add examples: Can now be achieved with response/arguments decorators.
  • Document specific API features (e.g. security information). Since this is quite specific, I think it could be done by a custom decorator defined in user code. Typically, when using a decorator to manage accesses to resources (generally from a flask third-party library), this decorator should be wrapped in a custom decorator that adds the information to the docs (see #36 (comment) for an example). This means the _apidoc attribute mechanism becomes public API.
  • Document / add extra-information to parameters. The only real need was for path parameters. Documentation for path parameters can be passed in Blueprint.route. (See #23)
  • Document / add extra-information to responses. We could add a specific decorator for that. If we assume a view function can only have one normal response and other responses are errors (or error-likes such as redirections or "not modified"), then that decorator could be called "error". (See #44)

@blueprint.response does not respect error responses

Hi! I'm using your library as a convenience wrapper and I really like it!

However, one thing I've come across is that when I use the @blueprint.response decorator on my view function, all responses are marshaled with the Schema and return a 200 response. The decorator does not respect when I've finished the request with an error code.

There are many times when I abort the request (using flask's abort) instead of returning data. flask-rest-api overrides my aborts and return {errors}, 409 statements, so for any view function that returns an error, I cannot use @blueprint.response. This means I'm using @blueprint.response for some of my view functions and not for others, which makes for some very inconsistent and bug-prone code.

As far as I can think of, there isn't a reason you would ever want to marshal an error response to the schema, right? So would it be possible for flask-rest-api to only marshal the response with marshmallow if the view function returns "normal" data, but respect things with error codes, like flask's abort? Maybe @blueprint.response could include a keyword arg like respect_errors which defaults to True or something like that?

Thanks again, great work!

Usage docs?

Hey guys,

Interesting project you have here; are there any plans on how to actually use it?

Thanks!
Diogo

Defining multiple responses

I'd like to define multiple response codes for a view (201 for created, 200 for updated).

It seems that decorating a view twice will always return 200:

    @thing_bp.response(ThingSchema, code=200, description='Thing was updated')
    @thing_bp.response(ThingSchema, code=201, description='Thing was created')
    def post(...)

Is there a way around this?

Thanks

Modify error handler logging level

Errors triggered by abort(code) are logged as INFO. This is to high.

I suggest

  • defaulting to DEBUG
  • making this overridable by using a class attribute ERROR_LOGGING_LEVEL of ErrorHandlerMixin

If this is still not enough, people can override _log_error.

flask_classful support

I use flask_classful in some of my projects, and it would be great if it was supported.

I'll need to dig into the code a bit more, but I'm fairly certain flask_classful's main export, FlaskView, is fairly similar in implementation to the now standard Flask.MethodView (which is already supported).

Would this be something that could be integrated into the project?

Route parameter openapi generation is incompatible with 3.0

When using a parameter in a route: @bp.route('/<string:foo_id>')

The openapi.json generated is not compatible with openapi 3.0:

"parameters": [
          {
            "in": "path",
            "name": "foo_id",
            "required": true,
            "type": "string"
          }
        ]

should be:

"parameters": [
          {
            "in": "path",
            "name": "bar_id",
            "required": true,
            "schema": {
                 "type": "string"
            }
          }
        ]

I am not sure if this is a flask-rest-api issue, or something to fix in a dependency… But I would be happy to take stab at a PR, if pointed in the right direction (I couldn't figure where in blueprint.py that part of the doc was generated).

Is it possible to implement extended response

Hello!

First of all, thank you for such a great library!

I have and old legacy API that returns responses like so:

{
    "type": "error",
    "kind": "validation",
    "message": { ... some message ...}  # this is built upon exception text
}

# or

{
    "type": "success",
    "result": { ... some data ...}  # this is what view returns
}

As you can see, the responses have envelope. Is it possible to implement such functionality in pythonic way still using @blp.response decorator?

Error condition can be determined on any exception (or specific like ValidationError to set kind and message fields) and on status method. Same for successful response.

Thanks!

Cannot create a second flask application

Hi,

I believe that due to recent changes around the Blueprint.doc decorator it's not possible to have 2 flask applications created at the same time. See below a minimal example. Tests should pass with flask-rest-api 0.10 but the test test_app fails with 0.11. Is this the expected behavior or it's a bug?

### app.py ###

from flask import Flask
from flask.views import MethodView

from flask_rest_api import Api, Blueprint

rest_api = Api()

_ = Blueprint('Test', __name__, url_prefix='/api/test')

@_.route('/')
class TestView(MethodView):

    @_.response()
    def get(self):
        return [1,2,3]

def create_app():
    app = Flask(__name__)

    rest_api.init_app(app)
    rest_api.register_blueprint(_)

    return app
### test_app.py ###

from app import create_app

def test_app():
    app = create_app()
    
    # This will fail with flask-rest-api >= 0.11
    app2 = create_app()

Here's the output from pytest

$ pytest                                                                                           
============================================================================ test session starts ============================================================================
platform darwin -- Python 3.7.0, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
rootdir: /Users/sancho/Development/test, inifile:
collected 1 item

test_app.py F                                                                                                                                                         [100%]

================================================================================= FAILURES ==================================================================================
_________________________________________________________________________________ test_app __________________________________________________________________________________

    def test_app():
        app = create_app()
>       app2 = create_app()

test_app.py:5:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app.py:21: in create_app
    rest_api.register_blueprint(_)
../flask-rest-api/flask_rest_api/__init__.py:81: in register_blueprint
    blp.register_views_in_doc(self._app, self.spec)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <flask_rest_api.blueprint.Blueprint object at 0x103a3d470>, app = <Flask 'app'>, spec = <flask_rest_api.spec.APISpec object at 0x10433a400>

    def register_views_in_doc(self, app, spec):
        """Register views information in documentation

        If a schema in a parameter or a response appears in the spec
        `definitions` section, it is replaced by a reference to its definition
        in the parameter or response documentation:

        "schema":{"$ref": "#/definitions/MySchema"}
        """
        # This method uses the documentation information associated with the
        # endpoint (in self._docs) to provide documentation for the route to
        # the spec object.
        for endpoint, doc in self._docs.items():
            # doc is a dict of documentation per method for the endpoint
            # {'get': documentation, 'post': documentation,...}

            # Prepend Blueprint name to endpoint
            endpoint = '.'.join((self.name, endpoint))

            # Format operations documentation in OpenAPI structure
            # Tag all operations with Blueprint name
            # Merge manual doc
            for key, (auto_doc, manual_doc) in doc.items():
                self._prepare_doc(auto_doc, spec.openapi_version)
>               auto_doc['tags'] = [self.name]
E               TypeError: 'str' object does not support item assignment

../flask-rest-api/flask_rest_api/blueprint.py:161: TypeError

Automatically fill `message` field for HTTPException, when it is present in api_docs…

Ideally, when an endpoint call abort with an HTTP error, we would like the message to be filled according to the endpoint's documentation (e.g. "Resource missing", rather than Flask's default "The requested URL was not found on the server" for a 404).

This seems like something that could be done in the abort method? I have had a look at the Blueprint class, but still unsure where it should look, to get the list of responses for that particular call…

APIspec error when using two-way schema nesting

I'm using marshmallow v3-rc5 and using two-way nesting
Using this technique I get the following error if I attempt to use something like @blp.response(CreatorSchema(many=True, exclude=('follower_count',))):

/Users/cyber/.virtualenvs/luve-pim5aQIP/lib/python3.7/site-packages/apispec/ext/marshmallow/common.py:143: 
UserWarning: Multiple schemas resolved to the name Creator. The name has been modified. Either manually add each of the schemas with a different name or provide a custom schema_name_resolver.

and see multiple versions of the schema in swagger (Creator, Creator1).

If I remove the exclude arg to my schemas and make new schemas then everything works perfectly. Something about exclude causes it to think there are multiple versions of the schema and it all explodes.

Support for http.HTTPStatus

When I started using this library I wrote, out of habit, something like this:

@bp.response(code=HTTPStatus.NO_CONTENT)

(In my opinion using htttp.HTTPStatus is a good think because it prevents Magic Number anti-pattern)
It looked fine, but after a while i realized that instead of resulting in:

"responses": {
    "204": {}
}

It is rendered as:

"responses": {
    "HTTPStatus.NO_CONTENT": {}
}

So my suggestion is to add int() to this line:
https://github.com/Nobatek/flask-rest-api/blob/92229aaf74819ba9a87ff98d7888ca25661d7e4e/flask_rest_api/response.py#L49

The error handler intercepts trailing slash redirect

I had an issue with URLs with trailing slash where flask would normally redirect.
e.g. @blp.route('/things/') , a request for /things should redirect to /things/ by default.

Example:

from flask import Flask
from flask_rest_api import Api, Blueprint

app = Flask(__name__)
app.config['OPENAPI_VERSION'] = '3.0.2'

api = Api(app)


blp = Blueprint('blp', __name__)


@blp.route('/things/')
def get_things():
    return 'things'


api.register_blueprint(blp)

When calling the endpoint without the trailing slash you get a serialized 308 response with no Location header instead of a proper redirect response:

curl -i http://localhost:5001/things
HTTP/1.0 308 PERMANENT REDIRECT
Content-Type: application/json
Content-Length: 42
Server: Werkzeug/0.15.1 Python/3.6.7
Date: Tue, 26 Mar 2019 20:57:42 GMT

{"status":"308 Permanent Redirect: None"}

Is this expected?

I looked at the errorhandler code and saw it captures any type of HTTPException, but the redirect triggers a werkzeug.routing.RequestRedirect which is a subclass of HTTPException and RoutingException

I worked around this in my app by overriding handle_http_exception like this

from flask_rest_api import Api as BaseApi
from werkzeug.routing import RoutingException

class Api(BaseApi):
    def handle_http_exception(self, error):
        # Don't serialize redirects
        if isinstance(error, RoutingException):
            return error
        return super().handle_http_exception(error)

But the framework probably should only register an error handler for codes above 400.

Cheers

Customizable docstring ignore tag

Summary

The string that stops docstring parsing (---) should be customizable.

Details

According to the docs, "the part of the docstring following the `'---`` line is ignored."

This is normally great, but can cause issues when using some docstring styles such as NumPyDoc.

The separator value should be user-customizable via flask config value, perhaps OPENAPI_DOCSTRING_SEP or something like that.

Current Behavior

def get(...):
    """
    Find pets by ID.

    Return pets based on ID.

    Parameters
    ----------
    pet_id : int
        The PK for the pet to query

    Returns
    -------
    dict : :class:`orm.Pet`
        A dict representation of the pet instance. When returned via flask, is
        passed through :func:`flask.jsonify`.
    """

will be mangled to:

{
    'get': {
        'summary': 'Find pets by ID.',
        'description': 'Return pets based on ID.\n\nParameters',
    }
}

Note the "Parameters" string in the description.

Expected Behavior

app = Flask('My API')
app.config['OPENAPI_VERSION'] = '3.0.2'
app.config['OPENAPI_DOCSTRING_SEP'] = "Parameters"
api = Api(app)
def get(...):
    """
    Find pets by ID.

    Return pets based on ID.

    Parameters
    ----------
    pet_id : int
        The PK for the pet to query

    Returns
    -------
    dict : :class:`orm.Pet`
        A dict representation of the pet instance. When returned via flask, is
        passed through :func:`flask.jsonify`.
    """
{
    'get': {
        'summary': 'Find pets by ID.',
        'description': 'Return pets based on ID.',
    }
}

Implementation Notes

Any string should be acceptable as a separator. In addition, the None value should also be accepted. If the separator is None, then the entire docstring is included in the swagger details.

It looks like this docstring parsing is done in utils.load_info_from_docstring. I haven't investigated much further than that, but it looks like it might be pretty easy to implement.

No way to add description to path parameters

I can't see any way to add a description to a path parameter. Using the @blp.doc decorator as in:

    @blp.doc(parameters=[{"name": "thing_id", "description": "The ID of the thing"}])

just results in adding another parameter with a duplicate name and only a description. I have a local fix for this in FlaskPlugin where I just scan the existing parameters and update rather than append if I find one with the same name. I can get this into a PR unless you have another way you'd rather approach this?

Include source distribution

According to https://pypi.org/project/flask-rest-api/#files this library currently only provides wheels to PyPI. Best practice is to upload source distributions as well. Fixing should just be a matter of running python setup.py sdist upload.

Thanks for maintaining flask-rest-api, hope to try it soon! (Would have tried it already if not for this issue, which makes it harder for me to use it on some internal infrastructure.)

[RFC] Change module name

This might sound silly, but the API's current name ('flask-rest-api') makes it practically impossible to Google.

Not only is it slightly frustrating when trying to recall the github or readthedocs URL in a bind, but in some possible future, it will make it all but impossible to try and find related posts on Stackoverflow or elsewhere.

I would strongly recommend picking a name (any name) that is somewhat unique and easy to search. I know it's going to be a bit of a mess updating, but probably still easier now, than another year down the road.

Serializing flask response

I need to set cookies with on some endpoints. Just returning jsonify({ 'key': 'value' }) does not get serialized. Is there a way to set a cookie in the response together with the "normal" endpoint response?

How to structure a real world application

Hi,

I believe that due to recent updates in both apispec and marshmallow my application started to throw warnings and exceptions (e.g., marshmallow.exceptions.RegistryError, apispec.exceptions.DuplicateComponentNameError) that were not happening a few weeks ago.

I couldn't came up with a minimal example to illustrate this yet. But I wonder if you could provide an example (beyond the basic "Petstore example" from the docs) of how to structure a more realistic application (like using the application factory pattern, splitting schemas and views in different modules, etc).

Regards,

Support webargs fields or Marshmallow instance with options in arguments

Hi,
I feel it would be handy if we can use webargs dict with arguments decorator.
Or if we can use Marshmallow instance with some params for example:

@blp.arguments(Schema(only=["first", "second"]))

The reason is, we just need one Schema model for all arguments request. We can decide which params needed to be there, don't need all.

I know we can have load_only or dump_only in Marshmallow. But it would be more flexible if we can use instance with only=, partial=, exclude=...

I really like this library. Found it is the best fit for my projects.
If you are planing to support this in long run, I would like to help.
Thanks

Blueprint definitions

Hi,

I'm creating an app with many blueprints for different sections of the API. It seems there should be a way to define definitions inside the blueprint. Something like:

blp = Blueprint(
    'pets', 'pets', url_prefix='/pets',
    description='Operations on pets'
)

@blp.definition('Pet')
class PetSchema(ma.Schema):

    class Meta:
        strict = True
        ordered = True

    id = ma.fields.Int(dump_only=True)
    name = ma.fields.String()

It's not suitable to create a circular dependency where the API is defined so it seems you need to do something like:

api=Api()
@api.definition('Pet')

Any recommendations on workarounds? thanks!

Document response without specifying schema

I have an endpoint that allows the end user to specify which fields they want returned, which I pass through to the Marshmallow serializer. This is pretty easy to do:

blp = Blueprint('people', 'people', url_prefix='/people',
                description='Operations on people')

class PersonGetArgsSchema(Schema):
    """Query arg schema for people get endpoint"""
    id = fields.String(required=True)
    return_fields = fields.List(fields.String(), required=True)

@api.definition('Person')
class PersonSchema(Schema):
    """Marshmallow schema for serializing a person instance"""
    id = fields.String(required=True, example='3065ac69-6aad-4832-9889-c81ab773424e')
    # other fields

@blp.route('/get')
class PersonResource(MethodView):
    @blp.arguments(PersonGetArgsSchema, location='json')
    @blp.response(None)
    def post(self, body):
        """Get a single person by ID (actually using POST, since REPORT isn't supported)"""

        # get the person from Elasticsearch
        person = PersonDoc.get(id=body['id'], _source=body['return_fields'])

        # serialize and return
        person_schema = PersonSchema(only=body['return_fields'])
        return person_schema.dump(person.to_dict())

This works fine, but because I specified @blp.response(None), my API docs don't automatically have an example response like I would if I had schema=PersonSchema.

My current solutions is to subclass Schema:

class SchemaWithEx(Schema):
    def example_dict(self):
        return {
            k: v.metadata['example'] for k, v in self.fields.items() if 'example' in v.metadata
        }

@api.definition('Person')
class PersonSchema(SchemaWithEx):
    # ...

@blp.response(schema=None, example=PersonSchema(only=['id', 'name']).example_dict())

Is my situation really obscure or would it be worth adding a feature like this?

as_kwargs=True always?

I like using as_kwargs, coming from flask-apispec which has a @use_kwargs decorator.

Feature request: have a decorator that has as_kwargs=True so I don't have to type it every time

Thanks!

No support for passing options while registering blueprint

Problem: register_blueprint method in Api does not takes "options" argument and so it is impossible to pass options to blueprint.

Current Api.register_blueprint without options support:
https://github.com/Nobatek/flask-rest-api/blob/master/flask_rest_api/__init__.py#L62

Flask's register_blueprint documentation with mentioned "options": http://flask.pocoo.org/docs/1.0/api/?highlight=blueprint#flask.Flask.register_blueprint

Blueprint register documentation:
http://flask.pocoo.org/docs/1.0/api/?highlight=blueprint#flask.Blueprint.register

It would be helpful to be able to use "options".

Schema Validation on Patch

So I was trying t implement a simple Patch use case but its proving to be a difficult affair. As Patch allows for partial submission of schema fields I tried the obvious solution and added the partial=True argument to my schema declaration.

@blp.arguments(UserInputSchema(partial=True))

This however leads to a problem related to the marshmallow model schema class, since it immediately tries to create a model instance from the passed object.

Am I missing something or does patch just not play well with the intended behavior of marshmallow's ModelSchema?

Thanks for the help in advance.

Multipart requests

Hello!

I did not found standard way to describe a multipart request. Is it supported?

Currently i had to write something like this to let uploaded files work:

request_body_description = {
    "content": {
        "multipart/form-data": {
            "schema": {
                "type": "object",
                "properties": {"logo": {"type": "string", "format": "binary"}},
            }
        }
    }
}


@bp.route("/update-logo")
class UpdateLogo(MethodView):
    @bp.response(code=204)
    @bp.doc(requestBody=request_body_description)
    def post(self):
        file = flask.request.files["logo"]
        filename = secure_filename(file.filename)
        binary = file.read()
        
        ... do something with file ...

This code allows swagger to render input type="file" with name "logo" and send multipart request when executing request from web interface.

Am i missing something?

Thanks!

Add authorization to header?

Currently I use flask_jwt_extended for API security, how can I define the need of passing Authorization header?

I tried using this:
@blp.doc(parameters={'Authorization': {'name': 'Authorization', 'in': 'header', 'description': 'Authorization: Bearer <access_token>', 'required': 'true'}})

but it throw an error

builtins.KeyError
KeyError: 'in

AttributeError while serving OpenAPI documentation

Hi,

First of all, thanks for such a great framework. I started using flask-rest-api only a few days ago and my API looks much better now.

I report this issue mostly for documentation purpose because I believe it's fixed with this commit but I thought it may be helpful for others until the next release of flask-rest-api.

Using flask-rest-api 0.9.0 with apispec 1.0.0b3, if you try to access the openapi.json file you will get the following error:

127.0.0.1 - - [10/Oct/2018 19:55:28] "GET /openapi/openapi.json HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/flask_rest_api/spec/__init__.py", line 123, in _openapi_json
    json.dumps(self.spec.to_dict(), indent=2),
  File "/Users/sancho/.local/share/virtualenvs/preciosa--GgWvvys/lib/python3.7/site-packages/apispec/core.py", line 118, in to_dict
    info.setdefault('title', self.title)
AttributeError: 'NoneType' object has no attribute 'setdefault'

To solve this issue, you either install flask-rest-api from github (after this commit) or you must do one of these:

In your config.py,

API_SPEC_OPTIONS = {
    'info': {
        'title': 'API Test',
        'version': 'v1'
    }
}

or

api = Api(app, spec_kwargs={'info': {'title': 'API Test', 'version': 'v1'}})

or using the factory pattern

api = Api()
def create_app():
    app = Flask(__name__)
    ...
    api.init_app(app, spec_kwargs={'info': {'title': 'API Test', 'version': 'v1'}})

openapi.json yields "Object of type PetSchema is not JSON serializable"

Hello,

I am just starting out with building REST Api in general, so not sure if I am missing something while using this library.

I used the example from the documentation; but when I try to access the openapi.json, I get the error mentioned in the description. Is there anything missing with the following code?

@api.definition('Pet')
class PetSchema(ma.Schema):

    class Meta:
        strict = True
        ordered = True

    id = ma.fields.Int(dump_only=True)
    name = ma.fields.String()

Also, if you could provide a complete working example, including a working openapi-ui endpoint, that'd be really helpful.

Thanks

OpenAPI JSON should use additionalProperties

OpenAPI v3 supports dictionaries to be specified with a key / value type. The following Marshamallow classes produce JSON without that information

@api.schema('Output')
class OutputSchema(ma.Schema):
    class Meta:
        strict = True

    min = ma.fields.Number()
    max = ma.fields.Number()


@api.schema('OutputDict')
class OutputDictSchema(ma.Schema):
    class Meta:
        strict = True

    data = ma.fields.Dict(keys=ma.fields.String(), values=ma.fields.Nested(OutputSchema))

Should produce something like this:

      "Output": {
        "type": "object",
        "properties": {
          "min": {
            "type": "number"
          },
          "max": {
            "type": "number"
          }
        }
      },
      "OutputDict": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "text": {
                "$ref": "#/components/schemas/Output"
                }
              }
            }
          }
        }

Handling invalid JSON input…

I think this is more of a webargs issue than specific to flask-rest-api, but I also suspect that the most efficient way to address it, would be inside the API…

In case an invalid JSON input is provided (to a POST or PUT call), it is treated as an empty dict {}, no error is thrown, and, depending on the value of model_build_obj, it is either:

  • passed as is to the function's payload
  • passed to Marshmallow, which fails to generate an object (but doesn't give the most useful reason why)

From what I understand, this is due to webargs' flask parser loading the JSON silently:

        # Fail silently so that the webargs parser can handle the error
        if hasattr(req, "get_json"):
            # Flask >= 0.10.x
            json_data = req.get_json(force=force, silent=True)
        else:
            # Flask <= 0.9.x
            json_data = req.json
        if json_data is None:
            return core.missing

… from the comment in webarg's code, it seems the user is supposed to implement error handling to catch this case, although I don't quite see how it would be possible to know retrospectively that the core.missing being returned was the reason of an invalid JSON, rather than actually missing…

Do you have any idea how I could go about ensuring my API returns a proper 400 error?

Override of summary an description for gets in same resource

So I was trying to do something akin to:

blp = Blueprint(
    "users", "users", description="Operations on users"
)

@blp.route("/users")
class UsersResource(MethodView):

    @blp.response(UserOutputSchema(many=True))
    def index(self):
        """
        Sum 1

        Desc 1
        """
        return User.query.all()

    @blp.route("/users/<user_id>")
    @blp.response(UserOutputSchema)
    def get(self, user_id):
        """
        Sum 2

        Desc 2
        """
        return User.query.get(user_id)

For this case the generated openapi spec for index is overridden with the summary and description values of get. As such:

...
  "paths": {
    "/users/{user_id}": {
      "get": {
        "summary": "Sum 2",
        "description": "Desc 2",
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserOutput"
                }
              }
            }
          }
        },
        "tags": [
          "users"
        ],
        "parameters": [
          {
            "in": "path",
            "name": "user_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/users": {
      "get": {
        "summary": "Sum 2",
        "description": "Desc 2",
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserOutput"
                }
              }
            }
          }
        },
        "tags": [
          "users"
        ]
      }
    }
  },
...

What is the correct way to fix this behaviour, should both cases be separated in different classes?

Exception after installing 0.13.1

Hello, i have just updated from version 0.12.0 and experiencing following issue:

127.0.0.1 - - [24/Feb/2019 12:06:49] "GET /api/endpoint HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Users/user/git/project/env/lib/python3.7/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/user/git/project/env/lib/python3.7/site-packages/flask/cli.py", line 76, in find_best_app
    app = call_factory(script_info, app_factory)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/flask/cli.py", line 116, in call_factory
    return app_factory()
  File "/Users/user/git/project/app.py", line 36, in create_app
    api.init_app(app)
  File "/Users/user/git/project/api/__init__.py", line 10, in init_app
    api.init_app(app)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/flask_rest_api/__init__.py", line 61, in init_app
    self._init_spec(**(spec_kwargs or {}))
  File "/Users/user/git/project/env/lib/python3.7/site-packages/flask_rest_api/spec/__init__.py", line 193, in _init_spec
    self.spec.components.schema(name, schema=schema_cls, **kwargs)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/core.py", line 141, in schema
    ret.update(plugin.schema_helper(name, component, **kwargs) or {})
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/ext/marshmallow/__init__.py", line 158, in schema_helper
    json_schema = self.openapi.schema2jsonschema(schema_instance)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 645, in schema2jsonschema
    jsonschema = self.fields2jsonschema(fields, partial=partial, ordered=ordered)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 669, in fields2jsonschema
    property = self.field2property(field_obj)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 421, in field2property
    ret["items"] = self.field2property(field.container)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/apispec/ext/marshmallow/openapi.py", line 415, in field2property
    schema_dict = self.resolve_nested_schema(field.schema)
  File "/Users/user/git/project/env/lib/python3.7/site-packages/marshmallow/fields.py", line 447, in schema
    dump_only=self._nested_normalized_option('dump_only'),
TypeError: __init__() missing 1 required positional argument: 'cls_or_instance'

Arguments in header

I tried using @blueprint.arguments(PetSchema, location='headers') and I tried with location='headers' in the schema field declaration without result. What is the proper way to use arguments in the headers? Thank you!

Question: multiple @response decorators

Hello, i have defined two response types on my view function:

    @blp.response(code=404, description="Product not found")
    @blp.response(ProductSchema(), code=200)
    def get(self, product_id):
        ...

Generated documentations contains description of those two possible response types and looks perfect.

If view is aborted via abort(404) then everything works as expected, but i got an error otherwise:

2018-11-22 18:18:43.686 INFO    127.0.0.1 - - [22/Nov/2018 18:18:43] "GET /api/products/1/ HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/views.py", line 88, in view
    return self.dispatch_request(*args, **kwargs)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/views.py", line 158, in dispatch_request
    return meth(*args, **kwargs)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask_rest_api/response.py", line 55, in wrapper
    resp = jsonify(self._prepare_response_content(result_dump))
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/json/__init__.py", line 321, in jsonify
    dumps(data, indent=indent, separators=separators) + '\n',
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/json/__init__.py", line 179, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 201, in encode
    chunks = list(chunks)
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 437, in _iterencode
    o = _default(o)
  File "/Users/asyncee/app/env/lib/python3.6/site-packages/flask/json/__init__.py", line 81, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py", line 180, in default
    o.__class__.__name__)
TypeError: Object of type 'Response' is not JSON serializable

So my question is: am i doing everything correctly and multiple decorators supported?

Exception after upgrading 0.13.1 -> 0.14.0

Hello.

Function flask_rest_api/utils.unpack_tuple_response checks that result returned is a tuple and tries to unpack it.

This is not working if i'm returning tuple-like object, for example sqlalchemy result object (which can be a tuple subclass) or namedtuple. All this objects pass isinstance(rv, tuple).

Object of type 'datetime' is not JSON serializable while rendering doc

First of all, thank you for this lib :)

I got the following marshmallow schema :

    class Meta:
        strict = True
        ordered = True
        a_date = fields.DateTime(
            format='iso',
            missing=datetime.combine(date.today(), time()))

But it fails while rendering the doc (json schema) with the error :

 Object of type 'datetime' is not JSON serializable

It fails in In this file

json.dumps(self.spec.to_dict(), indent=2)

As a quick fix, I propose a simple datetime serializer:

def custom_date_serializer(obj):
    """Custom json serializer"""
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    raise TypeError ("Type %s not serializable" % type(obj))

json.dumps(self.spec.to_dict(), indent=2, default=custom_date_serializer)

Create `register_schema` method in `flask_rest_api.Api`

Hi!

I want to start off with saying I am really thankful for work on this library.

The way I prefer structuring my flask app is via application factory but defining a schema, as suggested in the documentation, requires an instance of flask_rest_api.Api. I suggest creating a method in called register_schema an alternative to the schema decorator. Usage will be similar to register_blueprints.

from flask import Flask
from flask_rest_api import Api

from . import schemas

def create_app(config):
     app = Flask(__name__)
     api = Api(app)
     register_api_schemas(api)
     register_api_blueprint(api)
     return app

def register_schemas(api):
     api.register_schema("Foo", schemas.FooSchema)
     api.register_schema("Bar", schemas.BarSchema) 

My current work around is something like

def register_schemas(api):
     api.schema("Foo")(schemas.FooSchema)
     api.schema("Bar")(schemas.BarSchema) 

Wrong path response structure in docs when using OpenAPIv3

Hi,
First of all, thank you so much for this awesome extension! It really fullfills what I need.
I am just facing a little problem. At first i thought it was because i was using a application factory, but then i tried this simple code and I could not generate the openapi.json.
It throws the error TypeError: Object of type 'PetSchema' is not JSON serializable.
It sources back to

File "/flask_app/ext/flask_rest_api/spec/__init__.py", line 78, in _openapi_json
 def _openapi_json(self):
        """Serve JSON spec file"""
        # We don't use Flask.jsonify here as it would sort the keys
        # alphabetically while we want to preserve the order.
        return current_app.response_class(
            json.dumps(self.to_dict(), indent=2),
            mimetype='application/json')

Basically json.dumps fails because it is passed the Marshmallow schema.
Could you please tell me what I do wrong?
Merci beaucoup !!!

app.py:

import marshmallow as ma
from flask import Flask
from flask.views import MethodView

from flask_rest_api import Api, Blueprint

from .config import Config

app = Flask('My API')
app.config.from_object(Config)
api = Api(app)

class Pet:
    pass

api.definition('Pet')


class PetSchema(ma.Schema):

    class Meta:
        strict = True
        ordered = True

    id = ma.fields.Int(dump_only=True)
    name = ma.fields.String()


class PetQueryArgsSchema(ma.Schema):

    class Meta:
        strict = True
        ordered = True

    name = ma.fields.String()


blp = Blueprint(
    'pets', 'pets', url_prefix='/pets',
    description='Operations on pets'
)


@blp.route('/')
class Pets(MethodView):

    @blp.arguments(PetQueryArgsSchema, location='query')
    @blp.response(PetSchema(many=True))
    def get(self, args):
        """List pets"""
        return Pet.get(filters=args)


api.register_blueprint(blp)

config.py

API_VERSION = '1.0.0'
OPENAPI_VERSION = '3.0.0'
OPENAPI_URL_PREFIX = "/apidoc/"
OPENAPI_JSON_PATH = 'openapi.json'
OPENAPI_SWAGGER_UI_PATH = "swagger-ui"
OPENAPI_SWAGGER_UI_VERSION = '3.18.2'

requirements.txt

Flask==1.0.2
flask-rest-api==0.7.0
apispec==0.39.0
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
marshmallow==2.15.4
PyYAML==3.13
webargs==4.0.0
Werkzeug==0.14.1

Simplest app with Swagger UI

I can't make my app have the Swagger route. This is the simplest app with PeeWee I came up with:

import marshmallow as ma
from flask import Flask
from flask.views import MethodView
from flask_rest_api import Api, Blueprint
from peewee import TextField
from playhouse.flask_utils import FlaskDB


class Config:
    DATABASE = {
        'name': 'database.db',
        'engine': 'SqliteDatabase',
    }
    OPENAPI_URL_PREFIX = '/doc'
    OPENAPI_REDOC_PATH = '/redoc'
    OPENAPI_SWAGGER_UI_PATH = '/swagger'
    OPENAPI_SWAGGER_URL = '/swagger'
    API_SPEC_OPTIONS = {'x-internal-id': '2'}


class MyDB(FlaskDB):
    def connect_db(self):
        if self.database.is_closed():
            super(MyDB, self).connect_db()


app = Flask('My API')
app.config.from_object(Config)
api = Api(app)
db = MyDB(app)


class Pet(db.Model):
    name = TextField(unique=True)


db.database.create_tables([Pet])

pet = Pet(name='cici')
try:
    pet.save()
except:
    pass


#  @api.definition('Pet')
class PetSchema(ma.Schema):
    class Meta:
        strict = True
        ordered = True

    id = ma.fields.Int(dump_only=True)
    name = ma.fields.String()


blp = Blueprint(
    'pets',
    'pets',
    url_prefix='/pets',
    description='Operations on pets'
)


@blp.route('/')
class Pets(MethodView):
    @blp.response(PetSchema(many=True))
    def get(self):
        """List pets"""
        return Pet.select()


api.register_blueprint(blp)

if __name__ == '__main__':
    app.run()

These are the routes:

flask routes
Endpoint                Methods  Rule
----------------------  -------  -----------------------
api-docs.openapi_json   GET      /doc/openapi.json
api-docs.openapi_redoc  GET      /doc/redoc
pets.Pets               GET      /pets/
static                  GET      /static/<path:filename>

How can I enable Swagger UI?

Adding examples to endpoints

I'm looking for a way to add examples.

I'm trying to add them with Blueprint.doc, but it seems they are thrown away by Blueprint._prepare_doc.

Is there some other way?

No way to set custom headers

In regular flask function I could return headers as last parameter:

return body, status_code, headers

That's how I return e.g. "Location" header.

In flask-rest-api I can't return such tuple nor Response object with custom headers because Api.response requires object to serialize.

I see that there is some headers handling in code https://github.com/Nobatek/flask-rest-api/blob/master/flask_rest_api/response.py#L74 but I do not know how to elegantly set them in my function.

If there's no elegant way then maybe handling return value as in flask (https://github.com/pallets/flask/blob/master/flask/app.py#L1890) should be implemented?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.