jsonify-optuna 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ **/__pycache__/
2
+ .vscode/
3
+ .ruff_cache/
4
+ .venv/
5
+ dist/
6
+ .claude_utils/
7
+ .pytest_cache/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: jsonify-optuna
3
+ Version: 0.1.0
4
+ Summary: Convert an Optuna Study into a JSON-serializable dict.
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: optuna>=4.9.0
7
+ Description-Content-Type: text/markdown
8
+
9
+ # jsonify-optuna
10
+
11
+ Convert an [Optuna](https://github.com/optuna/optuna) Study into a plain, JSON-serializable Python dict.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install jsonify-optuna
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Convert an in-memory study to a JSON-friendly dict:
22
+
23
+ ```python
24
+ import optuna
25
+ from jsonify_optuna import jsonify
26
+ from optuna.trial import TrialState
27
+
28
+ def objective(trial):
29
+ x = trial.suggest_float("x", -10.0, 10.0)
30
+ return x ** 2
31
+
32
+ study = optuna.create_study(direction="minimize")
33
+ study.optimize(objective, n_trials=20)
34
+ result = jsonify(study, states=[TrialState.COMPLETE])
35
+ # This includes all the trials.
36
+ result = jsonify(study)
37
+ ```
38
+
39
+ If you have a study stored in a backend, you can load it as follows:
40
+
41
+ ```python
42
+ from jsonify_optuna import load_study
43
+
44
+ # From an RDB (SQLite, PostgreSQL, etc.)
45
+ study = load_study(rdb_url="sqlite:///example.db", study_name="my_study")
46
+
47
+ # From a journal file
48
+ study = load_study(journal_path="journal.log", study_name="my_study")
49
+
50
+ # From a storage object directly
51
+ study = load_study(storage=my_storage, study_name="my_study")
52
+ ```
53
+
54
+ > [!NOTE]
55
+ > When the storage contains exactly one study, `study_name` can be omitted.
56
+
57
+ ## API Reference
58
+
59
+ ### `jsonify(study, *, states=None, deepcopy=True)`
60
+
61
+ | Parameter | Type | Description |
62
+ |-----------|------|-------------|
63
+ | `study` | `optuna.Study` | The study to convert. |
64
+ | `states` | `list[TrialState] \| None` | Filter trials by state. `None` includes all states. |
65
+ | `deepcopy` | `bool` | Whether to deep-copy trials from storage. Default `True`. |
66
+
67
+ Returns a dict with:
68
+
69
+ | Key | Type | Description |
70
+ |-----|------|-------------|
71
+ | `directions` | `list[str]` | `"minimize"` or `"maximize"` per objective. |
72
+ | `user_attrs` | `dict` | Study-level user attributes. |
73
+ | `metric_names` | `list[str] \| None` | Metric names if set, else `None`. |
74
+ | `trials` | `list[dict]` | Trial dicts (see below). |
75
+ | `best_trial_indices` | `list[int]` | Indices into `trials` for Pareto-front trials. |
76
+
77
+ Each trial dict contains:
78
+
79
+ | Key | Type | Description |
80
+ |-----|------|-------------|
81
+ | `state` | `str` | `"complete"`, `"pruned"`, `"fail"`, `"running"`, or `"waiting"`. |
82
+ | `values` | `list[float] \| None` | Objective values. `None` if not complete. |
83
+ | `params` | `dict` | Parameter name-value pairs. |
84
+ | `user_attrs` | `dict` | Trial-level user attributes. |
85
+ | `intermediate_values` | `dict[int, float]` | Step-value pairs from `trial.report()`. |
86
+ | `distributions` | `dict` | Parameter distributions (see below). |
87
+
88
+ Distribution formats:
89
+
90
+ - **Int/Float**: `{"low": ..., "high": ..., "step": ..., "log": ...}`
91
+ - **Categorical**: `{"choices": [...]}`
92
+
93
+ ### `load_study(*, study_name=None, storage=None, journal_path=None, rdb_url=None)`
94
+
95
+ | Parameter | Type | Description |
96
+ |-----------|------|-------------|
97
+ | `study_name` | `str \| None` | Study name. Optional when storage has exactly one study. |
98
+ | `storage` | `BaseStorage \| None` | An Optuna storage object. |
99
+ | `journal_path` | `str \| None` | Path to a journal log file. |
100
+ | `rdb_url` | `str \| None` | SQLAlchemy-style database URL. |
101
+
102
+ Exactly one of `storage`, `journal_path`, or `rdb_url` must be provided.
@@ -0,0 +1,94 @@
1
+ # jsonify-optuna
2
+
3
+ Convert an [Optuna](https://github.com/optuna/optuna) Study into a plain, JSON-serializable Python dict.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install jsonify-optuna
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Convert an in-memory study to a JSON-friendly dict:
14
+
15
+ ```python
16
+ import optuna
17
+ from jsonify_optuna import jsonify
18
+ from optuna.trial import TrialState
19
+
20
+ def objective(trial):
21
+ x = trial.suggest_float("x", -10.0, 10.0)
22
+ return x ** 2
23
+
24
+ study = optuna.create_study(direction="minimize")
25
+ study.optimize(objective, n_trials=20)
26
+ result = jsonify(study, states=[TrialState.COMPLETE])
27
+ # This includes all the trials.
28
+ result = jsonify(study)
29
+ ```
30
+
31
+ If you have a study stored in a backend, you can load it as follows:
32
+
33
+ ```python
34
+ from jsonify_optuna import load_study
35
+
36
+ # From an RDB (SQLite, PostgreSQL, etc.)
37
+ study = load_study(rdb_url="sqlite:///example.db", study_name="my_study")
38
+
39
+ # From a journal file
40
+ study = load_study(journal_path="journal.log", study_name="my_study")
41
+
42
+ # From a storage object directly
43
+ study = load_study(storage=my_storage, study_name="my_study")
44
+ ```
45
+
46
+ > [!NOTE]
47
+ > When the storage contains exactly one study, `study_name` can be omitted.
48
+
49
+ ## API Reference
50
+
51
+ ### `jsonify(study, *, states=None, deepcopy=True)`
52
+
53
+ | Parameter | Type | Description |
54
+ |-----------|------|-------------|
55
+ | `study` | `optuna.Study` | The study to convert. |
56
+ | `states` | `list[TrialState] \| None` | Filter trials by state. `None` includes all states. |
57
+ | `deepcopy` | `bool` | Whether to deep-copy trials from storage. Default `True`. |
58
+
59
+ Returns a dict with:
60
+
61
+ | Key | Type | Description |
62
+ |-----|------|-------------|
63
+ | `directions` | `list[str]` | `"minimize"` or `"maximize"` per objective. |
64
+ | `user_attrs` | `dict` | Study-level user attributes. |
65
+ | `metric_names` | `list[str] \| None` | Metric names if set, else `None`. |
66
+ | `trials` | `list[dict]` | Trial dicts (see below). |
67
+ | `best_trial_indices` | `list[int]` | Indices into `trials` for Pareto-front trials. |
68
+
69
+ Each trial dict contains:
70
+
71
+ | Key | Type | Description |
72
+ |-----|------|-------------|
73
+ | `state` | `str` | `"complete"`, `"pruned"`, `"fail"`, `"running"`, or `"waiting"`. |
74
+ | `values` | `list[float] \| None` | Objective values. `None` if not complete. |
75
+ | `params` | `dict` | Parameter name-value pairs. |
76
+ | `user_attrs` | `dict` | Trial-level user attributes. |
77
+ | `intermediate_values` | `dict[int, float]` | Step-value pairs from `trial.report()`. |
78
+ | `distributions` | `dict` | Parameter distributions (see below). |
79
+
80
+ Distribution formats:
81
+
82
+ - **Int/Float**: `{"low": ..., "high": ..., "step": ..., "log": ...}`
83
+ - **Categorical**: `{"choices": [...]}`
84
+
85
+ ### `load_study(*, study_name=None, storage=None, journal_path=None, rdb_url=None)`
86
+
87
+ | Parameter | Type | Description |
88
+ |-----------|------|-------------|
89
+ | `study_name` | `str \| None` | Study name. Optional when storage has exactly one study. |
90
+ | `storage` | `BaseStorage \| None` | An Optuna storage object. |
91
+ | `journal_path` | `str \| None` | Path to a journal log file. |
92
+ | `rdb_url` | `str \| None` | SQLAlchemy-style database URL. |
93
+
94
+ Exactly one of `storage`, `journal_path`, or `rdb_url` must be provided.
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import optuna
6
+
7
+ from jsonify_optuna import jsonify
8
+
9
+
10
+ def objective(trial: optuna.Trial) -> float:
11
+ x = trial.suggest_float("x", -10.0, 10.0)
12
+ y = trial.suggest_int("y", 1, 5)
13
+ return (x - 2) ** 2 + y
14
+
15
+
16
+ if __name__ == "__main__":
17
+ optuna.logging.set_verbosity(optuna.logging.WARNING)
18
+ study = optuna.create_study(direction="minimize")
19
+ study.optimize(objective, n_trials=20)
20
+
21
+ result = jsonify(study)
22
+ print(json.dumps(result, indent=2))
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import optuna
6
+ from optuna.trial import TrialState
7
+
8
+ from jsonify_optuna import jsonify
9
+
10
+
11
+ def objective(trial: optuna.Trial) -> float:
12
+ x = trial.suggest_float("x", 0.0, 10.0)
13
+ trial.set_user_attr("note", f"trial {trial.number}")
14
+ for step in range(5):
15
+ intermediate = x + step
16
+ trial.report(intermediate, step)
17
+ if trial.should_prune():
18
+ raise optuna.TrialPruned()
19
+ return x**2
20
+
21
+
22
+ if __name__ == "__main__":
23
+ optuna.logging.set_verbosity(optuna.logging.WARNING)
24
+ study = optuna.create_study(
25
+ direction="minimize", pruner=optuna.pruners.MedianPruner()
26
+ )
27
+ study.set_user_attr("experiment", "demo")
28
+ study.optimize(objective, n_trials=20)
29
+
30
+ complete_only = jsonify(study, states=[TrialState.COMPLETE])
31
+ print(f"Study user_attrs: {complete_only['user_attrs']}")
32
+ print(f"Complete trials: {len(complete_only['trials'])}")
33
+ print(json.dumps(complete_only, indent=2))
34
+
35
+ pruned_only = jsonify(study, states=[TrialState.PRUNED])
36
+ print(f"\nPruned trials: {len(pruned_only['trials'])}")
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ import tempfile
6
+
7
+ import optuna
8
+
9
+ from jsonify_optuna import jsonify
10
+ from jsonify_optuna import load_study
11
+
12
+
13
+ def objective(trial: optuna.Trial) -> float:
14
+ x = trial.suggest_float("x", -5.0, 5.0)
15
+ return x**2
16
+
17
+
18
+ if __name__ == "__main__":
19
+ optuna.logging.set_verbosity(optuna.logging.WARNING)
20
+
21
+ with tempfile.TemporaryDirectory() as tmpdir:
22
+ db_path = Path(tmpdir) / "example.db"
23
+ rdb_url = f"sqlite:///{db_path}"
24
+ study = optuna.create_study(
25
+ storage=rdb_url, study_name="rdb_demo", direction="minimize"
26
+ )
27
+ study.optimize(objective, n_trials=10)
28
+
29
+ loaded = load_study(rdb_url=rdb_url, study_name="rdb_demo")
30
+ print("=== RDB storage ===")
31
+ print(json.dumps(jsonify(loaded), indent=2))
32
+
33
+ journal_path = str(Path(tmpdir) / "journal.log")
34
+ backend = optuna.storages.journal.JournalFileBackend(journal_path)
35
+ storage = optuna.storages.JournalStorage(backend)
36
+ study = optuna.create_study(
37
+ storage=storage, study_name="journal_demo", direction="minimize"
38
+ )
39
+ study.optimize(objective, n_trials=10)
40
+
41
+ loaded = load_study(
42
+ journal_path=journal_path, study_name="journal_demo"
43
+ )
44
+ print("\n=== Journal storage ===")
45
+ print(json.dumps(jsonify(loaded), indent=2))
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ import optuna
6
+
7
+ from jsonify_optuna import jsonify
8
+
9
+
10
+ def objective(trial: optuna.Trial) -> tuple[float, float]:
11
+ lr = trial.suggest_float("lr", 1e-5, 1e-1, log=True)
12
+ n_layers = trial.suggest_int("n_layers", 1, 4)
13
+ activation = trial.suggest_categorical("activation", ["relu", "tanh", "sigmoid"])
14
+ loss = lr * n_layers + (0.1 if activation == "relu" else 0.5)
15
+ accuracy = 1.0 - loss
16
+ return loss, accuracy
17
+
18
+
19
+ if __name__ == "__main__":
20
+ optuna.logging.set_verbosity(optuna.logging.WARNING)
21
+ study = optuna.create_study(directions=["minimize", "maximize"])
22
+ study.set_metric_names(["loss", "accuracy"])
23
+ study.optimize(objective, n_trials=30)
24
+
25
+ result = jsonify(study)
26
+ print(f"Directions: {result['directions']}")
27
+ print(f"Metric names: {result['metric_names']}")
28
+ print(f"Total trials: {len(result['trials'])}")
29
+ print(f"Pareto front size: {len(result['best_trial_indices'])}")
30
+ print(json.dumps(result, indent=2))
@@ -0,0 +1,5 @@
1
+ from jsonify_optuna.jsonify_optuna import jsonify
2
+ from jsonify_optuna.jsonify_optuna import load_study
3
+
4
+
5
+ __all__ = ["jsonify", "load_study"]
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import optuna
6
+ from optuna.trial import TrialState
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import Any
11
+ from typing import Literal
12
+ from typing import TypedDict
13
+
14
+ from optuna.distributions import CategoricalChoiceType
15
+
16
+ class NumericalDistributionType(TypedDict):
17
+ low: int | float
18
+ high: int | float
19
+ step: int | float | None
20
+ log: bool
21
+
22
+ class CategoricalDistributionType(TypedDict):
23
+ choices: list[CategoricalChoiceType]
24
+
25
+ class TrialType(TypedDict):
26
+ state: Literal["running", "waiting", "completepruned", "fail"]
27
+ values: list[float]
28
+ params: dict[str, int | float | CategoricalChoiceType]
29
+ user_attrs: dict[str, Any]
30
+ intermediate_values: list[float]
31
+ distributions: dict[str, NumericalDistributionType | CategoricalDistributionType]
32
+
33
+ class StudyType:
34
+ trials: list[TrialType]
35
+ best_trial_indices: list[int]
36
+ directions: list[Literal["minimize", "maximize"]]
37
+ user_attrs: dict[str, Any]
38
+ metric_names: list[str]
39
+
40
+
41
+ def jsonify(
42
+ study: optuna.Study, *, states: list[TrialState] | None = None, deepcopy: bool = True
43
+ ) -> StudyType:
44
+ states = states or [state for state in TrialState]
45
+ study_json = {
46
+ "directions": [dire.name.lower() for dire in study.directions],
47
+ "user_attrs": study.user_attrs,
48
+ "metric_names": study.metric_names,
49
+ }
50
+ best_trial_numbers = set(t.number for t in study.best_trials)
51
+ best_trial_indices = []
52
+ results = []
53
+ for t in study.get_trials(deepcopy=deepcopy):
54
+ if t.state not in states:
55
+ continue
56
+ if t.number in best_trial_numbers:
57
+ best_trial_indices.append(len(results))
58
+ dists = {}
59
+ for p, d in t.distributions.items():
60
+ if isinstance(d, optuna.distributions.CategoricalDistribution):
61
+ dists[p] = {"choices": d.choices}
62
+ continue
63
+ assert isinstance(
64
+ d, (optuna.distributions.IntDistribution, optuna.distributions.FloatDistribution)
65
+ )
66
+ dists[p] = {"low": d.low, "high": d.high, "step": d.step, "log": d.log}
67
+ row = {
68
+ "state": t.state.name.lower(),
69
+ "values": t.values,
70
+ "params": t.params,
71
+ "user_attrs": t.user_attrs,
72
+ "intermediate_values": t.intermediate_values,
73
+ "distributions": dists,
74
+ }
75
+ results.append(row)
76
+ study_json |= {"trials": results, "best_trial_indices": best_trial_indices}
77
+ return study_json
78
+
79
+
80
+ def load_study(
81
+ *,
82
+ study_name: str | None = None,
83
+ storage: optuna.storages.BaseStorage | None = None,
84
+ journal_path: str | None = None,
85
+ rdb_url: str | None = None,
86
+ ) -> optuna.Study:
87
+ if storage is not None:
88
+ if rdb_url is not None or journal_path is not None:
89
+ raise ValueError(
90
+ f"storage is provided but either {rdb_url=} or {journal_path=} is also specified."
91
+ )
92
+ elif rdb_url is not None:
93
+ if journal_path is not None:
94
+ raise ValueError(f"Specify only one of {rdb_url=} or {journal_path=}.")
95
+ storage = optuna.storages.RDBStorage(rdb_url)
96
+ elif journal_path:
97
+ journal_file = optuna.storages.journal.JournalFileBackend(journal_path)
98
+ storage = optuna.storages.JournalStorage(journal_file)
99
+ else:
100
+ raise ValueError("No storage was specified.")
101
+ assert isinstance(storage, optuna.storages.BaseStorage), "MyPy Redefinition"
102
+ study_names = [study.study_name for study in storage.get_all_studies()]
103
+ if study_name is None and len(study_names) == 1:
104
+ return optuna.load_study(storage=storage, study_name=study_names[0])
105
+ if study_name is None:
106
+ raise ValueError(f"Specify `study_name` from {study_names=}.")
107
+ if study_name not in study_names:
108
+ raise ValueError(f"Specify `study_name` from {study_names=} but got {study_name=}.")
109
+ return optuna.load_study(storage=storage, study_name=study_name)
@@ -0,0 +1,3 @@
1
+ uv build
2
+ pip install dist/jsonify_optuna-*.whl
3
+ uv publish
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jsonify-optuna"
7
+ version = "0.1.0"
8
+ description = "Convert an Optuna Study into a JSON-serializable dict."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "optuna>=4.9.0",
13
+ ]
14
+
15
+ [dependency-groups]
16
+ dev = [
17
+ "mypy>=1.19.1",
18
+ "pytest>=8",
19
+ "ruff>=0.15.21",
20
+ ]
21
+
22
+ [tool.pytest.ini_options]
23
+ testpaths = ["tests"]
24
+
25
+ [tool.ruff]
26
+ line-length = 99
27
+ target-version = "py39"
28
+
29
+ [tool.ruff.lint]
30
+ select = [
31
+ "E", # pycodestyle errors
32
+ "W", # pycodestyle warnings
33
+ "F", # pyflakes
34
+ "I", # isort
35
+ "TCH", # flake8 type checking
36
+ ]
37
+
38
+ [tool.ruff.lint.isort]
39
+ known-first-party = ["jsonify_optuna", "tests"]
40
+ lines-after-imports = 2
41
+ force-single-line = true
42
+ force-sort-within-sections = true
43
+ order-by-type = false
File without changes
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import optuna
4
+
5
+
6
+ optuna.logging.set_verbosity(optuna.logging.WARNING)