diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index cd274679..955a1354 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -40,7 +40,7 @@ You can use the Docker image like below: ``` # SQLite3 -$ docker run -it --rm -p 8080:8080 -v `PWD`:/app -w /app optuna-dashboard sqlite:///db.sqlite3 +$ docker run -it --rm -p 8080:8080 -v `PWD`:/app -w /app sqlite:///db.sqlite3 ``` ### Running dashboard server diff --git a/Dockerfile b/Dockerfile index 392e1dd9..bc5b4150 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ FROM python:3.8-buster AS python-builder WORKDIR /usr/src RUN pip install --upgrade pip setuptools -RUN pip install --progress-bar off PyMySQL[rsa] psycopg2-binary +RUN pip install --progress-bar off PyMySQL[rsa] psycopg2-binary gunicorn ADD ./setup.cfg /usr/src/setup.cfg ADD ./setup.py /usr/src/setup.py @@ -32,5 +32,5 @@ RUN mkdir /app WORKDIR /app EXPOSE 8080 -ENTRYPOINT ["optuna-dashboard", "--port", "8080", "--host", "0.0.0.0"] +ENTRYPOINT ["optuna-dashboard", "--port", "8080", "--host", "0.0.0.0", "--server", "gunicorn"] diff --git a/README.md b/README.md index 8f4865cf..d68e9556 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,23 @@ Listening on http://localhost:8080/ Hit Ctrl-C to quit. ``` +
+ +Running optuna-dashboard with Gunicorn + +optuna-dashboard uses [wsgiref](https://docs.python.org/3/library/wsgiref.html) module +which is provided as a Python standard library. But it has not been reviewed for security +issues, so not suitable for the production use. You can run optuna-dashboard with Gunicorn +more secure and/or more fast. + +```console +$ pip install gunicorn +$ optuna-dashboard sqlite:///db.sqlite3 --server gunicorn +``` + +
+ +
More command line options @@ -43,15 +60,32 @@ positional arguments: storage DB URL (e.g. sqlite:///example.db) optional arguments: - -h, --help show this help message and exit - --port PORT port number (default: 8080) - --host HOST hostname (default: 127.0.0.1) - --version, -v show program's version number and exit - --quiet, -q quiet + -h, --help show this help message and exit + --port PORT port number (default: 8080) + --host HOST hostname (default: 127.0.0.1) + --server {wsgiref,gunicorn} + server (default: wsgiref) + --version, -v show program's version number and exit + --quiet, -q quiet ```
+
+ +Python Interface + +**`run_server(storage: Union[str, BaseStorage], host: str = 'localhost', port: int = 8080) -> NoReturn`** + +Start running optuna-dashboard and blocks until the server terminates. +This function uses wsgiref module which is not intended for the production use. + +**`wsgi(storage: Union[str, BaseStorage]) -> WSGIApplication`** + +This function exposes WSGI interface for people who want to run on the +production-class WSGI servers like Gunicorn or uWSGI. + +
## Using an official Docker image @@ -102,20 +136,6 @@ You can walk-through trials by filtering and sorting. ![optuna-dashboard-trials-datagrid](https://user-images.githubusercontent.com/5564044/114265667-20d57d00-9a2d-11eb-8b9c-69541c9b4a28.gif) -## Python Interface - -### `run_server(storage: Union[str, BaseStorage], host: str = 'localhost', port: int = 8080) -> NoReturn` - -Start running optuna-dashboard and blocks until the server terminates. -This function uses wsgiref module which is not intended for the production -use. If you want to run optuna-dashboard more secure and/or more fast, -please use WSGI server like Gunicorn or uWSGI via `wsgi()` function. - -### `wsgi(storage: Union[str, BaseStorage]) -> WSGIApplication` - -This function exposes WSGI interface for people who want to run on the -production-class WSGI servers like Gunicorn or uWSGI. - ## Submitting patches If you want to contribute, please check [Developers Guide](./DEVELOPMENT.md). diff --git a/optuna_dashboard/cli.py b/optuna_dashboard/cli.py index 146b3218..87f43dfe 100644 --- a/optuna_dashboard/cli.py +++ b/optuna_dashboard/cli.py @@ -1,6 +1,8 @@ import argparse import os +from typing import NoReturn +from bottle import Bottle from bottle import run from optuna.storages import BaseStorage from optuna.storages import RDBStorage @@ -11,9 +13,38 @@ from .app import create_app AUTO_RELOAD = os.environ.get("OPTUNA_DASHBOARD_AUTO_RELOAD") == "1" +SERVER_CHOICES = ["wsgiref", "gunicorn"] -def main() -> None: +def run_wsgiref(app: Bottle, host: str, port: int, quiet: bool) -> NoReturn: # type: ignore + run( + app, + host=host, + port=port, + server="wsgiref", + quiet=quiet, + reloader=AUTO_RELOAD, + ) + + +def run_gunicorn(app: Bottle, host: str, port: int, quiet: bool) -> NoReturn: # type: ignore + # See https://docs.gunicorn.org/en/latest/custom.html + + from gunicorn.app.base import BaseApplication + + class _Application(BaseApplication): + def load_config(self) -> None: + self.cfg.set("bind", f"{host}:{port}") + if quiet: + self.cfg.set("loglevel", "error") + + def load(self) -> Bottle: + return app + + _Application().run() + + +def main() -> NoReturn: parser = argparse.ArgumentParser(description="Real-time dashboard for Optuna.") parser.add_argument("storage", help="DB URL (e.g. sqlite:///example.db)", type=str) parser.add_argument( @@ -22,6 +53,12 @@ def main() -> None: parser.add_argument( "--host", help="hostname (default: %(default)s)", default="127.0.0.1" ) + parser.add_argument( + "--server", + help="server (default: %(default)s)", + default="wsgiref", + choices=SERVER_CHOICES, + ) parser.add_argument("--version", "-v", action="version", version=__version__) parser.add_argument("--quiet", "-q", help="quiet", action="store_true") args = parser.parse_args() @@ -33,7 +70,12 @@ def main() -> None: storage = RDBStorage(args.storage) app = create_app(storage) - run(app, host=args.host, port=args.port, quiet=args.quiet, reloader=AUTO_RELOAD) + if args.server == "wsgiref": + run_wsgiref(app, args.host, args.port, args.quiet) + elif args.server == "gunicorn": + run_gunicorn(app, args.host, args.port, args.quiet) + else: + raise Exception("must not reach here") if __name__ == "__main__":