diff --git a/CHANGELOG.md b/CHANGELOG.md index a52837b6..5586a2a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## Unreleased + +- Added support for log level in events creation. + ## [v2.4.1](https://github.com/simvue-io/python-api/releases/tag/v2.4.1) - 2026-03-31 - Moved to using `threading.Event` as termination trigger events and added deprecation notice for `multiprocessing.Event`. diff --git a/pyproject.toml b/pyproject.toml index 54041d85..f488cfd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "simvue" -version = "2.4.1" +version = "2.5.0" description = "Simulation tracking and monitoring" authors = [{ name = "Simvue Development Team", email = "info@simvue.io" }] license = "Apache v2" diff --git a/simvue/config/user.py b/simvue/config/user.py index 8e3fa538..8685d0f7 100644 --- a/simvue/config/user.py +++ b/simvue/config/user.py @@ -66,6 +66,14 @@ class SimvueConfiguration(pydantic.BaseModel): metrics: MetricsSpecifications = MetricsSpecifications() eco: EcoConfig = EcoConfig() current_profile: str | None = None + _server_version: semver.Version | None = None + + @property + def server_version(self) -> semver.Version: + """Retrieve current Server version.""" + if not self._server_version: + raise RuntimeError("Expected server version to be defined") + return self._server_version @classmethod def _load_pyproject_configs(cls) -> dict | None: @@ -103,7 +111,7 @@ def _load_pyproject_configs(cls) -> dict | None: @functools.lru_cache def _check_server( cls, token: str, url: str, mode: typing.Literal["offline", "online", "disabled"] - ) -> None: + ) -> semver.Version: if mode in ("offline", "disabled"): return @@ -143,6 +151,7 @@ def _check_server( f"Python API v{_version_str} is not compatible with Simvue server versions " f"< {SIMVUE_SERVER_LOWER_CONSTRAINT}" ) + return _version @pydantic.validate_call def write(self, out_directory: pydantic.DirectoryPath) -> None: @@ -154,7 +163,9 @@ def check_valid_server(self) -> Self: if os.environ.get("SIMVUE_NO_SERVER_CHECK"): return self - self._check_server(self.server.token, self.server.url, self.run.mode) + self._server_version = self._check_server( + self.server.token, self.server.url, self.run.mode + ) return self diff --git a/simvue/models.py b/simvue/models.py index 63652310..b07f369e 100644 --- a/simvue/models.py +++ b/simvue/models.py @@ -19,6 +19,7 @@ str, pydantic.StringConstraints(pattern=METRIC_KEY_REGEX) ] ObjectID = typing.Annotated[str, pydantic.StringConstraints(pattern=OBJECT_ID)] +LogLevel = typing.Literal["debug", "info", "warning", "error", "critical"] def validate_timestamp(timestamp: str, raise_except: bool = True) -> bool: @@ -114,4 +115,7 @@ def serialize_array( class EventSet(pydantic.BaseModel): model_config = pydantic.ConfigDict(extra="forbid") message: str + log_level: ( + typing.Literal["info", "warning", "debug", "error", "critical"] | None + ) = None timestamp: typing.Annotated[str | None, pydantic.BeforeValidator(simvue_timestamp)] diff --git a/simvue/run.py b/simvue/run.py index a160fa75..d49013ef 100644 --- a/simvue/run.py +++ b/simvue/run.py @@ -52,6 +52,7 @@ MetricKeyString, validate_timestamp, simvue_timestamp, + LogLevel, ) from .system import get_system from .metadata import git_info, environment @@ -1356,9 +1357,11 @@ def update_tags(self, tags: list[str]) -> bool: def log_event( self, message: str, + *, timestamp: typing.Annotated[ datetime.datetime | str | None, pydantic.BeforeValidator(simvue_timestamp) ] = None, + log_level: LogLevel = "info", ) -> bool: """Log event to the server @@ -1369,6 +1372,9 @@ def log_event( timestamp : datetime.datetime | str, optional manually specify the time stamp for this log, by default None if a string is provided, local time + log_level : str, optional + the logging level for this event, default is 'info', + requires server with version >=1.2.16 Returns ------- @@ -1386,7 +1392,8 @@ def log_event( run.log_event( message="Good Night", - timestamp=datetime.datetime.now(datetime.UTC) + timestamp=datetime.datetime.now(datetime.UTC), + log_level="debug" ) ``` """ @@ -1405,7 +1412,15 @@ def log_event( self._error("Cannot log events when not in the running state") return False - _data = {"message": message, "timestamp": timestamp} + # FIXME: Temporary, this will eventually be removed + import semver + + _log_level_server_version = semver.parse("1.2.16") + if self._user_config.server_version < _log_level_server_version: + self._error("Log level is not supported on current server.") + return False + + _data = {"message": message, "timestamp": timestamp, "log_level": log_level} self._dispatcher.add_item( _data, object_type="events", blocking=self._queue_blocking ) diff --git a/tests/unit/test_events.py b/tests/unit/test_events.py index f407e305..d632a028 100644 --- a/tests/unit/test_events.py +++ b/tests/unit/test_events.py @@ -22,7 +22,7 @@ def test_events_creation_online() -> None: _events = Events.new( run=_run.id, events=[ - {"message": "This is a test!", "timestamp": _timestamp} + {"message": "This is a test!", "timestamp": _timestamp, "log_level": "debug"} ], ) assert _events.to_dict() @@ -44,7 +44,7 @@ def test_events_creation_offline(offline_cache_setup) -> None: _events = Events.new( run=_run.id, events=[ - {"message": "This is a test!", "timestamp": _timestamp} + {"message": "This is a test!", "timestamp": _timestamp, "log_level": "debug"} ], offline=True )