Skip to content

Added improved merging of staging dicts #692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 17 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies = [
"psutil (>=6.1.1,<7.0.0)",
"tenacity (>=9.0.0,<10.0.0)",
"typing-extensions (>=4.12.2,<5.0.0) ; python_version < \"3.11\"",
"deepmerge (>=2.0,<3.0)",
]

[project.urls]
Expand Down
4 changes: 3 additions & 1 deletion simvue/api/objects/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import msgpack
import pydantic

from simvue.utilities import staging_merger
from simvue.config.user import SimvueConfiguration
from simvue.exception import ObjectNotFoundError
from simvue.version import __version__
Expand Down Expand Up @@ -524,7 +525,8 @@ def _cache(self) -> None:
with self._local_staging_file.open() as in_f:
_local_data = json.load(in_f)

_local_data |= self._staging
staging_merger.merge(_local_data, self._staging)

with self._local_staging_file.open("w", encoding="utf-8") as out_f:
json.dump(_local_data, out_f, indent=2)

Expand Down
17 changes: 16 additions & 1 deletion simvue/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import os
import pathlib
import typing

import jwt
from deepmerge import Merger

from datetime import timezone
from simvue.models import DATETIME_FORMAT
Expand Down Expand Up @@ -395,3 +395,18 @@ def get_mimetype_for_file(file_path: pathlib.Path) -> str:
"""Return MIME type for the given file"""
_guess, *_ = mimetypes.guess_type(file_path)
return _guess or "application/octet-stream"


# Create a new Merge strategy for merging local file and staging attributes
staging_merger = Merger(
# pass in a list of tuple, with the
# strategies you are looking to apply
# to each type.
[(list, ["override"]), (dict, ["merge"]), (set, ["union"])],
# next, choose the fallback strategies,
# applied to all other types:
["override"],
# finally, choose the strategies in
# the case where the types conflict:
["override"],
)
52 changes: 46 additions & 6 deletions tests/functional/test_run_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,14 @@ def test_offline_tags(create_plain_run_offline: tuple[sv_run.Run, dict]) -> None

@pytest.mark.run
def test_update_metadata_running(create_test_run: tuple[sv_run.Run, dict]) -> None:
METADATA = {"a": 10, "b": 1.2, "c": "word"}
METADATA = {"a": 1, "b": 1.2, "c": "word", "d": "new"}
run, _ = create_test_run
run.update_metadata(METADATA)
# Add an initial set of metadata
run.update_metadata({"a": 10, "b": 1.2, "c": "word"})
# Try updating a second time, check original dict isnt overwritten
run.update_metadata({"d": "new"})
# Try updating an already defined piece of metadata
run.update_metadata({"a": 1})
run.close()
time.sleep(1.0)
client = sv_cl.Client()
Expand All @@ -234,9 +239,14 @@ def test_update_metadata_running(create_test_run: tuple[sv_run.Run, dict]) -> No

@pytest.mark.run
def test_update_metadata_created(create_pending_run: tuple[sv_run.Run, dict]) -> None:
METADATA = {"a": 10, "b": 1.2, "c": "word"}
METADATA = {"a": 1, "b": 1.2, "c": "word", "d": "new"}
run, _ = create_pending_run
run.update_metadata(METADATA)
# Add an initial set of metadata
run.update_metadata({"a": 10, "b": 1.2, "c": "word"})
# Try updating a second time, check original dict isnt overwritten
run.update_metadata({"d": "new"})
# Try updating an already defined piece of metadata
run.update_metadata({"a": 1})
time.sleep(1.0)
client = sv_cl.Client()
run_info = client.get_run(run.id)
Expand All @@ -250,13 +260,20 @@ def test_update_metadata_created(create_pending_run: tuple[sv_run.Run, dict]) ->
def test_update_metadata_offline(
create_plain_run_offline: tuple[sv_run.Run, dict],
) -> None:
METADATA = {"a": 10, "b": 1.2, "c": "word"}
METADATA = {"a": 1, "b": 1.2, "c": "word", "d": "new"}
run, _ = create_plain_run_offline
run_name = run._name
run.update_metadata(METADATA)
# Add an initial set of metadata
run.update_metadata({"a": 10, "b": 1.2, "c": "word"})
# Try updating a second time, check original dict isnt overwritten
run.update_metadata({"d": "new"})
# Try updating an already defined piece of metadata
run.update_metadata({"a": 1})

sv_send.sender(os.environ["SIMVUE_OFFLINE_DIRECTORY"], 2, 10)
run.close()
time.sleep(1.0)

client = sv_cl.Client()
run_info = client.get_run(client.get_run_id_from_name(run_name))

Expand Down Expand Up @@ -655,6 +672,29 @@ def test_update_tags_created(
assert sorted(run_data.tags) == sorted(tags + ["additional"])


@pytest.mark.offline
@pytest.mark.run
def test_update_tags_offline(
create_plain_run_offline: typing.Tuple[sv_run.Run, dict],
) -> None:
simvue_run, _ = create_plain_run_offline
run_name = simvue_run._name

simvue_run.set_tags(["simvue_client_unit_tests",])

simvue_run.update_tags(["additional"])

sv_send.sender(os.environ["SIMVUE_OFFLINE_DIRECTORY"], 2, 10)
simvue_run.close()
time.sleep(1.0)

client = sv_cl.Client()
run_data = client.get_run(client.get_run_id_from_name(run_name))

time.sleep(1)
run_data = client.get_run(simvue_run._id)
assert sorted(run_data.tags) == sorted(["simvue_client_unit_tests", "additional"])

@pytest.mark.run
@pytest.mark.parametrize("object_type", ("DataFrame", "ndarray"))
def test_save_object(
Expand Down
Loading