From 679646bacf7a949269d0f16a49de305201597e25 Mon Sep 17 00:00:00 2001 From: RuTiO2le Date: Sat, 30 Sep 2023 17:47:35 +0900 Subject: [PATCH 01/33] add csv download --- optuna_dashboard/_app.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 812201ad..7f263dfd 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -1,6 +1,8 @@ from __future__ import annotations +import csv import functools +import io import logging import os import typing @@ -447,6 +449,41 @@ def create_app( note.save_note_with_version(storage, study_id, trial_id, req_note_ver, req_note_body) response.status = 204 # No content return {} + + @app.get("/csv/") + def download_csv(study_id: int) -> BottleViewReturn: + # TODO: Create a CSV file + summary = get_study_summary(storage, study_id) + if summary is None: + response.status = 404 # Not found + return {"reason": f"study_id={study_id} is not found"} + trials = get_trials(storage, study_id) + + param_names = list(trials[0].params.keys()) + union_user_attrs = list(trials[0].user_attrs) + column_names = ["Number", "State", "Value"] + param_names + union_user_attrs + + buf = io.StringIO("") + writer = csv.writer(buf) + writer.writerow(column_names) + for frozen_trial in trials: + row = [ + frozen_trial.number, + frozen_trial.state, + frozen_trial.values[0] + ] + row += [frozen_trial.params[param] for param in param_names] + row += [frozen_trial.user_attrs[attr] for attr in union_user_attrs] + writer.writerow(row) + + # TODO: Set response headers + response.headers["Content-Type"] = "text/csv; chatset=cp932" + response.headers["Content-Disposition"] = f"attachment; filename=trials_{study_id}.csv" + + # TODO: Response body + buf.seek(0) + return buf.read() + @app.get("/favicon.ico") def favicon() -> BottleViewReturn: From c6f495dfa694720ba3edb5d81298249bb0556d51 Mon Sep 17 00:00:00 2001 From: RuTiO2le Date: Fri, 6 Oct 2023 06:22:46 +0900 Subject: [PATCH 02/33] add csv download button --- optuna_dashboard/ts/components/StudyDetail.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index ab37d02b..dc7d5894 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -12,6 +12,7 @@ import { import Grid2 from "@mui/material/Unstable_Grid2" import ChevronRightIcon from "@mui/icons-material/ChevronRight" import HomeIcon from "@mui/icons-material/Home" +import DownloadIcon from "@mui/icons-material/Download" import { StudyNote } from "./Note" import { actionCreator } from "../action" @@ -143,6 +144,16 @@ export const StudyDetail: FC<{ content = ( + + + From 44ad9489829ed3adee2a5cc89ab1e026906b9a14 Mon Sep 17 00:00:00 2001 From: RuTiO2le Date: Fri, 6 Oct 2023 06:54:02 +0900 Subject: [PATCH 03/33] remove branks in L484 --- optuna_dashboard/_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 7f263dfd..0a84c7c5 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -481,7 +481,7 @@ def create_app( response.headers["Content-Disposition"] = f"attachment; filename=trials_{study_id}.csv" # TODO: Response body - buf.seek(0) + buf.seek(0) return buf.read() From 547d985a9fe3d1634928e34f0b89374a4fd681d1 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 11:37:58 +0900 Subject: [PATCH 04/33] Update _app.py --- optuna_dashboard/_app.py | 51 +++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 8baa1280..d9d04666 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -2,7 +2,7 @@ from __future__ import annotations import csv import functools -import io +import io import logging import os import typing @@ -453,38 +453,57 @@ def create_app( @app.get("/csv/") def download_csv(study_id: int) -> BottleViewReturn: - # TODO: Create a CSV file + # Create a CSV file summary = get_study_summary(storage, study_id) if summary is None: response.status = 404 # Not found return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) - param_names = list(trials[0].params.keys()) - union_user_attrs = list(trials[0].user_attrs) - column_names = ["Number", "State", "Value"] + param_names + union_user_attrs + param_names = [] + user_attr_names = [] + for trial in trials: + for param_name in trial.params.keys(): + if param_name not in param_names: + param_names.append(param_name) + for attr_name in trial.user_attrs.keys(): + if attr_name not in user_attr_names: + user_attr_names.append(attr_name) + + param_names_heading = [f"Param {x}" for x in param_names] + user_attr_names_heading = [f"UserAttribute {x}" for x in user_attr_names] + value_heading = ["Value"] + if len(trials[0].values) > 1: + value_heading = [f"Objective {x}" for x in range(len(trials[0].values))] + column_names = ( + ["Number", "State"] + value_heading + param_names_heading + user_attr_names_heading + ) buf = io.StringIO("") writer = csv.writer(buf) writer.writerow(column_names) for frozen_trial in trials: - row = [ - frozen_trial.number, - frozen_trial.state, - frozen_trial.values[0] - ] - row += [frozen_trial.params[param] for param in param_names] - row += [frozen_trial.user_attrs[attr] for attr in union_user_attrs] + row = [frozen_trial.number, frozen_trial.state.name] + row += frozen_trial.values + for param_name in param_names: + if param_name in frozen_trial.params.keys(): + row += [frozen_trial.params[param_name]] + else: + row += [None] + for attr_name in user_attr_names: + if attr_name in frozen_trial.user_attrs.keys(): + row += [frozen_trial.user_attrs[attr_name]] + else: + row += [None] writer.writerow(row) - - # TODO: Set response headers + + # Set response headers response.headers["Content-Type"] = "text/csv; chatset=cp932" response.headers["Content-Disposition"] = f"attachment; filename=trials_{study_id}.csv" - # TODO: Response body + # Response body buf.seek(0) return buf.read() - @app.get("/favicon.ico") def favicon() -> BottleViewReturn: From 65d7c8e4cc62818a4c304bad692b5c75652b05ef Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 11:39:12 +0900 Subject: [PATCH 05/33] add download button --- .../ts/components/StudyDetail.tsx | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index f85d7458..ce7fe3ce 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -150,21 +150,39 @@ export const StudyDetail: FC<{ content = } else if (page === "trialTable") { content = ( - - - - - - - - + + + + + + + + Download CSV File + {" "} + + + + + + + + ) } else if (page === "note" && studyDetail !== null) { content = ( From 46ec27719566ae6c04462aec8e05d5b5aa9382e1 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 11:47:07 +0900 Subject: [PATCH 06/33] fix lint --- optuna_dashboard/_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index d9d04666..b2b13b67 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -450,7 +450,7 @@ def create_app( note.save_note_with_version(storage, study_id, trial_id, req_note_ver, req_note_body) response.status = 204 # No content return {} - + @app.get("/csv/") def download_csv(study_id: int) -> BottleViewReturn: # Create a CSV file From 6f681c40304c27b26066d513e645098284d95ddb Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 15:25:57 +0900 Subject: [PATCH 07/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index b2b13b67..c0476268 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -483,18 +483,9 @@ def create_app( writer = csv.writer(buf) writer.writerow(column_names) for frozen_trial in trials: - row = [frozen_trial.number, frozen_trial.state.name] - row += frozen_trial.values - for param_name in param_names: - if param_name in frozen_trial.params.keys(): - row += [frozen_trial.params[param_name]] - else: - row += [None] - for attr_name in user_attr_names: - if attr_name in frozen_trial.user_attrs.keys(): - row += [frozen_trial.user_attrs[attr_name]] - else: - row += [None] + row = [frozen_trial.number, frozen_trial.state.name] + frozen_trial.values + row.extend([frozen_trial.params.get(name, None) for name in param_names]) + row.extend([frozen_trial.user_attrs.get(name, None) for name in user_attr_names]) writer.writerow(row) # Set response headers From 3b3142209982034f0f7f38fc0db2e4bd2eebbb53 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 15:28:19 +0900 Subject: [PATCH 08/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index c0476268..db50e1f4 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -460,15 +460,8 @@ def create_app( return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) - param_names = [] - user_attr_names = [] - for trial in trials: - for param_name in trial.params.keys(): - if param_name not in param_names: - param_names.append(param_name) - for attr_name in trial.user_attrs.keys(): - if attr_name not in user_attr_names: - user_attr_names.append(attr_name) + param_names = sorted(set(chain.from_iterable([t.params.keys() for t in trials]))) + user_attr_names = sorted(set(chain.from_iterable([t.user_attrs.keys() for t in trials]))) param_names_heading = [f"Param {x}" for x in param_names] user_attr_names_heading = [f"UserAttribute {x}" for x in user_attr_names] From a5502cefc21f3ac1333d8f642acb0a7687b11fe4 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 15:31:04 +0900 Subject: [PATCH 09/33] Update _app.py --- optuna_dashboard/_app.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index db50e1f4..0af6756d 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -3,6 +3,7 @@ from __future__ import annotations import csv import functools import io +from itertools import chain import logging import os import typing @@ -463,13 +464,13 @@ def create_app( param_names = sorted(set(chain.from_iterable([t.params.keys() for t in trials]))) user_attr_names = sorted(set(chain.from_iterable([t.user_attrs.keys() for t in trials]))) - param_names_heading = [f"Param {x}" for x in param_names] - user_attr_names_heading = [f"UserAttribute {x}" for x in user_attr_names] - value_heading = ["Value"] + param_names_header = [f"Param {x}" for x in param_names] + user_attr_names_header = [f"UserAttribute {x}" for x in user_attr_names] + value_header = ["Value"] if len(trials[0].values) > 1: - value_heading = [f"Objective {x}" for x in range(len(trials[0].values))] + value_header = [f"Objective {x}" for x in range(len(trials[0].values))] column_names = ( - ["Number", "State"] + value_heading + param_names_heading + user_attr_names_heading + ["Number", "State"] + value_header + param_names_header + user_attr_names_header ) buf = io.StringIO("") From c73abacef3c96569638b120d28a7965f220c79c6 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 15:35:35 +0900 Subject: [PATCH 10/33] Add files via upload --- python_tests/test_csv_download.py | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 python_tests/test_csv_download.py diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py new file mode 100644 index 00000000..a820f37b --- /dev/null +++ b/python_tests/test_csv_download.py @@ -0,0 +1,73 @@ +import optuna +from optuna_dashboard._app import create_app +import pytest + +from .wsgi_client import send_request + + +@pytest.mark.parametrize("id", [0, 1]) +def test_download_csv_fail(id: int) -> None: + def objective(trial: optuna.Trial) -> float: + x = trial.suggest_float("x", -100, 100) + y = trial.suggest_categorical("y", [-1, 0, 1]) + return x**2 + y + + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + optuna.logging.set_verbosity(optuna.logging.ERROR) + study.optimize(objective, n_trials=10) + app = create_app(storage) + status, _, body = send_request( + app, + f"/csv/{id}", + "GET", + content_type="application/json", + ) + assert status == (404 if id != 0 else 200) + + +@pytest.mark.parametrize("is_multi_obj", [True, False]) +def test_download_csv_multi_obj(is_multi_obj: bool) -> None: + def objective(trial: optuna.Trial) -> float: + x = trial.suggest_float("x", -100, 100) + y = trial.suggest_categorical("y", [-1, 0, 1]) + if is_multi_obj: + return x**2, y + return x**2 + y + + storage = optuna.storages.InMemoryStorage() + if is_multi_obj: + study = optuna.create_study(storage=storage, directions=["minimize", "minimize"]) + else: + study = optuna.create_study(storage=storage) + optuna.logging.set_verbosity(optuna.logging.ERROR) + study.optimize(objective, n_trials=10) + app = create_app(storage) + status, _, body = send_request( + app, + f"/csv/{0}", + "GET", + content_type="application/json", + ) + assert status == 200 + + +def test_download_csv_user_attr() -> None: + def objective(trial: optuna.Trial) -> float: + x = trial.suggest_float("x", -100, 100) + y = trial.suggest_categorical("y", [-1, 0, 1]) + trial.set_user_attr("abs_y", abs(y)) + return x**2 + y + + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + optuna.logging.set_verbosity(optuna.logging.ERROR) + study.optimize(objective, n_trials=10) + app = create_app(storage) + status, _, body = send_request( + app, + f"/csv/{0}", + "GET", + content_type="application/json", + ) + assert status == 200 From 26d29f3c02e03b1b51127a642485a25da83e1fe9 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 15:54:40 +0900 Subject: [PATCH 11/33] fix mypy --- python_tests/test_csv_download.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index a820f37b..de144fd0 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -1,3 +1,5 @@ +from typing import Any + import optuna from optuna_dashboard._app import create_app import pytest @@ -28,7 +30,7 @@ def test_download_csv_fail(id: int) -> None: @pytest.mark.parametrize("is_multi_obj", [True, False]) def test_download_csv_multi_obj(is_multi_obj: bool) -> None: - def objective(trial: optuna.Trial) -> float: + def objective(trial: optuna.Trial) -> Any: x = trial.suggest_float("x", -100, 100) y = trial.suggest_categorical("y", [-1, 0, 1]) if is_multi_obj: From 895fc0bd0489a3dc9693a5ecbcef7f192eeafad5 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 16:26:02 +0900 Subject: [PATCH 12/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 0af6756d..f6718945 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -460,6 +460,8 @@ def create_app( response.status = 404 # Not found return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) + if len(trials) == 0: + return {"reason": f"study_id={study_id} has no trials"} param_names = sorted(set(chain.from_iterable([t.params.keys() for t in trials]))) user_attr_names = sorted(set(chain.from_iterable([t.user_attrs.keys() for t in trials]))) From 908788d029a6bc626eb4f1d69f6006c206160e9a Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 16:26:38 +0900 Subject: [PATCH 13/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index f6718945..7f323518 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -469,8 +469,9 @@ def create_app( param_names_header = [f"Param {x}" for x in param_names] user_attr_names_header = [f"UserAttribute {x}" for x in user_attr_names] value_header = ["Value"] - if len(trials[0].values) > 1: - value_header = [f"Objective {x}" for x in range(len(trials[0].values))] + n_objs = max([len(t.values) for t in trials if t.values is not None], default=1) + if n_objs > 1: + value_header = [f"Objective {x}" for x in range(n_objs)] column_names = ( ["Number", "State"] + value_header + param_names_header + user_attr_names_header ) From 2a03cd52855d3ff16a59e29fe5aae74f1e28c095 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 16:26:55 +0900 Subject: [PATCH 14/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 7f323518..ad4934e4 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -480,7 +480,8 @@ def create_app( writer = csv.writer(buf) writer.writerow(column_names) for frozen_trial in trials: - row = [frozen_trial.number, frozen_trial.state.name] + frozen_trial.values + row = [frozen_trial.number, frozen_trial.state.name] + row.extend(frozen_trial.values if frozen_trial.values is not None else [None] * n_objs) row.extend([frozen_trial.params.get(name, None) for name in param_names]) row.extend([frozen_trial.user_attrs.get(name, None) for name in user_attr_names]) writer.writerow(row) From 8eac8fc6f31e7420f0f5a277ebdd17953c7602be Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 16:36:00 +0900 Subject: [PATCH 15/33] add tests --- python_tests/test_csv_download.py | 52 +++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index de144fd0..b8491502 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -1,12 +1,60 @@ from typing import Any import optuna +from optuna.trial import TrialState from optuna_dashboard._app import create_app import pytest from .wsgi_client import send_request +def test_download_csv_no_trial() -> None: + def objective(trial: optuna.Trial) -> float: + x = trial.suggest_float("x", -100, 100) + y = trial.suggest_categorical("y", [-1, 0, 1]) + return x**2 + y + + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + study.optimize(objective, n_trials=0) + app = create_app(storage) + status, _, body = send_request( + app, + "/csv/0", + "GET", + content_type="application/json", + ) + assert status == 200 + + +def test_download_csv_all_waiting() -> None: + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + study.add_trial(optuna.trial.create_trial(state=TrialState.WAITING)) + app = create_app(storage) + status, _, body = send_request( + app, + "/csv/0", + "GET", + content_type="application/json", + ) + assert status == 200 + + +def test_download_csv_all_running() -> None: + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + study.add_trial(optuna.trial.create_trial(state=TrialState.RUNNING)) + app = create_app(storage) + status, _, body = send_request( + app, + "/csv/0", + "GET", + content_type="application/json", + ) + assert status == 200 + + @pytest.mark.parametrize("id", [0, 1]) def test_download_csv_fail(id: int) -> None: def objective(trial: optuna.Trial) -> float: @@ -47,7 +95,7 @@ def test_download_csv_multi_obj(is_multi_obj: bool) -> None: app = create_app(storage) status, _, body = send_request( app, - f"/csv/{0}", + "/csv/0", "GET", content_type="application/json", ) @@ -68,7 +116,7 @@ def test_download_csv_user_attr() -> None: app = create_app(storage) status, _, body = send_request( app, - f"/csv/{0}", + "/csv/0", "GET", content_type="application/json", ) From 2fde74b00dca4b7ee7a62a1b73b220b2a1ab40f6 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 17:19:42 +0900 Subject: [PATCH 16/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index ad4934e4..8ddd9526 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -460,8 +460,6 @@ def create_app( response.status = 404 # Not found return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) - if len(trials) == 0: - return {"reason": f"study_id={study_id} has no trials"} param_names = sorted(set(chain.from_iterable([t.params.keys() for t in trials]))) user_attr_names = sorted(set(chain.from_iterable([t.user_attrs.keys() for t in trials]))) From 3d6e69a16905f78b6b5221bb0e1dff05fe426b15 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 17:48:09 +0900 Subject: [PATCH 17/33] revised _app.py --- optuna_dashboard/_app.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 8ddd9526..a74ce1e9 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -6,6 +6,7 @@ import io from itertools import chain import logging import os +import re import typing from typing import Any from typing import Optional @@ -467,9 +468,12 @@ def create_app( param_names_header = [f"Param {x}" for x in param_names] user_attr_names_header = [f"UserAttribute {x}" for x in user_attr_names] value_header = ["Value"] - n_objs = max([len(t.values) for t in trials if t.values is not None], default=1) + n_objs = len(summary.directions) if n_objs > 1: - value_header = [f"Objective {x}" for x in range(n_objs)] + if "study:metric_names" in summary._system_attrs: + value_header = summary._system_attrs["study:metric_names"] + else: + value_header = [f"Objective {x}" for x in range(n_objs)] column_names = ( ["Number", "State"] + value_header + param_names_header + user_attr_names_header ) @@ -485,8 +489,9 @@ def create_app( writer.writerow(row) # Set response headers + output_filename = re.sub(r'[\\/:*?"<>|]+', "", summary.study_name) response.headers["Content-Type"] = "text/csv; chatset=cp932" - response.headers["Content-Disposition"] = f"attachment; filename=trials_{study_id}.csv" + response.headers["Content-Disposition"] = f"attachment; filename=trials_{output_filename}.csv" # Response body buf.seek(0) From e5e813bdde012379fbec3dbc229ce87c31fa96bf Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 17:56:00 +0900 Subject: [PATCH 18/33] fix lint --- optuna_dashboard/_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index a74ce1e9..115d4078 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -489,9 +489,9 @@ def create_app( writer.writerow(row) # Set response headers - output_filename = re.sub(r'[\\/:*?"<>|]+', "", summary.study_name) + output_name = re.sub(r'[\\/:*?"<>|]+', "", summary.study_name) response.headers["Content-Type"] = "text/csv; chatset=cp932" - response.headers["Content-Disposition"] = f"attachment; filename=trials_{output_filename}.csv" + response.headers["Content-Disposition"] = f"attachment; filename={output_name}.csv" # Response body buf.seek(0) From f57bab2d7d8a8bc3b897958148717c4764e914aa Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 18:05:15 +0900 Subject: [PATCH 19/33] revised _app.py --- optuna_dashboard/_app.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 115d4078..6a916c5d 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -456,24 +456,22 @@ def create_app( @app.get("/csv/") def download_csv(study_id: int) -> BottleViewReturn: # Create a CSV file - summary = get_study_summary(storage, study_id) - if summary is None: + try: + study_name = storage.get_study_name_from_id(study_id) + study = optuna.load_study(storage=storage, study_name=study_name) + except KeyError: response.status = 404 # Not found return {"reason": f"study_id={study_id} is not found"} - trials = get_trials(storage, study_id) - + trials = study.trials param_names = sorted(set(chain.from_iterable([t.params.keys() for t in trials]))) user_attr_names = sorted(set(chain.from_iterable([t.user_attrs.keys() for t in trials]))) - param_names_header = [f"Param {x}" for x in param_names] user_attr_names_header = [f"UserAttribute {x}" for x in user_attr_names] - value_header = ["Value"] - n_objs = len(summary.directions) - if n_objs > 1: - if "study:metric_names" in summary._system_attrs: - value_header = summary._system_attrs["study:metric_names"] - else: - value_header = [f"Objective {x}" for x in range(n_objs)] + n_objs = len(study.directions) + if study.metric_names is not None: + value_header = study.metric_names + else: + value_header = ["Value"] if n_objs == 1 else [f"Objective {x}" for x in range(n_objs)] column_names = ( ["Number", "State"] + value_header + param_names_header + user_attr_names_header ) @@ -489,7 +487,7 @@ def create_app( writer.writerow(row) # Set response headers - output_name = re.sub(r'[\\/:*?"<>|]+', "", summary.study_name) + output_name = re.sub(r'[\\/:*?"<>|]+', "", study_name) response.headers["Content-Type"] = "text/csv; chatset=cp932" response.headers["Content-Disposition"] = f"attachment; filename={output_name}.csv" From c9915ad12d3640741ffbc9e826689fdbe30d80f3 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 18:26:17 +0900 Subject: [PATCH 20/33] Update optuna_dashboard/_app.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- optuna_dashboard/_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index 6a916c5d..ead27188 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -487,7 +487,7 @@ def create_app( writer.writerow(row) # Set response headers - output_name = re.sub(r'[\\/:*?"<>|]+', "", study_name) + output_name = "-".join(re.sub(r'[\\/:*?"<>|]+', "", study_name).split(" ")) response.headers["Content-Type"] = "text/csv; chatset=cp932" response.headers["Content-Disposition"] = f"attachment; filename={output_name}.csv" From 7d834d3aaed6b4fc2078c52623b64f8986a9e94b Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Wed, 6 Dec 2023 18:36:56 +0900 Subject: [PATCH 21/33] Update StudyDetail.tsx --- .../ts/components/StudyDetail.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index ce7fe3ce..2c464027 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -151,19 +151,11 @@ export const StudyDetail: FC<{ } else if (page === "trialTable") { content = ( - - - - - - - Download CSV File - {" "} + + Download CSV File + + + + + + ) } else if (page === "note" && studyDetail !== null) { From 64dc46c0c2552159709dc3ac56ff49ac807f1008 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 10:30:09 +0900 Subject: [PATCH 22/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index b8491502..89f8978f 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -18,7 +18,7 @@ def test_download_csv_no_trial() -> None: study = optuna.create_study(storage=storage) study.optimize(objective, n_trials=0) app = create_app(storage) - status, _, body = send_request( + status, _, _ = send_request( app, "/csv/0", "GET", From 462bc99b339b49733852e7c8cd8e73f4df4ccb5f Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 10:31:31 +0900 Subject: [PATCH 23/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index 89f8978f..55cd4569 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -86,10 +86,8 @@ def test_download_csv_multi_obj(is_multi_obj: bool) -> None: return x**2 + y storage = optuna.storages.InMemoryStorage() - if is_multi_obj: - study = optuna.create_study(storage=storage, directions=["minimize", "minimize"]) - else: - study = optuna.create_study(storage=storage) + directions = ["minimize", "minimize"] if is_multi_obj else ["minimize"] + study = optuna.create_study(storage=storage, directions=directions) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) app = create_app(storage) From 678b15e60cc17ce5164e80610514fd60d1f3a477 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 10:31:55 +0900 Subject: [PATCH 24/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index 55cd4569..7740ea83 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -55,8 +55,8 @@ def test_download_csv_all_running() -> None: assert status == 200 -@pytest.mark.parametrize("id", [0, 1]) -def test_download_csv_fail(id: int) -> None: +@pytest.mark.parametrize("study_id", [0, 1]) +def test_download_csv_fail(study_id: int) -> None: def objective(trial: optuna.Trial) -> float: x = trial.suggest_float("x", -100, 100) y = trial.suggest_categorical("y", [-1, 0, 1]) From 18d68ea91959563d0364dc561ca4ee3576a1625a Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 10:58:16 +0900 Subject: [PATCH 25/33] Update test_csv_download.py --- python_tests/test_csv_download.py | 69 ++++++++++--------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index 7740ea83..e1ab12c9 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -8,6 +8,21 @@ import pytest from .wsgi_client import send_request +def _validate_output( + storage: optuna.storages.BaseStorage, + correct_status: int, + study_id: int, +) -> None: + app = create_app(storage) + status, _, _ = send_request( + app, + f"/csv/{study_id}", + "GET", + content_type="application/json", + ) + assert status == correct_status + + def test_download_csv_no_trial() -> None: def objective(trial: optuna.Trial) -> float: x = trial.suggest_float("x", -100, 100) @@ -17,42 +32,21 @@ def test_download_csv_no_trial() -> None: storage = optuna.storages.InMemoryStorage() study = optuna.create_study(storage=storage) study.optimize(objective, n_trials=0) - app = create_app(storage) - status, _, _ = send_request( - app, - "/csv/0", - "GET", - content_type="application/json", - ) - assert status == 200 + _validate_output(storage, 200, 0) def test_download_csv_all_waiting() -> None: storage = optuna.storages.InMemoryStorage() study = optuna.create_study(storage=storage) study.add_trial(optuna.trial.create_trial(state=TrialState.WAITING)) - app = create_app(storage) - status, _, body = send_request( - app, - "/csv/0", - "GET", - content_type="application/json", - ) - assert status == 200 + _validate_output(storage, 200, 0) def test_download_csv_all_running() -> None: storage = optuna.storages.InMemoryStorage() study = optuna.create_study(storage=storage) study.add_trial(optuna.trial.create_trial(state=TrialState.RUNNING)) - app = create_app(storage) - status, _, body = send_request( - app, - "/csv/0", - "GET", - content_type="application/json", - ) - assert status == 200 + _validate_output(storage, 200, 0) @pytest.mark.parametrize("study_id", [0, 1]) @@ -66,14 +60,7 @@ def test_download_csv_fail(study_id: int) -> None: study = optuna.create_study(storage=storage) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - app = create_app(storage) - status, _, body = send_request( - app, - f"/csv/{id}", - "GET", - content_type="application/json", - ) - assert status == (404 if id != 0 else 200) + _validate_output(storage, 404 if study_id != 0 else 200, study_id) @pytest.mark.parametrize("is_multi_obj", [True, False]) @@ -90,14 +77,7 @@ def test_download_csv_multi_obj(is_multi_obj: bool) -> None: study = optuna.create_study(storage=storage, directions=directions) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - app = create_app(storage) - status, _, body = send_request( - app, - "/csv/0", - "GET", - content_type="application/json", - ) - assert status == 200 + _validate_output(storage, 200, 0) def test_download_csv_user_attr() -> None: @@ -111,11 +91,4 @@ def test_download_csv_user_attr() -> None: study = optuna.create_study(storage=storage) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - app = create_app(storage) - status, _, body = send_request( - app, - "/csv/0", - "GET", - content_type="application/json", - ) - assert status == 200 + _validate_output(storage, 200, 0) From 4a3849b07312d53f8205b8e18fb9a442971108b9 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 11:57:52 +0900 Subject: [PATCH 26/33] Update StudyDetail.tsx --- optuna_dashboard/ts/components/StudyDetail.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 2c464027..f7a6b0eb 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -157,8 +157,8 @@ export const StudyDetail: FC<{ width: "auto", height: "auto", display: "flex", - justifyContent: "center", - alignItems: "center", + justifyContent: "left", + alignItems: "left", }} > @@ -166,7 +166,6 @@ export const StudyDetail: FC<{ aria-label="download csv" size="small" color="inherit" - download={`trials_${studyDetail?.id}.csv`} sx={{ margin: "auto 0" }} href={`/csv/${studyDetail?.id}`} > From 6d442254e2019f85b38d9728de84134458b43216 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 12:06:38 +0900 Subject: [PATCH 27/33] Update StudyDetail.tsx --- optuna_dashboard/ts/components/StudyDetail.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index f7a6b0eb..79702f2e 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -166,6 +166,7 @@ export const StudyDetail: FC<{ aria-label="download csv" size="small" color="inherit" + download sx={{ margin: "auto 0" }} href={`/csv/${studyDetail?.id}`} > From e1f43e1ce4e139df05bf48b282d09ddffa060955 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:14:24 +0900 Subject: [PATCH 28/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index e1ab12c9..bb4c2bf8 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -1,5 +1,8 @@ +from __future__ import annotations + from typing import Any + import optuna from optuna.trial import TrialState from optuna_dashboard._app import create_app From a4fb8fdcdbf33393da21cf75ab2be9c0dd344e81 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:14:55 +0900 Subject: [PATCH 29/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index bb4c2bf8..3c615995 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -15,15 +15,23 @@ def _validate_output( storage: optuna.storages.BaseStorage, correct_status: int, study_id: int, + expect_no_result: bool = False, + extra_col_names: list[str] | None = None, ) -> None: app = create_app(storage) - status, _, _ = send_request( + status, _, body = send_request( app, f"/csv/{study_id}", "GET", content_type="application/json", ) assert status == correct_status + decoded_csv = str(body.decode("utf-8")) + if expect_no_result: + assert "is not found" in decoded_csv + else: + col_names = ["Number", "State"] + ([] if extra_col_names is None else extra_col_names) + assert all(col_name in decoded_csv for col_name in col_names) def test_download_csv_no_trial() -> None: From 1e5d582572b29b4fa85ed239a258de15df079581 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:15:06 +0900 Subject: [PATCH 30/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index 3c615995..a93789e7 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -71,7 +71,9 @@ def test_download_csv_fail(study_id: int) -> None: study = optuna.create_study(storage=storage) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - _validate_output(storage, 404 if study_id != 0 else 200, study_id) + expect_no_result = study_id != 0 + cols = ["Param x", "Param y", "Value"] + _validate_output(storage, 404 if expect_no_result else 200, study_id, expect_no_result, cols) @pytest.mark.parametrize("is_multi_obj", [True, False]) From d7ff6f41f45fb1d8dbc5e61ff3642d7c99f52f5f Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:15:19 +0900 Subject: [PATCH 31/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index a93789e7..3eb0f330 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -90,7 +90,10 @@ def test_download_csv_multi_obj(is_multi_obj: bool) -> None: study = optuna.create_study(storage=storage, directions=directions) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - _validate_output(storage, 200, 0) + cols = ["Param x", "Param y"] + cols += ["Objective 0", "Objective 1"] if is_multi_obj else ["Value"] + _validate_output(storage, 200, 0, extra_col_names=cols) + def test_download_csv_user_attr() -> None: From a8c451ada283cecf18aa8f8a036f7ea1816d5999 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:15:30 +0900 Subject: [PATCH 32/33] Update python_tests/test_csv_download.py Co-authored-by: Shuhei Watanabe <47781922+nabenabe0928@users.noreply.github.com> --- python_tests/test_csv_download.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index 3eb0f330..f43c8751 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -107,4 +107,6 @@ def test_download_csv_user_attr() -> None: study = optuna.create_study(storage=storage) optuna.logging.set_verbosity(optuna.logging.ERROR) study.optimize(objective, n_trials=10) - _validate_output(storage, 200, 0) + cols = ["Param x", "Param y", "Value", "UserAttribute abs_y"] + _validate_output(storage, 200, 0, extra_col_names=cols) + From f63b5e9cedd733df22cd6cab3da3731a4ffcbf58 Mon Sep 17 00:00:00 2001 From: Hiroki Takizawa Date: Thu, 7 Dec 2023 18:21:46 +0900 Subject: [PATCH 33/33] fix lint --- python_tests/test_csv_download.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python_tests/test_csv_download.py b/python_tests/test_csv_download.py index f43c8751..019f18ba 100644 --- a/python_tests/test_csv_download.py +++ b/python_tests/test_csv_download.py @@ -2,7 +2,6 @@ from __future__ import annotations from typing import Any - import optuna from optuna.trial import TrialState from optuna_dashboard._app import create_app @@ -95,7 +94,6 @@ def test_download_csv_multi_obj(is_multi_obj: bool) -> None: _validate_output(storage, 200, 0, extra_col_names=cols) - def test_download_csv_user_attr() -> None: def objective(trial: optuna.Trial) -> float: x = trial.suggest_float("x", -100, 100) @@ -109,4 +107,3 @@ def test_download_csv_user_attr() -> None: study.optimize(objective, n_trials=10) cols = ["Param x", "Param y", "Value", "UserAttribute abs_y"] _validate_output(storage, 200, 0, extra_col_names=cols) -