Skip to content

Instantly share code, notes, and snippets.

@austinjp
Last active January 26, 2020 02:19
Show Gist options
  • Select an option

  • Save austinjp/c0c3ed361fd54ed4faf0065eb40502eb to your computer and use it in GitHub Desktop.

Select an option

Save austinjp/c0c3ed361fd54ed4faf0065eb40502eb to your computer and use it in GitHub Desktop.
Monkey-patch flask-restplus

How to use this:

# Make a suitable directory
mkdir whatever && cd whatever

# Set up virtual env
virtualenv --python=python3 venv
source venv/bin/activate
pip install flask flask_restplus

# Download app.py
wget 'https://gist.githubusercontent.com/austinjp/c0c3ed361fd54ed4faf0065eb40502eb/raw/app.py'

# Smoke-test:
python ./app.py # Should produce no output

# Serve on localhost:
FLASK_DEBUG=1 FLASK_ENV=development FLASK_APP=app.py \
    flask run --port 4000 --debugger

Visit http://127.0.0.1:4000/api/ and you should see a Swagger page complaining that there are "No operations defined in spec!" which is correct. Check the console wherein you ran Flask, and you should see something like:

 * Serving Flask app "app.py" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on https://127.0.0.1:4000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 123-456-789
127.0.0.1 - - [15/Dec/2019 12:53:34] "GET /api/swagger.json HTTP/1.1" 200 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /api/ HTTP/1.1" 200 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /swaggerui/droid-sans.css HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /swaggerui/swagger-ui.css HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /swaggerui/swagger-ui-bundle.js HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /swaggerui/swagger-ui-standalone-preset.js HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:53:35] "GET /api/swagger.json HTTP/1.1" 200 -

Note that swagger.json is located at /api 😃

To test over SSL, do the following. Note, this is not appropriate for production, just for testing on localhost.

pip install pyopenssl
FLASK_DEBUG=1 FLASK_ENV=development FLASK_APP=app.py \
    flask run --port 4000 --debugger --cert=adhoc

Visit https://127.0.0.1:4000/api/ (don't forget the trailing slash) and you will be warned about accepting the certificate. This is fine for testing. Accept, and you'll see the same Swagger page. In the console you should see something like:

 * Serving Flask app "app.py" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on https://127.0.0.1:4000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 123-456-789
127.0.0.1 - - [15/Dec/2019 12:36:15] "GET /api/ HTTP/1.1" 200 -
127.0.0.1 - - [15/Dec/2019 12:36:15] "GET /swaggerui/droid-sans.css HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:36:16] "GET /swaggerui/swagger-ui-standalone-preset.js HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:36:16] "GET /swaggerui/swagger-ui-bundle.js HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:36:16] "GET /swaggerui/swagger-ui.css HTTP/1.1" 304 -
127.0.0.1 - - [15/Dec/2019 12:36:16] "GET /api/swagger.json HTTP/1.1" 200 -

Again, note swagger.json is found at /api.

import logging, sys
from flask import Flask, abort, current_app, url_for, Blueprint, jsonify
from flask_restplus import Api
class Config(object):
LOG_LEVEL = logging.WARNING
NAME = "My App"
API_URL = "/api"
def create_app():
cfg = Config()
app = Flask(cfg.NAME)
configure_app(app,cfg)
monkey_patch(app,cfg)
return app
def configure_app(app,cfg):
app.config.from_object(cfg)
@app.before_first_request
def setup_logging():
app.logger.setLevel(cfg.LOG_LEVEL)
def return_error(e):
# I return a custom JSON object here. But do what you want.
return(jsonify({"errors": [{"status": 500, "object": str(e.__repr__) }] }))
def monkey_patch(app,cfg):
# Apply several monkey patches to Flask REST Plus.
# Ensure the specs endpoint is not treated as an "external" resource.
@property
def fix_specs_url(self):
return url_for(self.endpoint('specs'), _external=False)
Api.specs_url = fix_specs_url
# Ensure exceptions are picked up, API responses are produced as expected, and
# exceptions are written to the log. Logging code taken from flask_restplus error handler:
# https://flask-restplus.readthedocs.io/en/stable/_modules/flask_restplus/api.html#Api.handle_error
def fix_error_router(self, original_handler, e):
exc_info = sys.exc_info()
if exc_info[1] is None:
exc_info = None
if (exc_info):
current_app.log_exception(exc_info)
else:
current_app.logger.error(str(e))
return return_error(e)
Api.error_router = fix_error_router
blueprint = Blueprint('api', cfg.NAME, url_prefix=cfg.API_URL)
api = Api(blueprint)
app.register_blueprint(blueprint)
app = create_app()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment