Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
8a07c58
start using datetimes with fractional seconds with mysql
bpkroth May 28, 2025
d6fc9e2
adjust tests to also check for fractional time
bpkroth May 28, 2025
65e7f08
Revert "start using datetimes with fractional seconds with mysql"
bpkroth May 28, 2025
b7dad42
Reapply "start using datetimes with fractional seconds with mysql"
bpkroth May 28, 2025
4bf0c5f
apply black on alembic commit
bpkroth May 28, 2025
8088734
preparing to support testing multiple backend engines for schema changes
bpkroth May 28, 2025
a2c3256
refactoring storage tests to check other db engines
bpkroth May 28, 2025
5d3f0d3
change column lengths for mysql
bpkroth May 28, 2025
01b9df4
more refactor of storage tests
bpkroth May 28, 2025
d578176
preparing to support testing multiple backend engines for schema changes
bpkroth May 28, 2025
3b3bf6c
refactoring storage tests to check other db engines
bpkroth May 28, 2025
1c9633c
change column lengths for mysql
bpkroth May 28, 2025
f5b7bf3
fixups
bpkroth May 28, 2025
7fdf06d
fixup
bpkroth May 28, 2025
1bf3c30
fixup
bpkroth May 28, 2025
bd5862f
cleanup
bpkroth May 28, 2025
a5e9c05
Merge branch 'refactor/storage-tests' into feature/mysql-schema-chang…
bpkroth May 28, 2025
67f84ea
fixup
bpkroth May 28, 2025
41199fc
format
bpkroth May 28, 2025
ef33825
Merge branch 'refactor/storage-tests' into feature/mysql-schema-chang…
bpkroth May 28, 2025
ee4a900
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 28, 2025
82220ec
fixup
bpkroth May 28, 2025
aa2621b
fixup
bpkroth May 28, 2025
a84d30b
switch to interprocesslock - already using that
bpkroth May 29, 2025
2341a10
address a lint issue
bpkroth May 29, 2025
45ad11f
restore the original alembic comments - moving to separate PR
bpkroth May 29, 2025
cfa07ea
Merge branch 'refactor/storage-tests' into feature/mysql-schema-chang…
bpkroth May 29, 2025
bbcf689
Revert "restore the original alembic comments - moving to separate PR"
bpkroth May 29, 2025
53ee6e2
more comments
bpkroth May 29, 2025
994a32a
mypy
bpkroth May 29, 2025
2faf643
pylint
bpkroth May 29, 2025
a6b8941
allow retrieving storage url from the environment
bpkroth May 29, 2025
9c5ac14
more alembic tweaks
bpkroth May 29, 2025
988bc90
remove env
bpkroth May 29, 2025
f720d0d
temporarily revert back to something like the original schema
bpkroth May 29, 2025
f250e61
Revert "temporarily revert back to something like the original schema"
bpkroth May 29, 2025
793c2e5
Reapply "temporarily revert back to something like the original schema"
bpkroth May 29, 2025
952fba0
include timezone
bpkroth May 29, 2025
d6617ba
make mysql datetimes support fractional seconds
bpkroth May 29, 2025
2f28d79
log the alembic target engine url
bpkroth May 29, 2025
1b0fb2c
engine no longer optional
bpkroth May 29, 2025
928f491
Revert "make mysql datetimes support fractional seconds"
bpkroth May 29, 2025
4863a5b
Enable alembic to detect datetime precision issues with MySQL
bpkroth Jun 2, 2025
6a697a0
Enable floating point seconds with mysql
bpkroth Jun 2, 2025
f7ccf26
Alembic script to add floating point seconds precision
bpkroth Jun 2, 2025
40374c2
fixup
bpkroth Jun 2, 2025
fe4171a
rework to only apply to mysql
bpkroth Jun 2, 2025
5a42a14
be sure to mark that version as required
bpkroth Jun 2, 2025
f273ea3
add that refactor too
bpkroth Jun 2, 2025
b1b3733
Fixups and refactors to allow two things
bpkroth Jun 2, 2025
f632f90
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 2, 2025
565ac4d
Merge branch 'main' into fixup-refactor/allow-git-repo-root-env-resol…
bpkroth Jun 5, 2025
bf13366
copilot fixups
bpkroth Jun 5, 2025
9df032c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2025
07140b6
Merge branch 'main' into fixup-refactor/allow-git-repo-root-env-resol…
bpkroth Jun 13, 2025
e001d9a
merge cleanup
bpkroth Jun 13, 2025
c46f16a
unused-imnport
bpkroth Jun 13, 2025
8560416
bad merge
bpkroth Jun 23, 2025
f451e2b
add a test for non-git dirs, refactor to reduce duplicate code
bpkroth Jun 23, 2025
32a18fe
docs
bpkroth Jun 23, 2025
19fefde
test
bpkroth Jun 23, 2025
4041723
improvements on git info handling
bpkroth Jun 23, 2025
1757b7e
cleanup
bpkroth Jun 23, 2025
38ed63e
style
bpkroth Jun 23, 2025
7903c41
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 23, 2025
6838d45
mypy
bpkroth Jun 24, 2025
e9bf5f2
test tweak
bpkroth Jun 24, 2025
8263484
fixup
bpkroth Jun 24, 2025
3c10846
refactor
bpkroth Jun 24, 2025
0cb5948
fixup
bpkroth Jun 24, 2025
59050ce
start to disable the lookup logic for abs config path
bpkroth Jun 24, 2025
062fad7
error handling
bpkroth Jun 24, 2025
4bf6b88
remove resolution logic
bpkroth Jun 24, 2025
e5374e0
docs
bpkroth Jun 24, 2025
81dcea0
imports
bpkroth Jun 24, 2025
948c3d5
mypy
bpkroth Jun 24, 2025
7179301
cleanup
bpkroth Jun 24, 2025
43c5887
notes
bpkroth Jun 24, 2025
82c6b8b
rename
bpkroth Jun 24, 2025
a1eb6de
Fixups and improvements to git info retrieval
bpkroth Jun 24, 2025
d0f8e74
comments
bpkroth Jun 24, 2025
f7d91a7
tweaks
bpkroth Jun 24, 2025
739c3ce
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2025
a0dd7b7
tweak
bpkroth Jun 24, 2025
942d57b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2025
6d563a8
refactor
bpkroth Jun 24, 2025
4e33efb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2025
a93b93e
Merge branch 'fixup/git-info' into fixup-refactor/allow-git-repo-root…
bpkroth Jun 24, 2025
5f13ff9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2025
8f8f0bb
comment
bpkroth Jun 24, 2025
d009fe8
Merge branch 'fixup/git-info' into fixup-refactor/allow-git-repo-root…
bpkroth Jun 24, 2025
2257e17
format
bpkroth Jun 24, 2025
718f66e
Merge branch 'fixup/git-info' into fixup-refactor/allow-git-repo-root…
bpkroth Jun 24, 2025
01e844a
comments
bpkroth Jun 24, 2025
0357849
fixup
bpkroth Jun 24, 2025
8119ef4
Merge branch 'fixup/git-info' into fixup-refactor/allow-git-repo-root…
bpkroth Jun 24, 2025
c7245a2
fix docs
bpkroth Jun 24, 2025
1874b0e
Merge branch 'fixup/git-info' into fixup-refactor/allow-git-repo-root…
bpkroth Jun 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 86 additions & 8 deletions mlos_bench/mlos_bench/storage/base_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from collections.abc import Iterator, Mapping
from contextlib import AbstractContextManager as ContextManager
from datetime import datetime
from subprocess import CalledProcessError
from types import TracebackType
from typing import Any, Literal

Expand Down Expand Up @@ -187,15 +188,62 @@ def __init__( # pylint: disable=too-many-arguments
tunables: TunableGroups,
experiment_id: str,
trial_id: int,
root_env_config: str,
root_env_config: str | None,
description: str,
opt_targets: dict[str, Literal["min", "max"]],
git_repo: str | None = None,
git_commit: str | None = None,
git_rel_root_env_config: str | None = None,
):
self._tunables = tunables.copy()
self._trial_id = trial_id
self._experiment_id = experiment_id
(self._git_repo, self._git_commit, self._root_env_config) = get_git_info(
root_env_config
self._abs_root_env_config: str | None
if root_env_config is not None:
if git_repo or git_commit or git_rel_root_env_config:
# Extra args are only used when restoring an Experiment from the DB.
raise ValueError("Unexpected args: git_repo, git_commit, rel_root_env_config")
try:
(
self._git_repo,
self._git_commit,
self._git_rel_root_env_config,
self._abs_root_env_config,
) = get_git_info(root_env_config)
except CalledProcessError as e:
# Note: currently the Experiment schema requires git
# metadata to be set. We *could* set the git metadata to
# dummy values, but for now we just throw an error.
_LOG.warning(
"Failed to get git info for root_env_config %s: %s",
root_env_config,
e,
)
raise e
else:
# Restoring from DB.
if not (git_repo and git_commit and git_rel_root_env_config):
raise ValueError("Missing args: git_repo, git_commit, rel_root_env_config")
self._git_repo = git_repo
self._git_commit = git_commit
self._git_rel_root_env_config = git_rel_root_env_config
# Note: The absolute path to the root config is not stored in the DB,
# and resolving it is not always possible, so we omit this
# operation by default for now.
# See commit 0cb5948865662776e92ceaca3f0a80a34c6a39ef in
# <https://github.com/microsoft/MLOS/pull/985> for prior
# implementation attempts.
self._abs_root_env_config = None
assert isinstance(
self._git_rel_root_env_config, str
), "Failed to get relative root config path"
_LOG.info(
"Resolved relative root_config %s from %s at commit %s for Experiment %s to %s",
self._git_rel_root_env_config,
self._git_repo,
self._git_commit,
self._experiment_id,
self._abs_root_env_config,
)
self._description = description
self._opt_targets = opt_targets
Expand All @@ -205,6 +253,8 @@ def __enter__(self) -> Storage.Experiment:
"""
Enter the context of the experiment.

Notes
-----
Override the `_setup` method to add custom context initialization.
"""
_LOG.debug("Starting experiment: %s", self)
Expand All @@ -222,6 +272,8 @@ def __exit__(
"""
End the context of the experiment.

Notes
-----
Override the `_teardown` method to add custom context teardown logic.
"""
is_ok = exc_val is None
Expand All @@ -247,14 +299,14 @@ def _setup(self) -> None:
Create a record of the new experiment or find an existing one in the
storage.

This method is called by `Storage.Experiment.__enter__()`.
This method is called by :py:class:`.Storage.Experiment.__enter__()`.
"""

def _teardown(self, is_ok: bool) -> None:
"""
Finalize the experiment in the storage.

This method is called by `Storage.Experiment.__exit__()`.
This method is called by :py:class:`.Storage.Experiment.__exit__()`.

Parameters
----------
Expand All @@ -278,9 +330,35 @@ def description(self) -> str:
return self._description

@property
def root_env_config(self) -> str:
"""Get the Experiment's root Environment config file path."""
return self._root_env_config
def rel_root_env_config(self) -> str:
"""Get the Experiment's root Environment config's relative file path to the
git repo root.
"""
return self._git_rel_root_env_config

@property
def abs_root_env_config(self) -> str | None:
"""
Get the Experiment's root Environment config absolute file path.

This attempts to return the current absolute path to the root config
for this process instead of the path relative to the git repo root.

However, this may not always be possible if the git repo root is not
accessible, which can happen if the Experiment was restored from the
DB, but the process was started from a different working directory,
for instance.

Notes
-----
This is mostly useful for other components (e.g.,
:py:class:`~mlos_bench.schedulers.base_scheduler.Scheduler`) to use
within the same process, and not across invocations.
"""
# TODO: In the future, we can consider fetching the git_repo to a
# standard working directory for ``mlos_bench`` and then resolving
# the root config path from there based on the relative path.
return self._abs_root_env_config

@property
def tunables(self) -> TunableGroups:
Expand Down
10 changes: 8 additions & 2 deletions mlos_bench/mlos_bench/storage/sql/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ def __init__( # pylint: disable=too-many-arguments
tunables: TunableGroups,
experiment_id: str,
trial_id: int,
root_env_config: str,
root_env_config: str | None,
description: str,
opt_targets: dict[str, Literal["min", "max"]],
git_repo: str | None = None,
git_commit: str | None = None,
git_rel_root_env_config: str | None = None,
):
super().__init__(
tunables=tunables,
Expand All @@ -49,6 +52,9 @@ def __init__( # pylint: disable=too-many-arguments
root_env_config=root_env_config,
description=description,
opt_targets=opt_targets,
git_repo=git_repo,
git_commit=git_commit,
git_rel_root_env_config=git_rel_root_env_config,
)
self._engine = engine
self._schema = schema
Expand Down Expand Up @@ -89,7 +95,7 @@ def _setup(self) -> None:
description=self._description,
git_repo=self._git_repo,
git_commit=self._git_commit,
root_env_config=self._root_env_config,
root_env_config=self._git_rel_root_env_config,
)
)
conn.execute(
Expand Down
6 changes: 5 additions & 1 deletion mlos_bench/mlos_bench/storage/sql/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,13 @@ def get_experiment_by_id(
experiment_id=exp.exp_id,
trial_id=-1, # will be loaded upon __enter__ which calls _setup()
description=exp.description,
root_env_config=exp.root_env_config,
# Use special logic to load the experiment root config info directly.
root_env_config=None,
tunables=tunables,
opt_targets=opt_targets,
git_repo=exp.git_repo,
git_commit=exp.git_commit,
git_rel_root_env_config=exp.root_env_config,
)

def experiment( # pylint: disable=too-many-arguments
Expand Down
2 changes: 1 addition & 1 deletion mlos_bench/mlos_bench/tests/storage/exp_data_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_exp_data_root_env_config(
"""Tests the root_env_config property of ExperimentData."""
# pylint: disable=protected-access
assert exp_data.root_env_config == (
exp_storage._root_env_config,
exp_storage._git_rel_root_env_config,
exp_storage._git_repo,
exp_storage._git_commit,
)
Expand Down
4 changes: 2 additions & 2 deletions mlos_bench/mlos_bench/tests/storage/sql/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def exp_storage(
with storage.experiment(
experiment_id="Test-001",
trial_id=1,
root_env_config="environment.jsonc",
root_env_config="my-environment.jsonc",
description="pytest experiment",
tunables=tunable_groups,
opt_targets={"score": "min"},
Expand Down Expand Up @@ -375,7 +375,7 @@ def _dummy_run_exp(
trial_runners=trial_runners,
optimizer=opt,
storage=storage,
root_env_config=exp.root_env_config,
root_env_config=exp.abs_root_env_config or "ERROR-UNKNOWN.jsonc",
)

# Add some trial data to that experiment by "running" it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
# See Also: schema.py for an example of programmatic alembic config access.
CURRENT_ALEMBIC_HEAD = "b61aa446e724"

# Try to test multiple DBMS engines.


# Try to test multiple DBMS engines.
@pytest.mark.parametrize(
"some_sql_storage_fixture",
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_storage_pickle_restore_experiment_and_trial(
assert restored_experiment is not experiment
assert restored_experiment.experiment_id == experiment.experiment_id
assert restored_experiment.description == experiment.description
assert restored_experiment.root_env_config == experiment.root_env_config
assert restored_experiment.rel_root_env_config == experiment.rel_root_env_config
assert restored_experiment.tunables == experiment.tunables
assert restored_experiment.opt_targets == experiment.opt_targets
with restored_experiment:
Expand Down
87 changes: 84 additions & 3 deletions mlos_bench/mlos_bench/tests/util_git_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,95 @@
# Licensed under the MIT License.
#
"""Unit tests for get_git_info utility function."""
import os
import re
import tempfile
from pathlib import Path
from subprocess import CalledProcessError
from subprocess import check_call as run

from mlos_bench.util import get_git_info
import pytest

from mlos_bench.util import get_git_info, get_git_root, path_join


def test_get_git_info() -> None:
"""Check that we can retrieve git info about the current repository correctly."""
(git_repo, git_commit, rel_path) = get_git_info(__file__)
"""Check that we can retrieve git info about the current repository correctly from a
file.
"""
(git_repo, git_commit, rel_path, abs_path) = get_git_info(__file__)
assert "mlos" in git_repo.lower()
assert re.match(r"[0-9a-f]{40}", git_commit) is not None
assert rel_path == "mlos_bench/mlos_bench/tests/util_git_test.py"
assert abs_path == path_join(__file__, abs_path=True)


def test_get_git_info_dir() -> None:
"""Check that we can retrieve git info about the current repository correctly from a
directory.
"""
dirname = os.path.dirname(__file__)
(git_repo, git_commit, rel_path, abs_path) = get_git_info(dirname)
assert "mlos" in git_repo.lower()
assert re.match(r"[0-9a-f]{40}", git_commit) is not None
assert rel_path == "mlos_bench/mlos_bench/tests"
assert abs_path == path_join(dirname, abs_path=True)


def test_non_git_dir() -> None:
"""Check that we can handle a non-git directory."""
with tempfile.TemporaryDirectory() as non_git_dir:
with pytest.raises(CalledProcessError):
# This should raise an error because the directory is not a git repository.
get_git_root(non_git_dir)


def test_non_upstream_git() -> None:
"""Check that we can handle a git directory without an upstream."""
with tempfile.TemporaryDirectory() as local_git_dir:
local_git_dir = path_join(local_git_dir, abs_path=True)
# Initialize a new git repository.
run(["git", "init", local_git_dir, "-b", "main"])
run(["git", "-C", local_git_dir, "config", "--local", "user.email", "[email protected]"])
run(["git", "-C", local_git_dir, "config", "--local", "user.name", "PyTest User"])
Path(local_git_dir).joinpath("README.md").touch()
run(["git", "-C", local_git_dir, "add", "README.md"])
run(["git", "-C", local_git_dir, "commit", "-m", "Initial commit"])
# This should have slightly different behavior when there is no upstream.
(git_repo, _git_commit, rel_path, abs_path) = get_git_info(local_git_dir)
assert git_repo == f"file://{local_git_dir}"
assert abs_path == local_git_dir
assert rel_path == "."


@pytest.mark.skipif(
os.environ.get("GITHUB_ACTIONS") != "true",
reason="Not running in GitHub Actions CI.",
)
def test_github_actions_git_info() -> None:
"""
Test that get_git_info matches GitHub Actions environment variables if running in
CI.

Examples
--------
Test locally with the following command:

.. code-block:: shell

export GITHUB_ACTIONS=true
export GITHUB_SHA=$(git rev-parse HEAD)
# GITHUB_REPOSITORY should be in "owner/repo" format.
# e.g., GITHUB_REPOSITORY="bpkroth/MLOS" or "microsoft/MLOS"
export GITHUB_REPOSITORY=$(git rev-parse --abbrev-ref --symbolic-full-name HEAD@{u} | cut -d/ -f1 | xargs git remote get-url | grep https://github.com | cut -d/ -f4-)
pytest -n0 mlos_bench/mlos_bench/tests/util_git_test.py
""" # pylint: disable=line-too-long # noqa: E501
repo_env = os.environ.get("GITHUB_REPOSITORY") # "owner/repo" format
sha_env = os.environ.get("GITHUB_SHA")
assert repo_env, "GITHUB_REPOSITORY not set in environment."
assert sha_env, "GITHUB_SHA not set in environment."
git_repo, git_commit, _rel_path, _abs_path = get_git_info(__file__)
assert git_repo.endswith(repo_env), f"git_repo '{git_repo}' does not end with '{repo_env}'"
assert (
git_commit == sha_env
), f"git_commit '{git_commit}' does not match GITHUB_SHA '{sha_env}'"
Loading
Loading