diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 371d05f9..49f60d2a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: args: [--branch, main, --branch, dev] - id: check-added-large-files - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.4 + rev: v0.9.6 hooks: - id: ruff args: [ --fix, --exit-non-zero-on-fix, "--ignore=C901" ] diff --git a/simvue/api/objects/metrics.py b/simvue/api/objects/metrics.py index de5d3075..43b75561 100644 --- a/simvue/api/objects/metrics.py +++ b/simvue/api/objects/metrics.py @@ -37,7 +37,7 @@ def __init__( def new( cls, *, run: str, offline: bool = False, metrics: list[MetricSet], **kwargs ): - """Create a new Events entry on the Simvue server""" + """Create a new Metrics entry on the Simvue server""" return Metrics( run=run, metrics=[metric.model_dump() for metric in metrics], @@ -51,27 +51,23 @@ def get( cls, metrics: list[str], xaxis: typing.Literal["timestamp", "step", "time"], + runs: list[str], *, count: pydantic.PositiveInt | None = None, offset: pydantic.PositiveInt | None = None, **kwargs, ) -> typing.Generator[MetricSet, None, None]: _class_instance = cls(_read_only=True, _local=True) - if ( - _data := cls._get_all_objects( - count, - offset, - metrics=json.dumps(metrics), - xaxis=xaxis, - **kwargs, - ).get("data") - ) is None: - raise RuntimeError( - f"Expected key 'data' for retrieval of {_class_instance.__class__.__name__.lower()}s" - ) - - for _entry in _data: - yield MetricSet(**_entry) + _data = cls._get_all_objects( + count, + offset, + metrics=json.dumps(metrics), + runs=json.dumps(runs), + xaxis=xaxis, + **kwargs, + ) + # TODO: Temp fix, just return the dictionary. Not sure what format we really want this in... + return _data @pydantic.validate_call def span(self, run_ids: list[str]) -> dict[str, int | float]: diff --git a/tests/functional/test_dispatch.py b/tests/functional/test_dispatch.py index de77ccb2..e2ab6362 100644 --- a/tests/functional/test_dispatch.py +++ b/tests/functional/test_dispatch.py @@ -67,9 +67,10 @@ def callback(___: list[typing.Any], _: str, args=check_dict, var=variable) -> No event.set() dispatcher.join() + time.sleep(0.1) for variable in variables: - assert check_dict[variable]["counter"] >= 2 if overload_buffer else 1, f"Check of counter for dispatcher '{variable}' failed with count = {check_dict[variable]['counter']}" + assert check_dict[variable]["counter"] >= (2 if overload_buffer else 1), f"Check of counter for dispatcher '{variable}' failed with count = {check_dict[variable]['counter']}" assert time.time() - start_time < time_threshold diff --git a/tests/unit/test_events.py b/tests/unit/test_events.py index 205643e6..fb5d0587 100644 --- a/tests/unit/test_events.py +++ b/tests/unit/test_events.py @@ -7,6 +7,7 @@ from simvue.api.objects import Events, Folder, Run from simvue.models import DATETIME_FORMAT +from simvue.sender import sender @pytest.mark.api @pytest.mark.online @@ -30,3 +31,39 @@ def test_events_creation_online() -> None: _run.delete() _folder.delete(recursive=True, delete_runs=True, runs_only=False) +@pytest.mark.api +@pytest.mark.offline +def test_events_creation_offline() -> None: + _uuid: str = f"{uuid.uuid4()}".split("-")[0] + _folder_name = f"/simvue_unit_testing/{_uuid}" + _folder = Folder.new(path=_folder_name, offline=True) + _run = Run.new(folder=_folder_name, offline=True) + _folder.commit() + _run.commit() + _timestamp = datetime.datetime.now().strftime(DATETIME_FORMAT) + _events = Events.new( + run=_run.id, + events=[ + {"message": "This is a test!", "timestamp": _timestamp} + ], + offline=True + ) + _events.commit() + with _events._local_staging_file.open() as in_f: + _local_data = json.load(in_f) + + assert _local_data.get("run") == _run.id + assert _local_data.get("events")[0].get("message") == "This is a test!" + assert _local_data.get("events")[0].get("timestamp") == _timestamp + + _id_mapping = sender(_events._local_staging_file.parents[1], 1, 10, ["folders", "runs", "events"]) + time.sleep(1) + + # Get online version of events + _online_events = Events(_id_mapping.get(_events.id)) + _event_content = next(_online_events.get(run_id=_id_mapping.get(_run.id))) + assert _event_content.message == "This is a test!" + assert _event_content.timestamp == _timestamp + + _run.delete() + _folder.delete(recursive=True, delete_runs=True, runs_only=False) \ No newline at end of file diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index d8432fc0..5d8b7575 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -6,6 +6,8 @@ import uuid from simvue.api.objects import Metrics, Folder, Run +from simvue.models import DATETIME_FORMAT +from simvue.sender import sender @pytest.mark.api @pytest.mark.online @@ -38,9 +40,60 @@ def test_metrics_creation_online() -> None: ) assert _metrics.to_dict() _metrics.commit() - assert _metrics.get(metrics=["x", "y", "z"], xaxis="step") + assert _metrics.get(metrics=["x", "y", "z"], xaxis="step", runs=[_run.id]) assert _metrics.span(run_ids=[_run.id]) assert _metrics.names(run_ids=[_run.id]) _run.delete() _folder.delete(recursive=True, delete_runs=True, runs_only=False) +@pytest.mark.api +@pytest.mark.offline +def test_metrics_creation_offline() -> None: + _uuid: str = f"{uuid.uuid4()}".split("-")[0] + _folder_name = f"/simvue_unit_testing/{_uuid}" + _folder = Folder.new(path=_folder_name, offline=True) + _run = Run.new(name="hello", folder=_folder_name, offline=True) + _folder.commit() + _run.commit() + + _values = { + "x": 1, + "y": 2.0, + "z": True + } + _time: int = 1 + _step: int = 1 + _timestamp = datetime.datetime.now().strftime(DATETIME_FORMAT) + _metrics = Metrics.new( + run=_run.id, + metrics=[ + { + "timestamp": _timestamp, + "time": _time, + "step": _step, + "values": _values, + } + ], + offline=True + ) + _metrics.commit() + with _metrics._local_staging_file.open() as in_f: + _local_data = json.load(in_f) + + assert _local_data.get("run") == _run.id + assert _local_data.get("metrics")[0].get("values") == _values + assert _local_data.get("metrics")[0].get("timestamp") == _timestamp + assert _local_data.get("metrics")[0].get("step") == _step + assert _local_data.get("metrics")[0].get("time") == _time + + _id_mapping = sender(_metrics._local_staging_file.parents[1], 1, 10, ["folders", "runs", "metrics"]) + time.sleep(1) + + # Get online version of metrics + _online_metrics = Metrics(_id_mapping.get(_metrics.id)) + _data = _online_metrics.get(metrics=["x", "y", "z"], runs=[_id_mapping.get(_run.id)], xaxis="step") + assert sorted(_online_metrics.names(run_ids=[_id_mapping.get(_run.id)])) == sorted(_values.keys()) + assert _data.get(_id_mapping.get(_run.id)).get('y')[0].get('value') == 2.0 + assert _data.get(_id_mapping.get(_run.id)).get('y')[0].get('step') == 1 + _run.delete() + _folder.delete(recursive=True, delete_runs=True, runs_only=False) \ No newline at end of file diff --git a/tests/unit/test_user_alert.py b/tests/unit/test_user_alert.py index 71d08336..a819df15 100644 --- a/tests/unit/test_user_alert.py +++ b/tests/unit/test_user_alert.py @@ -191,13 +191,28 @@ def test_user_alert_status_offline() -> None: _run.alerts = [_alert.id] _run.commit() - sender(_alert._local_staging_file.parents[1], 1, 10, ["folders", "runs", "alerts"]) + _id_mapping = sender(_alert._local_staging_file.parents[1], 1, 10, ["folders", "runs", "alerts"]) time.sleep(1) + + # Get online aler, check status is not set + _online_alert = UserAlert(_id_mapping.get(_alert.id)) + assert not _online_alert.get_status(run_id=_id_mapping.get(_run.id)) _alert.set_status(_run.id, "critical") _alert.commit() - import pdb; pdb.set_trace() time.sleep(1) + + # Check online status is still not set as change has not been sent + _online_alert.refresh() + assert not _online_alert.get_status(run_id=_id_mapping.get(_run.id)) + + sender(_alert._local_staging_file.parents[1], 1, 10, ["alerts"]) + time.sleep(1) + + # Check online status has been updated + _online_alert.refresh() + assert _online_alert.get_status(run_id=_id_mapping.get(_run.id)) == "critical" + _run.delete() _folder.delete(recursive=True, runs_only=False, delete_runs=True) _alert.delete()