Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.

Overview

Flask-Rebar

Documentation Status CI Status PyPI status Code style Code of Conduct

Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.

Features

  • Request and Response Validation - Flask-Rebar relies on schemas from the popular Marshmallow package to validate incoming requests and marshal outgoing responses.
  • Automatic Swagger Generation - The same schemas used for validation and marshaling are used to automatically generate OpenAPI specifications (a.k.a. Swagger). This also means automatic documentation via Swagger UI.
  • Error Handling - Uncaught exceptions from Flask-Rebar are converted to appropriate HTTP errors.

Example

from flask import Flask
from flask_rebar import errors, Rebar
from marshmallow import fields, Schema

from my_app import database


rebar = Rebar()

# All handler URL rules will be prefixed by '/v1'
registry = rebar.create_handler_registry(prefix='/v1')

class TodoSchema(Schema):
    id = fields.Integer()
    complete = fields.Boolean()
    description = fields.String()

# This schema will validate the incoming request's query string
class GetTodosQueryStringSchema(Schema):
    complete = fields.Boolean()

# This schema will marshal the outgoing response
class GetTodosResponseSchema(Schema):
    data = fields.Nested(TodoSchema, many=True)


@registry.handles(
    rule='/todos',
    method='GET',
    query_string_schema=GetTodosQueryStringSchema(),
    response_body_schema=GetTodosResponseSchema(), # for versions <= 1.7.0, use marshal_schema
)
def get_todos():
    """
    This docstring will be rendered as the operation's description in
    the auto-generated OpenAPI specification.
    """
    # The query string has already been validated by `query_string_schema`
    complete = rebar.validated_args.get('complete')

    ...

    # Errors are converted to appropriate HTTP errors
    raise errors.Forbidden()

    ...

    # The response will be marshaled by `marshal_schema`
    return {'data': []}


def create_app(name):
    app = Flask(name)
    rebar.init_app(app)
    return app


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

For a more complete example, check out the example app at examples/todo.py. Some example requests to this example app can be found at examples/todo_output.md.

Installation

pip install flask-rebar

Documentation

More extensive documentation can be found here.

Extensions

Flask-Rebar is extensible! Here are some open source extensions:

Contributing

There is still work to be done, and contributions are encouraged! Check out the contribution guide for more information.

Comments
  • DELETE requests should return specified Content-Type

    DELETE requests should return specified Content-Type

    DELETE requests are returning Content-Type: text/html, this is breaking some clients that expect application/json, even though there is no actual content, simply the type in the header broke a lookup. Looks like this was introduced here, as text/html is the Flask response default mimetype.

    Rolling that back here and adding a test.

    opened by joeb1415 20
  • Prepare for Marshmallow 3

    Prepare for Marshmallow 3

    https://github.com/plangrid/flask-rebar/blob/master/setup.py#L19 specifies marshmallow>=2.13. Right now this is picking up Marshmallow 2, but as soon as Marshmallow 3 final comes out, this will start picking up v3, which has several backwards-incompatible changes:

    https://marshmallow.readthedocs.io/en/3.0/changelog.html

    Are any updates necessary before then?

    v2.0 triaged swagger-generation validation 
    opened by twosigmajab 20
  • Create a more complete example

    Create a more complete example

    I just discovered flask-rebar and I think it could very well become the new standard for REST with flask. The documentation is very well made, but a more complete "real-life" example/template would help a lot to get started. I am creating one for my project that i could share, but I don't know all the best practices for the framework. Keep up the good work, cheers!

    wip 
    opened by Sytten 19
  • Fix bug that prevents returning `Flask.Response`s.

    Fix bug that prevents returning `Flask.Response`s.

    https://flask-rebar.readthedocs.io/en/latest/quickstart/basics.html#marshaling currently documents the following: "This means the if response_body_schema is None, the return value must be a return value that Flask supports, e.g. a string or a Flask.Response object.

    However, Flask-Rebar currently fails to interoperate properly with a Flask.Response object, as demonstrated by the included test. Without this bugfix to rebar.py, the included test fails with KeyError: 200. This is because the code currently passes rv to _unpack_view_func_return_value, which always returns a 200 status when rv is not a tuple, not realizing that a Flask.Response instance can get passed through as rv which can have a non-200 .status_code (and also custom .headers as well).

    opened by twosigmajab 13
  • Adding pep8 compliance test case?

    Adding pep8 compliance test case?

    Since this came up in the discussion, would it be a good idea to just jump into the deep and make add a test to ensure pep8 compliance going forward? Do people have a strong opinion about pep8 settings or should we just go with the vanilla version for now?

    Thoughts?

    opened by kazamatzuri 12
  • BP-763: Add support for multiple authenticators

    BP-763: Add support for multiple authenticators

    Corresponds to Issue #121

    • Extended deprecated_parameters decorator to allow a coercion function to convert between old and new styles of parameters.
    • Adds support for handlers to have multiple Authenticators.
    • Adds support for registries to have multiple Authenticators as their default authentication.
    • Should mark authenticator parameters as deprecated using the deprecation utils

    JIRA Tickets | --------------| BP-763|

    enhancement 
    opened by airstandley 11
  • Customize error returned in Flask-rebar

    Customize error returned in Flask-rebar

    Today we discussed creating our own class of errors because we wanted to have uniforms errors with a machine parsable type. Though we could add it just fine with the additional_data field, it is a bit painful to enforce it on every error thrown. The problem is that we can't currently modify the default errors that Flask-Rebar raise in the case of an invalid schema for example. I don't have a clear answer as to how we should handle this, but I figured I could ask if someone had an idea as to how we could do this.

    v2.0 triaged 
    opened by Sytten 11
  • Integrate marshmallow-objects

    Integrate marshmallow-objects

    One annoying thing about marshmallow is the fast that you get a dictionary instead of an object with attributes (I am bit jealous of pydantic). We started using marshmallow-objects in our project. Since we already define some custom schemas, it would be very valuable to either use this lib or make something similar. Especially since we are going toward a v2 with some breaking changes anyway.

    enhancement v2.0 triaged v2-breaking-change 
    opened by Sytten 10
  • sort required array

    sort required array

    I believe obj.fields.items(): does not consistently return objects in the same order. When committing the generated swagger.json file, it causes some redundant diffs in the required fields array

    e.g: https://github.com/plangrid/informant/pull/419

    opened by BrandonWeng 10
  • registry.add_supplemental_schemas

    registry.add_supplemental_schemas

    Add registry.add_supplemental_schemas function to export supplemental schemas to swagger, in addition to those used by the API handlers.

    In my app, I have a handler:

    @registry.handles(
        marshal_schema={201: FooSchema}
    )
    def create_foo():
        pass
    

    With:

    class FooSchema(Schema):
        template = fields.String()
        vars = fields.Dict()
    

    Elsewhere, I validate the vars against the template according to various template schemas:

    class Template_1_Schema(Schema):
        var_1 = fields.String()
        var_2 = fields.String()
    
    class Template_2_Schema(Schema):
        var_3 = fields.Boolean()
        var_4 = fields.Integer()
    

    I would like to export these template schemas in the swagger definition, so others can know what to expect to send to my handler in the vars dictionary param.

    This PR introduces a registry function add_supplemental_schemas to support this. The function can accept a single schema, or for convenience, a directory containing all the supplemental schemas.

    cc: @barakalon

    opened by joeb1415 10
  • Allow disabling OrderedDicts in generated swagger

    Allow disabling OrderedDicts in generated swagger

    If you try to yaml.dump a rebar-generated swagger object, you end up with yaml that looks like this:

    !!python/object/apply:collections.OrderedDict
    - - - consumes
        - [application/json]
      - - definitions
        - !!python/object/apply:collections.OrderedDict
          - - - Error
              - !!python/object/apply:collections.OrderedDict
                - - - properties
                    - !!python/object/apply:collections.OrderedDict
                      - - - errors
                          - !!python/object/apply:collections.OrderedDict
                            - - [type, object]
                        - - message
                          - !!python/object/apply:collections.OrderedDict
                            - - [type, string]
                  - - required
                    - [message]
                  - [title, Error]
                  - [type, object]
    ...
    

    It looks like this is due to the current behavior of unconditionally converting dicts to OrderedDicts.

    This PR allows passing order_dicts=False to swagger_generator.generate(...) to suppress the conversion.

    This may also be desired by users who want to avoid the extra work if they're using a Python implementation where dicts preserve insertion order already (e.g. PyPy or CPython >= 3.6).

    opened by twosigmajab 10
  • Flask signals not triggered on unhandled exceptions

    Flask signals not triggered on unhandled exceptions

    Flask has a signal got_request_exception intended to signify that an unhandled exception occurred in a handler. This signal is triggered by the default error handler, ref: https://github.com/pallets/flask/blob/0d8c8ba71bc6362e6ea9af08146dc97e1a0a8abc/src/flask/app.py#L1675. This means that to Flask, adding error handlers is equivalent to catching an exception; their documentation attempts to explains this, but it can be easy to miss the implication that the exception signal is not sent if a registered error handler matches the exception.

    Existing instrumentation/observability tooling uses this signal to detect when an unexpected error has occurred in a service.

    Rebar currently registers a generic Exception error handler, ref: https://github.com/plangrid/flask-rebar/blob/63922a17379a5b5a99c7422b7f7b881f8c08eb13/flask_rebar/rebar.py#L818-L827.

    This causes tools/plugins that rely on the got_request_exception signal to fail to work with a Rebar service.

    Rebar should either

    1. Switch the generic error handler to register for InternalServerError; meaning that the rebar handler would only run after the default flask handler had run and sent the got_request_exception signal.
    2. Send the got_request_exception signal as part of it's generic exception handling.
    bug error-handling 
    opened by airstandley 0
  • marshmallow-to-swagger RecursionError if schema has self references

    marshmallow-to-swagger RecursionError if schema has self references

    I am getting a RecursionError trying to generate swagger with this schema:

    class ZipStructureRowSchema(Schema):
        name = fields.String(required=True)
        sub_rows = fields.List(
            fields.Nested("ZipStructureRowSchema"), load_from="subRows", dump_to="subRows"
        )
    
    
    class ZipPreviewResponseSchema(Schema):
        preview = fields.List(fields.Nested(ZipStructureRowSchema))
    

    As far as I can tell, this is valid according to Marshmallow. Thank you!

    opened by AutodeskAbe 1
  • AttributeError: 'AuthenticatorConverterRegistry' object has no attribute 'get_security_schemes_legacy'

    AttributeError: 'AuthenticatorConverterRegistry' object has no attribute 'get_security_schemes_legacy'

    I have a custom authenticator that works, but I am trying to get the swagger docs to work as well but I keep getting the above exception.

    Here is my custom authenticator:

    class CustomAuthenticator(authenticators.Authenticator):
        def authenticate(self):
            if current_user and current_user.is_authenticated:
                pass
            elif current_app.config["AUTH_OFF"]:
                try:
                    login_user(default_user)
                except AttributeError:
                    raise ValueError("Trying to log into user that doesn't exist!")
    

    This part I took directly from the flask_rebar docs:

    class SwaggerAuthConverter(AuthenticatorConverter):
        AUTHENTICATOR_TYPE = CustomAuthenticator
    
        def get_security_schemes(self, obj, context):
            return {
                obj.name: {sw.type_: sw.api_key, sw.in_: sw.header, sw.name: obj.header}
            }
    
        def get_security_requirements(self, obj, context):
            return [{obj.name: []}]
    
    
    custom_auth_registry = AuthenticatorConverterRegistry()
    custom_auth_registry.register_type(SwaggerAuthConverter())
    

    Finally, this is where I set up the app:

    rebar = Rebar()
    swagger_generator = SwaggerV2Generator(authenticator_converter_registry=custom_auth_registry)
    registry = rebar.create_handler_registry(swagger_generator=swagger_generator)
    registry.set_default_authenticator(CustomAuthenticator)
    

    Here is the full backtrace:

    127.0.0.1 - - [04/Nov/2021 09:33:13] "GET /swagger HTTP/1.0" 500 -
    Traceback (most recent call last):
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 2464, in __call__
        return self.wsgi_app(environ, start_response)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 2450, in wsgi_app
        response = self.handle_exception(e)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 1867, in handle_exception
        reraise(exc_type, exc_value, tb)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
        raise value
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 2447, in wsgi_app
        response = self.full_dispatch_request()
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 1952, in full_dispatch_request
        rv = self.handle_user_exception(e)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 1822, in handle_user_exception
        return handler(e)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask_rebar/rebar.py", line 831, in handle_generic_error
        raise error
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 1950, in full_dispatch_request
        rv = self.dispatch_request()
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask/app.py", line 1936, in dispatch_request
        return self.view_functions[rule.endpoint](**req.view_args)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask_rebar/rebar.py", line 612, in get_swagger
        registry=self, host=request.host_url.rstrip("/")
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask_rebar/swagger_generation/swagger_generator_v2.py", line 100, in generate_swagger
        return self.generate(registry=registry, host=host)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask_rebar/swagger_generation/swagger_generator_v2.py", line 131, in generate
        self.authenticator_converter.get_security_schemes(authenticator)
      File "/home/canopy/canopy/venv/lib/python3.6/site-packages/flask_rebar/swagger_generation/authenticator_to_swagger.py", line 168, in get_security_schemes
        return self.get_security_schemes_legacy(registry=authenticator)
    AttributeError: 'AuthenticatorConverterRegistry' object has no attribute 'get_security_schemes_legacy'
    
    bug 
    opened by mattcarp12 5
  • Feature: Add SchemaOpts to opt-in to validation

    Feature: Add SchemaOpts to opt-in to validation

    Currently (technically, when 2.0.1 is merged and released) it is possible to opt in to response validation at the schema level by inclusion of `RequireOnDumpMixin. While this works, a more natural fit would be to allow this to be directly specified in the schema definition itself by including a "custom class Meta option" as described here: https://marshmallow.readthedocs.io/en/stable/extending.html

    opened by RookieRick 0
  • Rebar can generate invalid OpenAPI specs: non-unique operationId's

    Rebar can generate invalid OpenAPI specs: non-unique operationId's

    https://swagger.io/docs/specification/paths-and-operations/#operationid

    operationId is an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API.

    This is important because things downstream of the spec (such as generated clients) may use the operationId (e.g. to code-generate methods) in such a way that depends on unique operationIds.

    However, it looks like Rebar's OpenAPI spec generation uses the function name to generate operationIds, which need not be unique:

    # my_rebar_registry.py 
    from flask_rebar import Rebar
    
    rebar = Rebar()
    registry = rebar.create_handler_registry(prefix="/api")
    
    # handlers1.py 
    from my_rebar_registry import registry
    
    @registry.handles(
        rule="/foo",
    )
    def foo():
        return "foo"
    
    # handlers2.py 
    from my_rebar_registry import registry
    
    @registry.handles(
        rule="/bar",
    )
    def foo():
        return "bar"
    
    # my_api_spec.py
    import json
    
    from my_rebar_registry import registry
    import handlers1
    import handlers2
    
    if __name__ == "__main__":
        print(json.dumps(registry.swagger_generator.generate_swagger(registry), indent=2))
    
    > python -m my_api_spec | grep operationId  # thither be duplicates
            "operationId": "foo",
            "operationId": "foo",
    
    (Click for full spec output)
    {
      "consumes": [
        "application/json"
      ],
      "definitions": {
        "Error": {
          "properties": {
            "errors": {
              "type": "object"
            },
            "message": {
              "type": "string"
            }
          },
          "required": [
            "message"
          ],
          "title": "Error",
          "type": "object"
        }
      },
      "host": "localhost",
      "info": {
        "description": "",
        "title": "My API",
        "version": "1.0.0"
      },
      "paths": {
        "/api/bar": {
          "get": {
            "operationId": "foo",
            "responses": {
              "default": {
                "description": "Error",
                "schema": {
                  "$ref": "#/definitions/Error"
                }
              }
            }
          }
        },
        "/api/foo": {
          "get": {
            "operationId": "foo",
            "responses": {
              "default": {
                "description": "Error",
                "schema": {
                  "$ref": "#/definitions/Error"
                }
              }
            }
          }
        }
      },
      "produces": [
        "application/json"
      ],
      "schemes": [],
      "securityDefinitions": {},
      "swagger": "2.0"
    }
    

    This may not be a terribly big deal in practice, since the moment you try to use such a registry with a Flask app, Flask's own duplication checking (of endpoint names) prevents you from doing so:

    # app.py 
    from flask import Flask
    
    from my_rebar_registry import rebar
    import handlers1
    import handlers2
    
    app = Flask("app")
    rebar.init_app(app)  # kaboom
    
    > python -m app
    Traceback (most recent call last):
      File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "/usr/local/Cellar/[email protected]/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/runpy.py", line 87, in _run_code
        exec(code, run_globals)
      File "/Users/jab/tmp/rebarv2/app.py", line 9, in <module>
        rebar.init_app(app)  # kaboom
      File "/Users/jab/tmp/rebarv2/.venv/lib/python3.9/site-packages/flask_rebar/rebar.py", line 784, in init_app
        registry.register(app=app)
      File "/Users/jab/tmp/rebarv2/.venv/lib/python3.9/site-packages/flask_rebar/rebar.py", line 554, in register
        self._register_routes(app=app)
      File "/Users/jab/tmp/rebarv2/.venv/lib/python3.9/site-packages/flask_rebar/rebar.py", line 576, in _register_routes
        app.add_url_rule(
      File "/Users/jab/tmp/rebarv2/.venv/lib/python3.9/site-packages/flask/app.py", line 98, in wrapper_func
        return f(self, *args, **kwargs)
      File "/Users/jab/tmp/rebarv2/.venv/lib/python3.9/site-packages/flask/app.py", line 1282, in add_url_rule
        raise AssertionError(
    AssertionError: View function mapping is overwriting an existing endpoint function: api.foo
    

    However, it still seems preferable to make Rebar's OpenAPI spec generation smart enough to not generate invalid specs in this way. I think Rebar should instead raise an error telling the user to provide a unique function name to avoid generating duplicate operationIds, or perhaps better yet, the @registry.handles() decorators (and so forth) could accept an operation_id param that allows the user to explicitly set the associated operationId that gets generated into the spec, making the operationId no longer tied to the Python function name 1-to-1. (A non-option, IMO, would be for Rebar to silently append some number to deduplicate what would otherwise be a duplicate operationId, which I've seen some Swagger tooling do, and it causes all kinds of madness.)

    opened by jab 0
Releases(v2.2.1)
Owner
PlanGrid
PlanGrid
level2-data-annotation_cv-level2-cv-15 created by GitHub Classroom

[AI Tech 3기 Level2 P Stage] 글자 검출 대회 팀원 소개 김규리_T3016 박정현_T3094 석진혁_T3109 손정균_T3111 이현진_T3174 임종현_T3182 Overview OCR (Optimal Character Recognition) 기술

6 Jun 10, 2022
API spec validator and OpenAPI document generator for Python web frameworks.

API spec validator and OpenAPI document generator for Python web frameworks.

1001001 249 Dec 22, 2022
sphinx builder that outputs markdown files.

sphinx-markdown-builder sphinx builder that outputs markdown files Please ★ this repo if you found it useful ★ ★ ★ If you want frontmatter support ple

Clay Risser 144 Jan 06, 2023
This tutorial will guide you through the process of self-hosting Polygon

Hosting guide This tutorial will guide you through the process of self-hosting Polygon Before starting Make sure you have the following tools installe

Polygon 2 Jan 31, 2022
In this Github repository I will share my freqtrade files with you. I want to help people with this repository who don't know Freqtrade so much yet.

My Freqtrade stuff In this Github repository I will share my freqtrade files with you. I want to help people with this repository who don't know Freqt

Simon Kebekus 104 Dec 31, 2022
A collection of simple python mini projects to enhance your python skills

A collection of simple python mini projects to enhance your python skills

PYTHON WORLD 12.1k Jan 05, 2023
Material for the ros2 crash course

Material for the ros2 crash course

Emmanuel Dean 1 Jan 22, 2022
Showing potential issues with merge strategies

Showing potential issues with merge strategies Context There are two branches in this repo: main and a feature branch feat/inverting-method (not the b

Rubén 2 Dec 20, 2021
A collection and example code of every topic you need to know about in the basics of Python.

The Python Beginners Guide: Master The Python Basics Tonight This guide is a collection of every topic you need to know about in the basics of Python.

Ahmed Baari 1 Dec 19, 2021
Explorative Data Analysis Guidelines

Explorative Data Analysis Get data into a usable format! Find out if the following predictive modeling phase will be successful! Combine everything in

Florian Rohrer 18 Dec 26, 2022
Run `black` on python code blocks in documentation files

blacken-docs Run black on python code blocks in documentation files. install pip install blacken-docs usage blacken-docs provides a single executable

Anthony Sottile 460 Dec 23, 2022
Clases y ejercicios del curso de python diactodo por la UNSAM

Programación en Python En el marco del proyecto de Inteligencia Artificial Interdisciplinaria, la Escuela de Ciencia y Tecnología de la UNSAM vuelve a

Maximiliano Villalva 3 Jan 06, 2022
Automated Integration Testing and Live Documentation for your API

Automated Integration Testing and Live Documentation for your API

ScanAPI 1.3k Dec 30, 2022
Openapi-core is a Python library that adds client-side and server-side support for the OpenAPI Specification v3.

Openapi-core is a Python library that adds client-side and server-side support for the OpenAPI Specification v3.

A 186 Dec 30, 2022
Materi workshop "Light up your Python!" Himpunan Mahasiswa Sistem Informasi Fakultas Ilmu Komputer Universitas Singaperbangsa Karawang, 4 September 2021 (Online via Zoom).

Workshop Python UNSIKA 2021 Materi workshop "Light up your Python!" Himpunan Mahasiswa Sistem Informasi Fakultas Ilmu Komputer Universitas Singaperban

Eka Putra 20 Mar 24, 2022
Beautiful static documentation generator for OpenAPI/Swagger 2.0

Spectacle The gentleman at REST Spectacle generates beautiful static HTML5 documentation from OpenAPI/Swagger 2.0 API specifications. The goal of Spec

Sourcey 1.3k Dec 13, 2022
Parser manager for parsing DOC, DOCX, PDF or HTML files

Parser manager Description Parser gets PDF, DOC, DOCX or HTML file via API and saves parsed data to the database. Implemented in Ruby 3.0.1 using Acti

Эдем 4 Dec 04, 2021
Resource hub for Obsidian resources.

Obsidian Community Vault Welcome! This is an experimental vault that is maintained by the Obsidian community. For best results we recommend downloadin

Obsidian Community 320 Jan 02, 2023
Build documentation in multiple repos into one site.

mkdocs-multirepo-plugin Build documentation in multiple repos into one site. Setup Install plugin using pip: pip install git+https://github.com/jdoiro

Joseph Doiron 47 Dec 28, 2022
My solutions to the Advent of Code 2021 problems in Go and Python 🎄

🎄 Advent of Code 2021 🎄 Summary Advent of Code is an annual Advent calendar of programming puzzles. This year I am doing it in Go and Python. Runnin

Orfeas Antoniou 16 Jun 16, 2022