debug Flask server inside Jupyter Notebook

The trick is to run the Flask server in a separate thread. This code allows registering data providers. The key features are

  • Find a free port for the server. If you run multiple instances of the server in different notebooks they would compete for the same port.

  • The register_data function returns the URL of the server so you can use it for whatever you need.

  • The server is started on-demand (when the first data provider is registered)

  • Note: I added the @cross_origin() decorator from the flask-cors package. Else you cannot call the API form within the notebook.

  • Note: there is no way to stop the server in this code...

  • Note: The code uses typing and python 3.

  • Note: There is no good error handling at the moment

import socket
import threading
import uuid
from typing import Any, Callable, cast, Optional

from flask import Flask, abort, jsonify
from flask_cors import cross_origin
from werkzeug.serving import run_simple

app = Flask('DataServer')


@app.route('/data/<id>')
@cross_origin()
def data(id: str) -> Any:
    func = _data.get(id)
    if not func:
        abort(400)
    return jsonify(func())


_data = {}

_port: int = 0


def register_data(f: Callable[[], Any], id: Optional[str] = None) -> str:
    """Sets a callback for data and returns a URL"""
    _start_sever()
    id = id or str(uuid.uuid4())
    _data[id] = f
    return f'http://localhost:{_port}/data/{id}'


def _init_port() -> int:
    """Creates a random free port."""
    # see https://stackoverflow.com/a/5089963/2297345
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 0))

    port = sock.getsockname()[1]
    sock.close()
    return cast(int, port)


def _start_sever() -> None:
    """Starts a flask server in the background."""
    global _port
    if _port:
        return
    _port = _init_port()
    thread = threading.Thread(target=lambda: run_simple('localhost', _port, app))
    thread.start()

I installed Jupyter and Flask and your original code works.


The flask.Flask object is a WSGI application, not a server. Flask uses Werkzeug's development server as a WSGI server when you call python -m flask run in your shell. It creates a new WSGI server and then passes your app as paremeter to werkzeug.serving.run_simple. Maybe you can try doing that manually:

from werkzeug.wrappers import Request, Response
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 9000, app)

Flask.run() calls run_simple() internally, so there should be no difference here.