Merge pull request #167 from optuna/stable-interface

Provide stable Python API
This commit is contained in:
Masashi Shibata
2022-02-17 18:10:56 +09:00
committed by GitHub
9 changed files with 73 additions and 21 deletions
+13
View File
@@ -102,6 +102,19 @@ 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
+1 -1
View File
@@ -6,7 +6,7 @@ import optuna
def objective(trial):
x = trial.suggest_float("x", -100, 100)
y = trial.suggest_categorical("y", [-1, 0, 1])
return x ** 2 + y
return x**2 + y
if __name__ == "__main__":
+5
View File
@@ -0,0 +1,5 @@
from .app import run_server # noqa
from .app import wsgi # noqa
__version__ = "0.5.0"
+36
View File
@@ -6,11 +6,13 @@ import logging
import os
import threading
import traceback
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import List
from typing import NoReturn
from typing import Optional
from typing import TypeVar
from typing import Union
@@ -20,10 +22,13 @@ from bottle import Bottle
from bottle import redirect
from bottle import request
from bottle import response
from bottle import run
from bottle import static_file
import optuna
from optuna.exceptions import DuplicatedStudyError
from optuna.storages import BaseStorage
from optuna.storages import RDBStorage
from optuna.storages import RedisStorage
from optuna.study import Study
from optuna.study import StudyDirection
from optuna.study import StudySummary
@@ -34,6 +39,9 @@ from . import serializer
from .search_space import get_search_space
if typing.TYPE_CHECKING:
from _typeshed.wsgi import WSGIApplication
BottleViewReturn = Union[str, bytes, Dict[str, Any], BaseResponse]
BottleView = TypeVar("BottleView", bound=Callable[..., BottleViewReturn])
@@ -274,3 +282,31 @@ def create_app(storage: BaseStorage) -> Bottle:
return static_file(filename, root=STATIC_DIR)
return app
def get_storage(storage: Union[str, BaseStorage]) -> BaseStorage:
if isinstance(storage, str):
if storage.startswith("redis"):
return RedisStorage(storage)
else:
return RDBStorage(storage)
return storage
def run_server( # type: ignore
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.
"""
app = create_app(get_storage(storage))
run(app, host=host, port=port)
def 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.
"""
return create_app(get_storage(storage))
+1 -1
View File
@@ -6,8 +6,8 @@ from optuna.storages import BaseStorage
from optuna.storages import RDBStorage
from optuna.storages import RedisStorage
from . import __version__
from .app import create_app
from .version import __version__
AUTO_RELOAD = os.environ.get("OPTUNA_DASHBOARD_AUTO_RELOAD") == "1"
-1
View File
@@ -1 +0,0 @@
__version__ = "0.5.0"
+10 -11
View File
@@ -1,17 +1,16 @@
import io
from typing import Any
from typing import Callable
import typing
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from bottle import Bottle
WSGIEnv = Dict[str, Any] # Cannot use TypedDict because of 'HTTP_' variables
StartResponse = Callable[[str, List[Tuple[str, str]]], None]
WSGIApp = Callable[[WSGIEnv, StartResponse], Iterable[bytes]]
if typing.TYPE_CHECKING:
from _typeshed.wsgi import WSGIEnvironment
def create_wsgi_env(
@@ -21,7 +20,7 @@ def create_wsgi_env(
body: bytes,
queries: Dict[str, str],
headers: Dict[str, str],
) -> WSGIEnv:
) -> "WSGIEnvironment":
# 'key1=value1&key2=value2'
query_string = "&".join([f"{k}={v}" for k, v in queries.items()])
@@ -48,7 +47,7 @@ def create_wsgi_env(
def send_request(
app: WSGIApp,
app: Bottle,
path: str,
method: str,
body: Union[str, bytes] = b"",
@@ -68,10 +67,10 @@ def send_request(
headers = headers or {}
queries = queries or {}
env = create_wsgi_env(path, method, content_type, bytes_body, queries, headers)
body = b""
response_body = b""
iterable_body = app(env, start_response)
for b in iterable_body:
body += b
response_body += b
status_code = int(status.split()[0])
return status_code, response_headers, body
return status_code, response_headers, response_body
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = optuna-dashboard
version = attr: optuna_dashboard.version.__version__
version = attr: optuna_dashboard.__version__
url = https://github.com/optuna/optuna-dashboard
author = Masashi Shibata
+6 -6
View File
@@ -12,7 +12,7 @@ import optuna
from pyppeteer import launch
from pyppeteer.page import Page
from optuna_dashboard.app import create_app
from optuna_dashboard import wsgi
parser = argparse.ArgumentParser()
@@ -94,7 +94,7 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage:
def objective_multi(trial: optuna.Trial) -> Tuple[float, float]:
x = trial.suggest_float("x", 0, 5)
y = trial.suggest_float("y", 0, 3)
v0 = 4 * x ** 2 + 4 * y ** 2
v0 = 4 * x**2 + 4 * y**2
v1 = (x - 5) ** 2 + (y - 5) ** 2
return v0, v1
@@ -110,13 +110,13 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage:
if category == "foo":
x = trial.suggest_float("x1", 0, 5)
y = trial.suggest_float("y1", 0, 3)
v0 = 4 * x ** 2 + 4 * y ** 2
v0 = 4 * x**2 + 4 * y**2
v1 = (x - 5) ** 2 + (y - 5) ** 2
return v0, v1
else:
x = trial.suggest_float("x2", 0, 5)
y = trial.suggest_float("y2", 0, 3)
v0 = 2 * x ** 2 + 2 * y ** 2
v0 = 2 * x**2 + 2 * y**2
v1 = (x - 2) ** 2 + (y - 3) ** 2
return v0, v1
@@ -130,7 +130,7 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage:
def objective_prune_without_report(trial: optuna.Trial) -> float:
x = trial.suggest_float("x", -15, 30)
y = trial.suggest_float("y", -15, 30)
v = x ** 2 + y ** 2
v = x**2 + y**2
if v > 100:
raise optuna.TrialPruned()
return v
@@ -201,7 +201,7 @@ def main() -> None:
else:
storage = optuna.storages.RDBStorage(args.storage)
app = create_app(storage)
app = wsgi(storage)
httpd = make_server(args.host, args.port, app)
thread = threading.Thread(target=httpd.serve_forever)
thread.start()