Skip to content

Commit 933ad3b

Browse files
authored
python(feat): Allow adding TDMS metadata to existing runs (#320)
1 parent bbe43ac commit 933ad3b

3 files changed

Lines changed: 88 additions & 11 deletions

File tree

python/lib/sift_py/data_import/_tdms_test.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,8 @@ def post_side_effect(*args, **kwargs):
777777

778778
svc = TdmsUploadService(rest_config)
779779

780-
# Should raise if run_id is provided
781-
with pytest.raises(ValueError, match="Metadata can only be included in new runs"):
780+
# Should raise if run_id and run_name is provided
781+
with pytest.raises(ValueError, match="Must specify either run_name or run_id, not both"):
782782
svc.upload(
783783
"some_tdms.tdms",
784784
"asset_name",
@@ -788,7 +788,7 @@ def post_side_effect(*args, **kwargs):
788788
)
789789

790790
# Should raise if run_name is not provided
791-
with pytest.raises(ValueError, match="Must provide a run_name to include metadata"):
791+
with pytest.raises(ValueError, match="Metadata can only be included in Runs"):
792792
svc.upload(
793793
"some_tdms.tdms",
794794
"asset_name",
@@ -845,3 +845,45 @@ def post_side_effect(*args, **kwargs):
845845
== MetadataKeyType.METADATA_KEY_TYPE_STRING
846846
)
847847
assert create_run_post_data["metadata"][4]["string_value"].startswith("2024-01-01T12:00:00")
848+
849+
850+
def test_tdms_upload_service_upload_with_metadata_run_id(
851+
mocker: MockFixture, mock_waveform_tdms_file: MockTdmsFile
852+
):
853+
mock_path_is_file = mocker.patch("sift_py.data_import.tdms.Path.is_file")
854+
mock_path_is_file.return_value = True
855+
856+
mock_path_getsize = mocker.patch("sift_py.data_import.csv.os.path.getsize")
857+
mock_path_getsize.return_value = 10
858+
859+
# Patch TdmsFile to return our mock file
860+
mocker.patch("sift_py.data_import.tdms.TdmsFile", return_value=mock_waveform_tdms_file)
861+
862+
# Patch requests.Session.post and patch requests.Session.patch for metadata update
863+
mock_requests_post = mocker.patch("sift_py.rest.requests.Session.post")
864+
mock_requests_post.return_value = MockResponse()
865+
mock_requests_patch = mocker.patch("sift_py.rest.requests.Session.patch")
866+
mock_requests_patch.return_value = MockResponse(
867+
status_code=200,
868+
text=json.dumps({"run": {"runId": "existing_run_id"}}),
869+
)
870+
871+
svc = TdmsUploadService(rest_config)
872+
873+
# Should succeed and call _add_metadata_to_run via PATCH with metadata if only run_id is provided
874+
svc.upload(
875+
"some_tdms.tdms",
876+
"asset_name",
877+
include_metadata=True,
878+
run_id="existing_run_id",
879+
)
880+
881+
# Check that PATCH was called for metadata update
882+
patch_call = mock_requests_patch.call_args_list[0]
883+
patch_data = json.loads(patch_call.kwargs["data"])
884+
assert patch_data["run"]["runId"] == "existing_run_id"
885+
assert "metadata" in patch_data["run"]
886+
assert patch_data["updateMask"] == "metadata"
887+
# Metadata keys should match those in the mock_tdms_file properties
888+
keys = [md["key"]["name"] for md in patch_data["run"]["metadata"]]
889+
assert set(keys) == set(mock_waveform_tdms_file.properties.keys())

python/lib/sift_py/data_import/csv.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,36 @@ def _create_run(self, run_name: str, metadata: Optional[List[MetadataValue]] = N
305305

306306
return run_info["run"]["runId"]
307307

308+
def _add_metadata_to_run(self, run_id: str, metadata: List[MetadataValue]):
309+
"""
310+
Updates metadata for the specified Run.
311+
312+
Args:
313+
run_id: The ID of the run to update.
314+
metadata: Metadata fields to update.
315+
"""
316+
run_uri = urljoin(self._base_uri, self.RUN_PATH)
317+
318+
req: Dict[str, Any] = {
319+
"run": {
320+
"runId": run_id,
321+
"metadata": metadata_pb_to_dict_api(metadata),
322+
},
323+
"updateMask": "metadata",
324+
}
325+
326+
response = self._session.patch(
327+
url=run_uri,
328+
headers={
329+
"Content-Type": "application/json",
330+
},
331+
data=json.dumps(req),
332+
)
333+
if response.status_code != 200:
334+
raise Exception(
335+
f"Run metadata update failed with status code {response.status_code}. {response.text}"
336+
)
337+
308338

309339
class _ProgressFile:
310340
"""Displays the status with alive_bar while reading the file."""

python/lib/sift_py/data_import/tdms.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,14 @@ def upload(
147147
if not posix_path.is_file():
148148
raise Exception(f"Provided path, '{path}', does not point to a regular file.")
149149

150-
# If metadata should be included, create the run first.
151150
if include_metadata:
152151
# Do not allow including metadata in existing runs since it could lead
153152
# to overwriting metadata fields.
154-
if run_id:
155-
raise ValueError("Metadata can only be included in new runs")
153+
if not (run_id or run_name):
154+
raise ValueError("Metadata can only be included in Runs")
156155

157-
if not run_name:
158-
raise ValueError("Must provide a run_name to include metadata")
156+
if run_name and run_id:
157+
raise ValueError("Must specify either run_name or run_id, not both")
159158

160159
def parse_datetime(value):
161160
"""Convert datetime metadata to strings."""
@@ -168,9 +167,15 @@ def parse_datetime(value):
168167

169168
tdms_file = TdmsFile(path)
170169
metadata = metadata_dict_to_pb(tdms_file.properties, parse_datetime)
171-
run_id = self._csv_upload_service._create_run(run_name, metadata)
172-
# Clear the run name since we are using run_id now.
173-
run_name = None
170+
171+
# Create a new run with metadata fields.
172+
if run_name:
173+
run_id = self._csv_upload_service._create_run(run_name, metadata)
174+
# Clear the run name since we are using run_id now.
175+
run_name = None
176+
# Add metadata to existing Run.
177+
else:
178+
self._csv_upload_service._add_metadata_to_run(run_id, metadata) # type: ignore
174179

175180
with NamedTemporaryFile(mode="wt", suffix=".csv.gz") as temp_file:
176181
csv_config = self._convert_to_csv(

0 commit comments

Comments
 (0)