debug Flask server inside Jupyter Notebook
Question:
I want to debug small flask server inside jupyter notebook for demo.
I created virtualenv on latest Ubuntu and Python2 (on Mac with Python3 this error occurs as well), pip install flask jupyter.
However, when I create a cell with helloworld script it does not run inside notebook.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True,port=1234)
File
“/home/***/test/local/lib/python2.7/site-packages/ipykernel/kernelapp.py”,
line 177, in _bind_socket
s.bind(“tcp://%s:%i” % (self.ip, port)) File “zmq/backend/cython/socket.pyx”, line 495, in
zmq.backend.cython.socket.Socket.bind
(zmq/backend/cython/socket.c:5653) File
“zmq/backend/cython/checkrc.pxd”, line 25, in
zmq.backend.cython.checkrc._check_rc
(zmq/backend/cython/socket.c:10014)
raise ZMQError(errno) ZMQError: Address already in use
NB – I change the port number after each time it fails.
Sure, it runs as a standalone script.
update without (debug=True) it’s ok.
Answers:
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.
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()
Although this question was asked long ago, I come up with another suggestion:
The following code is adapted from how PyCharm starts a Flask console.
import sys
from flask.cli import ScriptInfo
app = None
locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())
print("Python %s on %snApp: %s [%s]nInstance: %s" % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))
Now you can access app
and use everything described in the Flask docs on working with the shell
I want to debug small flask server inside jupyter notebook for demo.
I created virtualenv on latest Ubuntu and Python2 (on Mac with Python3 this error occurs as well), pip install flask jupyter.
However, when I create a cell with helloworld script it does not run inside notebook.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True,port=1234)
File
“/home/***/test/local/lib/python2.7/site-packages/ipykernel/kernelapp.py”,
line 177, in _bind_socket
s.bind(“tcp://%s:%i” % (self.ip, port)) File “zmq/backend/cython/socket.pyx”, line 495, in
zmq.backend.cython.socket.Socket.bind
(zmq/backend/cython/socket.c:5653) File
“zmq/backend/cython/checkrc.pxd”, line 25, in
zmq.backend.cython.checkrc._check_rc
(zmq/backend/cython/socket.c:10014)
raise ZMQError(errno) ZMQError: Address already in use
NB – I change the port number after each time it fails.
Sure, it runs as a standalone script.
update without (debug=True) it’s ok.
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.
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 theflask-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()
Although this question was asked long ago, I come up with another suggestion:
The following code is adapted from how PyCharm starts a Flask console.
import sys
from flask.cli import ScriptInfo
app = None
locals().update(ScriptInfo(create_app=None).load_app().make_shell_context())
print("Python %s on %snApp: %s [%s]nInstance: %s" % (sys.version, sys.platform, app.import_name, app.env, app.instance_path))
Now you can access app
and use everything described in the Flask docs on working with the shell