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
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
A hack to run custom shell commands when building documentation on Read the Docs.

readthedocs-custom-steps A hack to run custom steps when building documentation on Read the Docs. Important: This module should not be installed outsi

Niklas Rosenstein 5 Feb 22, 2022
The OpenAPI Specification Repository

The OpenAPI Specification The OpenAPI Specification is a community-driven open specification within the OpenAPI Initiative, a Linux Foundation Collabo

OpenAPI Initiative 25.5k Dec 29, 2022
Poetry plugin to export the dependencies to various formats

Poetry export plugin This package is a plugin that allows the export of locked packages to various formats. Note: For now, only the requirements.txt f

Poetry 90 Jan 05, 2023
Collection of Summer 2022 tech internships!

Collection of Summer 2022 tech internships!

Pitt Computer Science Club (CSC) 15.6k Jan 03, 2023
DataRisk Detection Learning Resources

DataRisk Detection Learning Resources Data security: Based on the "data-centric security system" position, it generally refers to the entire security

Liao Wenzhe 59 Dec 05, 2022
Sane and flexible OpenAPI 3 schema generation for Django REST framework.

drf-spectacular Sane and flexible OpenAPI 3.0 schema generation for Django REST framework. This project has 3 goals: Extract as much schema informatio

T. Franzel 1.4k Jan 08, 2023
EasyModerationKit is an open-source framework designed to moderate and filter inappropriate content.

EasyModerationKit is a public transparency statement. It declares any repositories and legalities used in the EasyModeration system. It allows for implementing EasyModeration into an advanced charact

Aarav 1 Jan 16, 2022
Proyecto - Desgaste y rendimiento de empleados de IBM HR Analytics

Acceder al código desde Google Colab para poder ver de manera adecuada todas las visualizaciones y poder interactuar con ellas. Links de acceso: Noteb

1 Jan 31, 2022
Autolookup GUI Plugin for Plover

Word Tray for Plover Word Tray is a GUI plugin that automatically looks up efficient outlines for words that start with the current input, much like a

Kathy 3 Jun 08, 2022
Repository for learning Python (Python Tutorial)

Repository for learning Python (Python Tutorial) Languages and Tools 🧰 Overview 📑 Repository for learning Python (Python Tutorial) Languages and Too

Swiftman 2 Aug 22, 2022
freeCodeCamp Scientific Computing with Python Project for Certification.

Polygon_Area_Calculator freeCodeCamp Python Project freeCodeCamp Scientific Computing with Python Project for Certification. In this project you will

Rajdeep Mondal 1 Dec 23, 2021
Test utility for validating OpenAPI documentation

DRF OpenAPI Tester This is a test utility to validate DRF Test Responses against OpenAPI 2 and 3 schema. It has built-in support for: OpenAPI 2/3 yaml

snok 106 Jan 05, 2023
NetBox plugin that stores configuration diffs and checks templates compliance

Config Officer - NetBox plugin NetBox plugin that deals with Cisco device configuration (collects running config from Cisco devices, indicates config

77 Dec 21, 2022
Loudchecker - Python script to check files for earrape

loudchecker python script to check files for earrape automatically installs depe

1 Jan 22, 2022
Version bêta d'un système pour suivre les prix des livres chez Books to Scrape,

Version bêta d'un système pour suivre les prix des livres chez Books to Scrape, un revendeur de livres en ligne. En pratique, dans cette version bêta, le programme n'effectuera pas une véritable surv

Mouhamed Dia 1 Jan 06, 2022
The project that powers MDN.

Kuma Kuma is the platform that powers MDN (developer.mozilla.org) Development Code: https://github.com/mdn/kuma Issues: P1 Bugs (to be fixed ASAP) P2

MDN Web Docs 1.9k Dec 26, 2022
Explain yourself! Interrogate a codebase for docstring coverage.

interrogate: explain yourself Interrogate a codebase for docstring coverage. Why Do I Need This? interrogate checks your code base for missing docstri

Lynn Root 435 Dec 29, 2022
Source Code for 'Practical Python Projects' (video) by Sunil Gupta

Apress Source Code This repository accompanies %Practical Python Projects by Sunil Gupta (Apress, 2021). Download the files as a zip using the green b

Apress 2 Jun 01, 2022
A module filled with many useful functions and modules in various subjects.

Usefulpy Check out the Usefulpy site Usefulpy site is not always up to date Download and Import download and install with with pip download usefulpyth

Austin Garcia 1 Dec 28, 2021