From 0d22aab33f0b3b4a542e9fdde98e73fd88a562a4 Mon Sep 17 00:00:00 2001 From: Matt Drozt Date: Wed, 20 May 2026 16:39:30 -0500 Subject: [PATCH 1/4] Make default path for Model/Ensemble/Orchestrator the current working directory at runtime --- smartsim/database/orchestrator.py | 4 ++-- smartsim/entity/ensemble.py | 9 +++++++-- smartsim/entity/model.py | 4 ++-- tests/test_ensemble.py | 23 +++++++++++++++++++++++ tests/test_model.py | 22 ++++++++++++++++++++++ tests/test_orchestrator.py | 23 +++++++++++++++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/smartsim/database/orchestrator.py b/smartsim/database/orchestrator.py index 25ec48f4e..acdaf775e 100644 --- a/smartsim/database/orchestrator.py +++ b/smartsim/database/orchestrator.py @@ -163,7 +163,7 @@ class Orchestrator(EntityList[DBNode]): def __init__( self, - path: str | None = getcwd(), + path: str | None = None, port: int = 6379, interface: str | list[str] = "lo", launcher: str = "local", @@ -229,7 +229,7 @@ def __init__( super().__init__( name=db_identifier, - path=str(path), + path=path if path is not None else getcwd(), port=port, interface=interface, db_nodes=db_nodes, diff --git a/smartsim/entity/ensemble.py b/smartsim/entity/ensemble.py index 8ec9a0c0a..8e260d85f 100644 --- a/smartsim/entity/ensemble.py +++ b/smartsim/entity/ensemble.py @@ -62,7 +62,7 @@ def __init__( self, name: str, params: dict[str, t.Any], - path: str | None = getcwd(), + path: str | None = None, params_as_args: list[str] | None = None, batch_settings: BatchSettings | None = None, run_settings: RunSettings | None = None, @@ -96,7 +96,12 @@ def __init__( self.run_settings = run_settings self.replicas: str - super().__init__(name, str(path), perm_strat=perm_strat, **kwargs) + super().__init__( + name, + path if path is not None else getcwd(), + perm_strat=perm_strat, + **kwargs, + ) @property def models(self) -> Collection[Model]: diff --git a/smartsim/entity/model.py b/smartsim/entity/model.py index 93ed9f3c5..fabda927f 100644 --- a/smartsim/entity/model.py +++ b/smartsim/entity/model.py @@ -74,7 +74,7 @@ def __init__( name: str, params: dict[str, str], run_settings: RunSettings, - path: str | None = getcwd(), + path: str | None = None, params_as_args: list[str] | None = None, batch_settings: BatchSettings | None = None, ): @@ -91,7 +91,7 @@ def __init__( :param batch_settings: Launcher settings for running the individual model as a batch job """ - super().__init__(name, str(path), run_settings) + super().__init__(name, path if path is not None else getcwd(), run_settings) self.params = _parse_model_parameters(params) self.params_as_args = params_as_args self.incoming_entities: list[SmartSimEntity] = [] diff --git a/tests/test_ensemble.py b/tests/test_ensemble.py index 0830914f5..c54fd80f0 100644 --- a/tests/test_ensemble.py +++ b/tests/test_ensemble.py @@ -25,6 +25,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import contextlib +import os +import os.path from copy import deepcopy import pytest @@ -303,3 +306,23 @@ def test_ensemble_type(test_dir): ens_settings = RunSettings("python") ensemble = exp.create_ensemble("name", replicas=4, run_settings=ens_settings) assert ensemble.type == "Ensemble" + + +@pytest.mark.parametrize( + "kwargs", + [ + pytest.param({}, id="Default Argument"), + pytest.param({"path": None}, id="Explicit `path=None`"), + ], +) +def test_ensemble_path_defaults_to_cwd(test_dir, kwargs): + rs = RunSettings("echo", exe_args=["spam", "eggs"]) + test_subdir = os.path.join(test_dir, "subdir") + os.mkdir(test_subdir) + with contextlib.chdir(test_dir): + e1 = Ensemble("root-ens", {}, run_settings=rs, replicas=3, **kwargs) + with contextlib.chdir(test_subdir): + e2 = Ensemble("subdir-ens", {}, run_settings=rs, replicas=3, **kwargs) + assert e1.path == test_dir + assert e2.path == test_subdir + assert all(os.path.join(e.path, m.name) == m.path for e in (e1, e2) for m in e) diff --git a/tests/test_model.py b/tests/test_model.py index 9e60be575..45165ce39 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -24,6 +24,9 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import contextlib +import os +import os.path from uuid import uuid4 import numpy as np @@ -196,3 +199,22 @@ def test_good_model_params(dtype): def test_bad_model_params(bad_val): with pytest.raises(TypeError): _parse_model_parameters({"foo": bad_val}) + + +@pytest.mark.parametrize( + "kwargs", + [ + pytest.param({}, id="Default Argument"), + pytest.param({"path": None}, id="Explicit `path=None`"), + ], +) +def test_model_path_defaults_to_cwd(test_dir, kwargs): + rs = RunSettings("echo", exe_args=["spam", "eggs"]) + test_subdir = os.path.join(test_dir, "subdir") + os.mkdir(test_subdir) + with contextlib.chdir(test_dir): + m1 = Model("root-model", {}, run_settings=rs, **kwargs) + with contextlib.chdir(test_subdir): + m2 = Model("subdir-model", {}, run_settings=rs, **kwargs) + assert m1.path == test_dir + assert m2.path == test_subdir diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 7e992f3ad..330ead8b7 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -25,6 +25,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import contextlib +import os +import os.path import typing as t import psutil @@ -119,6 +122,26 @@ def test_multiple_interfaces( exp.stop(db) +@pytest.mark.parametrize( + "kwargs", + [ + pytest.param({}, id="Default Argument"), + pytest.param({"path": None}, id="Explicit `path=None`"), + ], +) +def test_orchestrator_path_defaults_to_cwd(test_dir, kwargs): + test_subdir = os.path.join(test_dir, "subdir") + os.mkdir(test_subdir) + with contextlib.chdir(test_dir): + db1 = Orchestrator(launcher="local", **kwargs) + with contextlib.chdir(test_subdir): + db2 = Orchestrator(launcher="local", **kwargs) + + assert db1.path == test_dir + assert db2.path == test_subdir + assert all(db.path == n.path for db in (db1, db2) for n in db) + + def test_catch_local_db_errors() -> None: # local database with more than one node not allowed with pytest.raises(SSUnsupportedError): From 7edc93b928c9e4cb0f6469f4f61339adf4f464ed Mon Sep 17 00:00:00 2001 From: Matt Drozt Date: Wed, 20 May 2026 18:55:29 -0500 Subject: [PATCH 2/4] Static changes in linter config for pylint v4 --- .pylintrc | 4 ---- smartsim/entity/entityList.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.pylintrc b/.pylintrc index 15f970157..5f70c7dce 100644 --- a/.pylintrc +++ b/.pylintrc @@ -32,10 +32,6 @@ jobs=4 # Pickle collected data for later comparisons. persistent=yes -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - # Ignore problematic extensions extension-pkg-whitelist=pydantic, diff --git a/smartsim/entity/entityList.py b/smartsim/entity/entityList.py index 1eccc470c..a01ecfbfe 100644 --- a/smartsim/entity/entityList.py +++ b/smartsim/entity/entityList.py @@ -34,7 +34,7 @@ import smartsim _T = t.TypeVar("_T", bound=SmartSimEntity) -# Old style pyint from TF 2.6.x does not know about pep484 style ``TypeVar`` names +# Old style pylint from TF 2.6.x does not know about pep484 style ``TypeVar`` names # pylint: disable-next=invalid-name _T_co = t.TypeVar("_T_co", bound=SmartSimEntity, covariant=True) From fa0d3263e43cffc96687e666c7e3053535f8439c Mon Sep 17 00:00:00 2001 From: Matt Drozt Date: Wed, 20 May 2026 19:07:22 -0500 Subject: [PATCH 3/4] Changelog --- doc/changelog.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/changelog.md b/doc/changelog.md index 1665c069f..d8d95789d 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -26,6 +26,8 @@ Description - Add instructions for installing SmartSim on PML's Scylla - Drop unsued development dependencies - Fix typos in documentation +- Changed default path when manually constructing `Model`s, `Ensemble`s and + `Orchestrator`s to the current working directory Detailed Notes @@ -108,6 +110,14 @@ Detailed Notes - Removes an Numpy upper bound in the SmartSim dependency list now that SmartRedis and supported ML backends support Numpy 2.0. ([SmartSim-PR803](https://github.com/CrayLabs/SmartSim/pull/803)) +- When constructing a `Model`, `Ensemble`, or `Orchestrator`, the `__init__` + method was type hinted as able to accept a `None` value. This was supposed to + act as a sentinel that would default to the current working directory. There + was a bug in how this resolved such that if a `path` argument was not + provided it would resolve to the working directory at import time and if + `path` was explicitly passed in as `None` it would resolve to the string + `"None"` this has since been corrected to the expected behavior. + ([SmartSim-PR808](https://github.com/CrayLabs/SmartSim/pull/808)) ### 0.8.0 From befa872dca401c9e95038536f2328fde12f9fb14 Mon Sep 17 00:00:00 2001 From: Matt Drozt Date: Thu, 21 May 2026 12:14:06 -0500 Subject: [PATCH 4/4] Remove `contextlib.chdir` in tests for Python 3.10 --- tests/test_ensemble.py | 11 ++++++----- tests/test_model.py | 11 ++++++----- tests/test_orchestrator.py | 12 ++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/test_ensemble.py b/tests/test_ensemble.py index c54fd80f0..de31a3679 100644 --- a/tests/test_ensemble.py +++ b/tests/test_ensemble.py @@ -25,7 +25,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import contextlib import os import os.path from copy import deepcopy @@ -315,14 +314,16 @@ def test_ensemble_type(test_dir): pytest.param({"path": None}, id="Explicit `path=None`"), ], ) -def test_ensemble_path_defaults_to_cwd(test_dir, kwargs): +def test_ensemble_path_defaults_to_cwd(monkeypatch, test_dir, kwargs): rs = RunSettings("echo", exe_args=["spam", "eggs"]) test_subdir = os.path.join(test_dir, "subdir") os.mkdir(test_subdir) - with contextlib.chdir(test_dir): + with monkeypatch.context() as ctx: + ctx.chdir(test_dir) e1 = Ensemble("root-ens", {}, run_settings=rs, replicas=3, **kwargs) - with contextlib.chdir(test_subdir): - e2 = Ensemble("subdir-ens", {}, run_settings=rs, replicas=3, **kwargs) + with monkeypatch.context() as ctx: + ctx.chdir(test_subdir) + e2 = Ensemble("subdir-ens", {}, run_settings=rs, replicas=3, **kwargs) assert e1.path == test_dir assert e2.path == test_subdir assert all(os.path.join(e.path, m.name) == m.path for e in (e1, e2) for m in e) diff --git a/tests/test_model.py b/tests/test_model.py index 45165ce39..5b0455267 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -24,7 +24,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import contextlib import os import os.path from uuid import uuid4 @@ -208,13 +207,15 @@ def test_bad_model_params(bad_val): pytest.param({"path": None}, id="Explicit `path=None`"), ], ) -def test_model_path_defaults_to_cwd(test_dir, kwargs): +def test_model_path_defaults_to_cwd(monkeypatch, test_dir, kwargs): rs = RunSettings("echo", exe_args=["spam", "eggs"]) test_subdir = os.path.join(test_dir, "subdir") os.mkdir(test_subdir) - with contextlib.chdir(test_dir): + with monkeypatch.context() as ctx: + ctx.chdir(test_dir) m1 = Model("root-model", {}, run_settings=rs, **kwargs) - with contextlib.chdir(test_subdir): - m2 = Model("subdir-model", {}, run_settings=rs, **kwargs) + with monkeypatch.context() as ctx: + ctx.chdir(test_subdir) + m2 = Model("subdir-model", {}, run_settings=rs, **kwargs) assert m1.path == test_dir assert m2.path == test_subdir diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 330ead8b7..afaabf4e4 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -25,7 +25,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import contextlib import os import os.path import typing as t @@ -129,14 +128,15 @@ def test_multiple_interfaces( pytest.param({"path": None}, id="Explicit `path=None`"), ], ) -def test_orchestrator_path_defaults_to_cwd(test_dir, kwargs): +def test_orchestrator_path_defaults_to_cwd(monkeypatch, test_dir, kwargs): test_subdir = os.path.join(test_dir, "subdir") os.mkdir(test_subdir) - with contextlib.chdir(test_dir): + with monkeypatch.context() as ctx: + ctx.chdir(test_dir) db1 = Orchestrator(launcher="local", **kwargs) - with contextlib.chdir(test_subdir): - db2 = Orchestrator(launcher="local", **kwargs) - + with monkeypatch.context() as ctx: + ctx.chdir(test_subdir) + db2 = Orchestrator(launcher="local", **kwargs) assert db1.path == test_dir assert db2.path == test_subdir assert all(db.path == n.path for db in (db1, db2) for n in db)