From 52c701c07b3ff3e7da5f740c6c0563c41fee0ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Mon, 21 Sep 2026 16:12:52 +0100 Subject: [PATCH 1/6] Created metadata configuration option for enabling/disabling --- simvue/config/parameters.py | 24 +++++++++++++++++++----- simvue/metadata.py | 23 +++++++++++++---------- simvue/run.py | 18 ++++++++++++------ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/simvue/config/parameters.py b/simvue/config/parameters.py index d32406c0..60e3088a 100644 --- a/simvue/config/parameters.py +++ b/simvue/config/parameters.py @@ -18,12 +18,25 @@ logger = logging.getLogger(__name__) +BASE_MODEL_CONFIG = pydantic.ConfigDict(extra="forbid", strict=True) + + +class MetadataSpecifications(pydantic.BaseModel): + environment: bool = True + git: bool = True + system: bool = True + record_shell_vars: list[str] | None = None + custom: dict[str, str | int | float | bool] | None = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG + class CertificateSpecifications(pydantic.BaseModel): storage_ca_cert: pydantic.FilePath | bool = True server_ca_cert: pydantic.FilePath | bool = True client_cert: pydantic.FilePath | None = None client_key: pydantic.SecretStr | None = None + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG @pydantic.model_validator(mode="before") @classmethod @@ -43,10 +56,7 @@ def check_for_cert_env( class ServerSpecifications(pydantic.BaseModel): - model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( - extra="forbid", - strict=True, - ) + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG url: pydantic.AnyHttpUrl | None token: pydantic.SecretStr | None env: dict[str, str] | None = None @@ -78,6 +88,7 @@ def check_token(cls, v: pydantic.SecretStr | None) -> pydantic.SecretStr | None: class OfflineSpecifications(pydantic.BaseModel): cache: pathlib.Path | None = None + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG @pydantic.field_validator("cache") @classmethod @@ -94,6 +105,7 @@ def check_valid_cache_path(cls, cache: pathlib.Path) -> pathlib.Path: class MetricsSpecifications(pydantic.BaseModel): system_metrics_interval: pydantic.PositiveInt | None = -1 enable_emission_metrics: bool = False + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG class DefaultRunSpecifications(pydantic.BaseModel): @@ -101,10 +113,12 @@ class DefaultRunSpecifications(pydantic.BaseModel): description: str | None = None tags: list[str] | None = None folder: str = pydantic.Field(default="/", pattern=sv_models.FOLDER_REGEX) - metadata: dict[str, str | int | float | bool] | None = None + metadata: MetadataSpecifications = MetadataSpecifications() mode: typing.Literal["offline", "disabled", "online"] = "online" record_shell_vars: list[str] | None = None + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG class ClientGeneralOptions(pydantic.BaseModel): debug: bool = False + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG diff --git a/simvue/metadata.py b/simvue/metadata.py index 8c7e5714..e5815ec5 100644 --- a/simvue/metadata.py +++ b/simvue/metadata.py @@ -270,7 +270,7 @@ def _node_js_env(repository: pathlib.Path) -> dict[str, typing.Any]: return js_meta -def _environment_variables(glob_exprs: list[str]) -> dict[str, str]: +def _environment_variables(glob_exprs: set[str]) -> dict[str, str]: """Retrieve values for environment variables.""" _env_vars: list[str] = list(os.environ.keys()) _metadata: dict[str, str] = {} @@ -283,20 +283,23 @@ def _environment_variables(glob_exprs: list[str]) -> dict[str, str]: def environment( + *, repository: pathlib.Path | None = None, + record_repo_environment: bool = True, env_var_glob_exprs: set[str] | None = None, ) -> dict[str, typing.Any]: """Retrieve environment metadata.""" _environment_meta = {} - _repository: pathlib.Path = repository or pathlib.Path.cwd() - if _python_meta := _python_env(_repository): - _environment_meta["python"] = _python_meta - if _rust_meta := _rust_env(_repository): - _environment_meta["rust"] = _rust_meta - if _julia_meta := _julia_env(_repository): - _environment_meta["julia"] = _julia_meta - if _js_meta := _node_js_env(_repository): - _environment_meta["javascript"] = _js_meta + if record_repo_environment: + _repository: pathlib.Path = repository or pathlib.Path.cwd() + if _python_meta := _python_env(_repository): + _environment_meta["python"] = _python_meta + if _rust_meta := _rust_env(_repository): + _environment_meta["rust"] = _rust_meta + if _julia_meta := _julia_env(_repository): + _environment_meta["julia"] = _julia_meta + if _js_meta := _node_js_env(_repository): + _environment_meta["javascript"] = _js_meta if env_var_glob_exprs: _environment_meta["shell"] = _environment_variables(env_var_glob_exprs) return _environment_meta diff --git a/simvue/run.py b/simvue/run.py index 5c69a7a0..0e649dd0 100644 --- a/simvue/run.py +++ b/simvue/run.py @@ -741,7 +741,7 @@ def init( tags = (tags or []) + (self._user_config.run.tags or []) folder = folder or self._user_config.run.folder name = name or self._user_config.run.name - metadata = (metadata or {}) | (self._user_config.run.metadata or {}) + metadata = (metadata or {}) | (self._user_config.run.metadata.custom or {}) record_shell_vars = record_shell_vars or self._user_config.run.record_shell_vars self._term_color = not no_color @@ -809,17 +809,23 @@ def init( self._sv_obj.ttl = self._retention self._sv_obj.status = self._status self._sv_obj.tags = tags - self._sv_obj.metadata = ( - (metadata or {}) - | git_info(pathlib.Path.cwd()) - | environment(env_var_glob_exprs=record_shell_vars) + + _session_metadata = metadata or {} + + _session_metadata |= environment( + env_var_glob_exprs=record_shell_vars, + record_repo_environment=self._user_config.run.metadata.environment, ) + + if self._user_config.run.metadata.git: + _session_metadata |= git_info(pathlib.Path.cwd()) + self._sv_obj.heartbeat_timeout = timeout self._sv_obj.alerts = [] self._sv_obj.created = time.time() self._sv_obj.notifications = notification - if self._status == "running": + if self._status == "running" and self._user_config.run.metadata.system: self._sv_obj.system = get_system() self._data = self._sv_obj.staging From 412db6458251401712181f79a68673e3b3d6de1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Mon, 21 Sep 2026 16:20:58 +0100 Subject: [PATCH 2/6] Added environment variables for metadata disabling --- simvue/config/parameters.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simvue/config/parameters.py b/simvue/config/parameters.py index 60e3088a..8766f58f 100644 --- a/simvue/config/parameters.py +++ b/simvue/config/parameters.py @@ -22,9 +22,9 @@ class MetadataSpecifications(pydantic.BaseModel): - environment: bool = True - git: bool = True - system: bool = True + environment: bool = os.environ.get("SIMVUE_DISABLE_ENVIRONMENT_METRICS") is None + git: bool = os.environ.get("SIMVUE_DISABLE_GIT_METRICS") is None + system: bool = os.environ.get("SIMVUE_DISABLE_SYSTEM_METRICS") is None record_shell_vars: list[str] | None = None custom: dict[str, str | int | float | bool] | None = None From 886c6a31544736a27622d883c6bf6cfcd9d80c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Mon, 21 Sep 2026 16:23:02 +0100 Subject: [PATCH 3/6] Disable metadata for plain runs in tests --- tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4ed8d58e..2c2418c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -168,8 +168,11 @@ def create_test_run_offline(request, monkeypatch: pytest.MonkeyPatch, prevent_sc @pytest.fixture -def create_plain_run(request, prevent_script_exit, mocker) -> Generator[tuple[sv_run.Run, dict]]: +def create_plain_run(request, prevent_script_exit, mocker, monkeypatch) -> Generator[tuple[sv_run.Run, dict]]: _ = prevent_script_exit + monkeypatch.setenv("SIMVUE_DISABLE_ENVIRONMENT_METRICS", "True") + monkeypatch.setenv("SIMVUE_DISABLE_GIT_METRICS", "True") + monkeypatch.setenv("SIMVUE_DISABLE_SYSTEM_METRICS", "True") def testing_exit(status: int) -> None: raise SystemExit(status) with sv_run.Run() as run: From c935034ddb0296d95c9d9d434f233f978876df50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Tue, 22 Sep 2026 12:58:24 +0100 Subject: [PATCH 4/6] Fix broken config options due to tightened typing --- simvue/config/parameters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/simvue/config/parameters.py b/simvue/config/parameters.py index 8766f58f..d9eb66f5 100644 --- a/simvue/config/parameters.py +++ b/simvue/config/parameters.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -BASE_MODEL_CONFIG = pydantic.ConfigDict(extra="forbid", strict=True) +BASE_MODEL_CONFIG = pydantic.ConfigDict(extra="forbid") class MetadataSpecifications(pydantic.BaseModel): @@ -56,7 +56,9 @@ def check_for_cert_env( class ServerSpecifications(pydantic.BaseModel): - model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG + model_config: typing.ClassVar[pydantic.ConfigDict] = BASE_MODEL_CONFIG | { + "extra": "forbid" + } url: pydantic.AnyHttpUrl | None token: pydantic.SecretStr | None env: dict[str, str] | None = None From ad9942e338ff197c1575909fe1462a7964c44c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Tue, 22 Sep 2026 13:02:17 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=A7=AA=20Fix=20keyword=20argument=20i?= =?UTF-8?q?n=20test=20for=20environments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/functional/test_run_class.py | 2 +- tests/unit/test_metadata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/test_run_class.py b/tests/functional/test_run_class.py index 4b4f84c8..d617d902 100644 --- a/tests/functional/test_run_class.py +++ b/tests/functional/test_run_class.py @@ -1630,7 +1630,7 @@ def test_run_environment_metadata(environment: str, mocker: pytest_mock.MockerFi running=False, visibility="tenant" if os.environ.get("CI") else None, ) - run.update_metadata(env_func(_target_dir)) + run.update_metadata(env_func(repository=_target_dir)) @pytest.mark.online diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index a8e75e1b..e8e948e8 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -52,7 +52,7 @@ def test_js_env() -> None: @pytest.mark.metadata @pytest.mark.local def test_environment() -> None: - metadata = sv_meta.environment(pathlib.Path(__file__).parents[1].joinpath("example_data")) + metadata = sv_meta.environment(repository=pathlib.Path(__file__).parents[1].joinpath("example_data")) assert metadata["python"]["project"]["name"] == "example-repo" assert metadata["rust"]["project"]["name"] == "example_project" assert metadata["julia"]["project"]["name"] == "Julia Demo Project" From 7d99273a48df185971c64d6dceab961983d1d531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Zar=C4=99bski?= Date: Tue, 22 Sep 2026 13:15:19 +0100 Subject: [PATCH 6/6] Fix missing metadata --- simvue/run.py | 1 + tests/functional/test_run_class.py | 3 ++- uv.lock | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/simvue/run.py b/simvue/run.py index 0e649dd0..8378ad1b 100644 --- a/simvue/run.py +++ b/simvue/run.py @@ -824,6 +824,7 @@ def init( self._sv_obj.alerts = [] self._sv_obj.created = time.time() self._sv_obj.notifications = notification + self._sv_obj.metadata = _session_metadata if self._status == "running" and self._user_config.run.metadata.system: self._sv_obj.system = get_system() diff --git a/tests/functional/test_run_class.py b/tests/functional/test_run_class.py index d617d902..b6ba71f3 100644 --- a/tests/functional/test_run_class.py +++ b/tests/functional/test_run_class.py @@ -1566,7 +1566,7 @@ def test_env_var_metadata() -> None: } os.environ.update(_recorded_env) with simvue.Run() as run: - run.init( + _ = run.init( name="test_reconnect", folder="/simvue_unit_testing", retention_period="2 minutes", @@ -1575,6 +1575,7 @@ def test_env_var_metadata() -> None: record_shell_vars={"SIMVUE_RUN_TEST_VAR_*"} ) _recorded_meta = RunObject(identifier=run.id).metadata + assert "shell" in _recorded_meta assert all(key in _recorded_meta.get("shell") for key in _recorded_env) @pytest.mark.run diff --git a/uv.lock b/uv.lock index 5d7e1232..2a18bdcf 100644 --- a/uv.lock +++ b/uv.lock @@ -2101,7 +2101,7 @@ requires-dist = [ { name = "numpy", specifier = ">=2.0.0,<3.0.0" }, { name = "pandas", specifier = ">=2.2.3,<3.0.6" }, { name = "pip", specifier = ">=26.1.2" }, - { name = "plotly", marker = "extra == 'plot'", specifier = ">=6.0.0,<7.0.0" }, + { name = "plotly", marker = "extra == 'plot'", specifier = ">=6.0.0,<8.0.0" }, { name = "psutil", specifier = ">=6.1.1,<8.0.0" }, { name = "pydantic", specifier = ">=2.11,<3.0.0" }, { name = "pydantic-extra-types", specifier = ">=2.10.5,<3.0.0" },