micantis 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micantis
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Package to simplify Micantis API usage
5
5
  Author-email: Mykela DeLuca <mykela.deluca@micantis.io>
6
6
  License-Expression: MIT
@@ -548,6 +548,55 @@ assert result.success, result.errors
548
548
 
549
549
  > **Rename rules:** CellTest names must be unique and may only contain letters, digits, and `_ - . ( ) space`. CellData names must be unique; renaming a CellData also auto-updates the owning CellTest's DataSourceKey.
550
550
 
551
+ ## Write Data Metadata
552
+ Assign or update metadata on a **data set** (CellData record), the same way as cells, using either:
553
+ - the built-in field `"name"` (renames the data set) or `"dataSourceKey"`
554
+ - the human-readable field name of a user-defined property (e.g., "Active Cycle Test?")
555
+ - or the internal propertyDefinitionId (UUID)
556
+
557
+ User-defined property names are resolved against the data-set metadata definitions, which you can list with `api.list_data_metadata_definitions()`.
558
+
559
+ **Per-entity atomic:** all changes targeting a single data set commit or rollback together; other data sets are unaffected. Returns a `BatchUpdateResult` with `.success` and `.errors`, identical to `write_cell_metadata`.
560
+
561
+ #### 📘 Examples
562
+
563
+ ```python
564
+ # Example 1: Set a user-defined property on specific data sets
565
+ changes = [
566
+ {"id": "your-cell-data-guid-here", "field": "Active Cycle Test?", "value": True},
567
+ ]
568
+
569
+ result = api.write_data_metadata(changes=changes)
570
+ if not result:
571
+ for e in result.errors:
572
+ print(f"{e['itemId']}: {e['error']}")
573
+ ```
574
+
575
+ ```python
576
+ # Example 2: Rename a data set and update its dataSourceKey
577
+ changes = [
578
+ {"id": "your-cell-data-guid-here", "field": "name", "value": "Formation_Run_42"},
579
+ {"id": "your-cell-data-guid-here", "field": "dataSourceKey", "value": "Formation"},
580
+ ]
581
+
582
+ result = api.write_data_metadata(changes=changes)
583
+ assert result.success, result.errors
584
+ ```
585
+
586
+ ```python
587
+ # Example 3: Update using propertyDefinitionId (advanced)
588
+ changes = [
589
+ {
590
+ "id": "your-cell-data-guid-here",
591
+ "propertyDefinitionId": "your-property-definition-guid",
592
+ "value": "true"
593
+ }
594
+ ]
595
+
596
+ result = api.write_data_metadata(changes=changes)
597
+ assert result.success, result.errors
598
+ ```
599
+
551
600
  ## Stitch Data
552
601
  Combine multiple data sets into a single stitched data set. This is useful for creating continuous test data from multiple separate test runs.
553
602
 
@@ -528,6 +528,55 @@ assert result.success, result.errors
528
528
 
529
529
  > **Rename rules:** CellTest names must be unique and may only contain letters, digits, and `_ - . ( ) space`. CellData names must be unique; renaming a CellData also auto-updates the owning CellTest's DataSourceKey.
530
530
 
531
+ ## Write Data Metadata
532
+ Assign or update metadata on a **data set** (CellData record), the same way as cells, using either:
533
+ - the built-in field `"name"` (renames the data set) or `"dataSourceKey"`
534
+ - the human-readable field name of a user-defined property (e.g., "Active Cycle Test?")
535
+ - or the internal propertyDefinitionId (UUID)
536
+
537
+ User-defined property names are resolved against the data-set metadata definitions, which you can list with `api.list_data_metadata_definitions()`.
538
+
539
+ **Per-entity atomic:** all changes targeting a single data set commit or rollback together; other data sets are unaffected. Returns a `BatchUpdateResult` with `.success` and `.errors`, identical to `write_cell_metadata`.
540
+
541
+ #### 📘 Examples
542
+
543
+ ```python
544
+ # Example 1: Set a user-defined property on specific data sets
545
+ changes = [
546
+ {"id": "your-cell-data-guid-here", "field": "Active Cycle Test?", "value": True},
547
+ ]
548
+
549
+ result = api.write_data_metadata(changes=changes)
550
+ if not result:
551
+ for e in result.errors:
552
+ print(f"{e['itemId']}: {e['error']}")
553
+ ```
554
+
555
+ ```python
556
+ # Example 2: Rename a data set and update its dataSourceKey
557
+ changes = [
558
+ {"id": "your-cell-data-guid-here", "field": "name", "value": "Formation_Run_42"},
559
+ {"id": "your-cell-data-guid-here", "field": "dataSourceKey", "value": "Formation"},
560
+ ]
561
+
562
+ result = api.write_data_metadata(changes=changes)
563
+ assert result.success, result.errors
564
+ ```
565
+
566
+ ```python
567
+ # Example 3: Update using propertyDefinitionId (advanced)
568
+ changes = [
569
+ {
570
+ "id": "your-cell-data-guid-here",
571
+ "propertyDefinitionId": "your-property-definition-guid",
572
+ "value": "true"
573
+ }
574
+ ]
575
+
576
+ result = api.write_data_metadata(changes=changes)
577
+ assert result.success, result.errors
578
+ ```
579
+
531
580
  ## Stitch Data
532
581
  Combine multiple data sets into a single stitched data set. This is useful for creating continuous test data from multiple separate test runs.
533
582
 
@@ -884,6 +884,59 @@ class MicantisAPI:
884
884
  except requests.exceptions.RequestException as e:
885
885
  raise self._format_request_error(e)
886
886
 
887
+ def list_data_metadata_definitions(self, return_format='df', timeout: int = 30):
888
+ """
889
+ Returns a list of cell data metadata types.
890
+
891
+ Parameters
892
+ ----------
893
+ return_format : str, optional
894
+ Return format: 'df' for DataFrame (default), anything else for raw list.
895
+ timeout : int, optional
896
+ Request timeout in seconds. Default: 30.
897
+ """
898
+ try:
899
+ return self._retry_on_auth_failure(
900
+ self._list_data_metadata_definitions, return_format, timeout
901
+ )
902
+ except Exception as e:
903
+ print(f"⚠️ list_data_metadata_definitions failed: {e}")
904
+ return None
905
+
906
+ def _list_data_metadata_definitions(self, return_format: str, timeout: int):
907
+ """
908
+ Internal helper for list_data_metadata_definitions. Does not handle retries or printing.
909
+
910
+ Raises
911
+ ------
912
+ RuntimeError
913
+ If the request fails or authentication is missing.
914
+ """
915
+ if not self.headers:
916
+ raise RuntimeError("Authentication required. Call authenticate() first.")
917
+
918
+ self._refresh_token_if_needed()
919
+
920
+ try:
921
+ # Make the GET request
922
+ response = self.session.get(
923
+ f"{self.service_url}/publicapi/v1/metadata/list/data",
924
+ headers=self.headers,
925
+ timeout=timeout,
926
+ )
927
+ response.raise_for_status()
928
+
929
+ item = response.json()
930
+ if return_format == 'df':
931
+ df = pd.DataFrame(item)
932
+ return df
933
+
934
+ else:
935
+ return item
936
+
937
+ except requests.exceptions.RequestException as e:
938
+ raise self._format_request_error(e)
939
+
887
940
  def get_cell_metadata(self, cell_ids: list, metadata: list = None, return_images: bool = False, timeout: int = 30):
888
941
  """
889
942
  Fetch metadata for specific cells, using either metadata names or IDs.
@@ -1144,9 +1197,14 @@ class MicantisAPI:
1144
1197
  """
1145
1198
  Wrapper for POST /data/updatemetadata endpoint.
1146
1199
 
1147
- Updates built-in metadata fields on cell data records. Supported built-in
1148
- fields are "name" and "dataSourceKey". User-defined metadata properties on
1149
- cell data are not yet supported via the public API.
1200
+ Each change targets either a built-in field (supported: "name",
1201
+ "dataSourceKey") or a user-defined metadata property. For user-defined
1202
+ properties, pass either a human-readable 'field' name (resolved via
1203
+ metadata definitions) or a 'propertyDefinitionId' UUID.
1204
+
1205
+ Built-in field names take precedence over user-defined names; if you have a
1206
+ user-defined property literally named "name" or "dataSourceKey", use
1207
+ propertyDefinitionId to disambiguate.
1150
1208
 
1151
1209
  Renaming a data set ("name" field) enforces name-uniqueness; the change is
1152
1210
  rejected if another data set already has the target name. The owning
@@ -1166,7 +1224,10 @@ class MicantisAPI:
1166
1224
  changes : list of dict
1167
1225
  A list of metadata change dictionaries. Each dictionary must contain:
1168
1226
  - "id": str (cell data GUID)
1169
- - "field": "name" or "dataSourceKey"
1227
+ - One of:
1228
+ - "field": "name" or "dataSourceKey" (built-in fields)
1229
+ - "field": "<metadata name>" (user-defined, e.g. "Active Cycle Test?")
1230
+ - "propertyDefinitionId": "<UUID>" (user-defined)
1170
1231
  - "value": any (will be cast to str)
1171
1232
 
1172
1233
  timeout : int, optional
@@ -1184,8 +1245,9 @@ class MicantisAPI:
1184
1245
  Examples
1185
1246
  --------
1186
1247
  >>> result = api.write_data_metadata([
1187
- ... {"id": cell_data_id, "field": "name", "value": "Formation_Run_42"},
1188
- ... {"id": cell_data_id, "field": "dataSourceKey", "value": "Formation"},
1248
+ ... {"id": cell_data_id, "field": "name", "value": "Formation_Run_42"},
1249
+ ... {"id": cell_data_id, "field": "dataSourceKey", "value": "Formation"},
1250
+ ... {"id": cell_data_id, "field": "Active Cycle Test?", "value": True},
1189
1251
  ... ])
1190
1252
  >>> if not result:
1191
1253
  ... for e in result.errors:
@@ -1203,20 +1265,35 @@ class MicantisAPI:
1203
1265
 
1204
1266
  self._refresh_token_if_needed()
1205
1267
 
1268
+ # Only look up user-property definitions if at least one change uses a 'field' that
1269
+ # isn't a built-in (lookup is a separate round-trip and unnecessary otherwise).
1270
+ needs_lookup = any(
1271
+ "field" in change and change["field"] not in self._BUILTIN_DATA_METADATA_FIELDS
1272
+ for change in changes
1273
+ )
1274
+ if needs_lookup:
1275
+ metadata_df = self._list_data_metadata_definitions('df', timeout)
1276
+ name_to_id = dict(zip(metadata_df['name'], metadata_df['id']))
1277
+ else:
1278
+ name_to_id = {}
1279
+
1206
1280
  formatted_changes = []
1207
1281
  for change in changes:
1208
- field = change.get("field")
1209
- if field not in self._BUILTIN_DATA_METADATA_FIELDS:
1210
- supported = ", ".join(f'"{f}"' for f in sorted(self._BUILTIN_DATA_METADATA_FIELDS))
1211
- raise RuntimeError(
1212
- f"write_data_metadata only supports built-in fields ({supported}). "
1213
- f"Got field={field!r}."
1214
- )
1215
- formatted_changes.append({
1216
- "id": change["id"],
1217
- "field": field,
1218
- "value": str(change["value"]),
1219
- })
1282
+ item = {"id": change["id"], "value": str(change["value"])}
1283
+
1284
+ if "field" in change and change["field"] in self._BUILTIN_DATA_METADATA_FIELDS:
1285
+ item["field"] = change["field"]
1286
+ elif "propertyDefinitionId" in change:
1287
+ item["propertyDefinitionId"] = change["propertyDefinitionId"]
1288
+ elif "field" in change:
1289
+ field_name = change["field"]
1290
+ if field_name not in name_to_id:
1291
+ raise RuntimeError(f"Field name '{field_name}' not found in metadata definitions.")
1292
+ item["propertyDefinitionId"] = name_to_id[field_name]
1293
+ else:
1294
+ raise RuntimeError("Each change must include either 'field' or 'propertyDefinitionId'.")
1295
+
1296
+ formatted_changes.append(item)
1220
1297
 
1221
1298
  payload = {"changes": formatted_changes}
1222
1299
 
@@ -2213,6 +2290,111 @@ class MicantisAPI:
2213
2290
  cycle, step_kind, step_index)
2214
2291
  return action
2215
2292
 
2293
+ @staticmethod
2294
+ def rename_column(source_column_index, new_name, name=None):
2295
+ """Build a rename-column action for clean_data (MetadataEdit mode).
2296
+
2297
+ Rename a column's display Name — what graphs, tables, and reports look up
2298
+ by YAxisName. Targets the ORIGINAL source column index; action order does
2299
+ not shift index meaning.
2300
+
2301
+ Parameters
2302
+ ----------
2303
+ source_column_index : int
2304
+ 0-based index into the ORIGINAL source column list.
2305
+ new_name : str
2306
+ New display name. Preserve exact spacing / punctuation expected by
2307
+ downstream lookups (e.g. "Temp 1 (C)" with two spaces).
2308
+ name : str, optional
2309
+ Descriptive name for this action.
2310
+ """
2311
+ action = {"$type": "rename",
2312
+ "sourceColumnIndex": int(source_column_index),
2313
+ "newName": new_name}
2314
+ if name is not None:
2315
+ action["name"] = name
2316
+ return action
2317
+
2318
+ @staticmethod
2319
+ def change_column_kind(source_column_index, new_kind, name=None):
2320
+ """Build a change-column-kind action for clean_data (MetadataEdit mode).
2321
+
2322
+ Change a column's YAxisKind (cycle-tester aux) or ImportItemKind (logger)
2323
+ — controls which kind-based lookups match the column. Targets the ORIGINAL
2324
+ source column index.
2325
+
2326
+ Parameters
2327
+ ----------
2328
+ source_column_index : int
2329
+ 0-based index into the ORIGINAL source column list.
2330
+ new_kind : str
2331
+ Exact enum value name (e.g. 'Voltage', 'AuxTemperature').
2332
+ name : str, optional
2333
+ Descriptive name for this action.
2334
+ """
2335
+ action = {"$type": "kind",
2336
+ "sourceColumnIndex": int(source_column_index),
2337
+ "newKind": new_kind}
2338
+ if name is not None:
2339
+ action["name"] = name
2340
+ return action
2341
+
2342
+ @staticmethod
2343
+ def reorder_columns(source_column_indices, name=None):
2344
+ """Build a reorder-columns action for clean_data (MetadataEdit mode).
2345
+
2346
+ Declarative final column layout: source columns appear in the cleaned
2347
+ output in the order listed; any source index not listed is dropped.
2348
+ Combines drop + reorder in one action. At most one per definition.
2349
+
2350
+ Parameters
2351
+ ----------
2352
+ source_column_indices : list[int]
2353
+ Ordered list of 0-based source column indices to keep. Omit an index
2354
+ to drop that column. Example: [0, 1, 3, 4, 5] drops source column 2.
2355
+ name : str, optional
2356
+ Descriptive name for this action.
2357
+ """
2358
+ action = {"$type": "reorderColumns",
2359
+ "sourceColumnIndices": [int(i) for i in source_column_indices]}
2360
+ if name is not None:
2361
+ action["name"] = name
2362
+ return action
2363
+
2364
+ @staticmethod
2365
+ def edit_logger_step(line=None, lines=None, columns=None, name=None):
2366
+ """Build an edit-logger-timestep action for clean_data (logger-data analog of modify_step/bulk_modify).
2367
+
2368
+ Edit column values by 0-based column index on rows matched by ``line`` and/or
2369
+ ``lines`` (union). Use on DataLoggerData sources only; for cycle-tester data
2370
+ use modify_step / bulk_modify.
2371
+
2372
+ Parameters
2373
+ ----------
2374
+ line : int, optional
2375
+ Single line number to edit.
2376
+ lines : list of (int, int) tuples, optional
2377
+ Line number ranges to edit, e.g. [(100, 200)]. End may be None for open-ended.
2378
+ columns : dict of {int: float}
2379
+ Column edits as {column_index: value}. Column index is 0-based and
2380
+ must refer to a valid column in the source ImportDefinition.
2381
+ name : str, optional
2382
+ Descriptive name for this action.
2383
+ """
2384
+ if line is None and not lines:
2385
+ raise ValueError("edit_logger_step requires either line or lines.")
2386
+ if not columns:
2387
+ raise ValueError("edit_logger_step requires columns.")
2388
+ action = {"$type": "editLogger"}
2389
+ if name is not None:
2390
+ action["name"] = name
2391
+ if line is not None:
2392
+ action["lineNumber"] = line
2393
+ if lines:
2394
+ action["lineNumberRanges"] = _ranges(lines)
2395
+ action["columns"] = [{"columnIndex": int(k), "value": float(v)} for k, v in columns.items()]
2396
+ return action
2397
+
2216
2398
  def clean_data(self,
2217
2399
  source_ids: list,
2218
2400
  mode: str = 'CycleAutoFixup',
@@ -2231,14 +2413,19 @@ class MicantisAPI:
2231
2413
  source_ids : list of str
2232
2414
  List of cell data IDs (GUIDs) to clean.
2233
2415
  mode : str, optional
2234
- Cleaning mode. One of 'CycleAutoFixup', 'FilterCycles', 'Parametric'.
2235
- Default: 'CycleAutoFixup'. Auto-set to 'Parametric' when actions is provided.
2416
+ Cleaning mode. One of 'CycleAutoFixup', 'FilterCycles', 'Parametric', 'MetadataEdit'.
2417
+ Default: 'CycleAutoFixup'. When ``actions`` is provided and mode is still the default,
2418
+ it auto-sets to 'Parametric'. For 'MetadataEdit', pass mode explicitly.
2236
2419
  filter_cycles_definition : dict, optional
2237
2420
  Required when mode is 'FilterCycles'. Definition for filtering cycles.
2238
2421
  actions : list, optional
2239
- List of action dicts built with api.remove_steps(),
2240
- api.modify_step(), and/or api.bulk_modify().
2241
- Auto-sets mode to 'Parametric'. Example::
2422
+ For Parametric mode: list of dicts from api.remove_steps(), api.modify_step(),
2423
+ api.bulk_modify(), api.edit_logger_step().
2424
+ For MetadataEdit mode: list of dicts from api.rename_column(),
2425
+ api.change_column_kind(), api.reorder_columns().
2426
+ Actions are applied in the order given.
2427
+
2428
+ Parametric example (cycle-tester)::
2242
2429
 
2243
2430
  api.clean_data(
2244
2431
  source_ids=[id],
@@ -2248,6 +2435,29 @@ class MicantisAPI:
2248
2435
  api.bulk_modify(lines=[(100, 5000)], cycle=1),
2249
2436
  ]
2250
2437
  )
2438
+
2439
+ Parametric example (DataLoggerData)::
2440
+
2441
+ api.clean_data(
2442
+ source_ids=[logger_id],
2443
+ actions=[
2444
+ api.remove_steps(lines=[(1, 3)]),
2445
+ api.edit_logger_step(line=10, columns={0: 1.5}),
2446
+ ]
2447
+ )
2448
+
2449
+ MetadataEdit example (rename + drop — works on both CT and logger)::
2450
+
2451
+ api.clean_data(
2452
+ source_ids=[id],
2453
+ mode='MetadataEdit',
2454
+ actions=[
2455
+ api.rename_column(source_column_index=3, new_name='Temp 1 (C)'),
2456
+ api.rename_column(source_column_index=4, new_name='Temp 2 (C)'),
2457
+ api.rename_column(source_column_index=5, new_name='Temp 3 (C)'),
2458
+ api.reorder_columns([0, 1, 3, 4, 5]), # drops source column 2
2459
+ ]
2460
+ )
2251
2461
  allow_async : bool, optional
2252
2462
  If True, runs asynchronously and returns job ID. Default: False.
2253
2463
  timeout : int, optional
@@ -2260,7 +2470,8 @@ class MicantisAPI:
2260
2470
  If allow_async is True: {'job_id': str}
2261
2471
  Returns None if an error occurs.
2262
2472
  """
2263
- if actions is not None:
2473
+ # Auto-set mode only if the caller left the default; preserves explicit 'MetadataEdit' etc.
2474
+ if actions is not None and mode == 'CycleAutoFixup':
2264
2475
  mode = 'Parametric'
2265
2476
 
2266
2477
  try:
@@ -2291,8 +2502,10 @@ class MicantisAPI:
2291
2502
 
2292
2503
  if mode == 'Parametric' and not actions:
2293
2504
  raise ValueError("actions is required when mode is 'Parametric'.")
2505
+ if mode == 'MetadataEdit' and not actions:
2506
+ raise ValueError("actions is required when mode is 'MetadataEdit'.")
2294
2507
 
2295
- _mode_map = {'CycleAutoFixup': 1, 'Parametric': 2, 'FilterCycles': 4}
2508
+ _mode_map = {'CycleAutoFixup': 1, 'Parametric': 2, 'FilterCycles': 4, 'MetadataEdit': 5}
2296
2509
  if mode not in _mode_map:
2297
2510
  raise ValueError(f"mode must be one of {list(_mode_map)}, got '{mode}'")
2298
2511
 
@@ -2304,7 +2517,10 @@ class MicantisAPI:
2304
2517
  if filter_cycles_definition is not None:
2305
2518
  payload["filterCyclesDefinition"] = filter_cycles_definition
2306
2519
  if actions is not None:
2307
- payload["parametricDefinition"] = {"actions": actions}
2520
+ if mode == 'MetadataEdit':
2521
+ payload["metadataEditDefinition"] = {"actions": actions}
2522
+ else:
2523
+ payload["parametricDefinition"] = {"actions": actions}
2308
2524
 
2309
2525
  url = f"{self.service_url}/publicapi/v1/data/clean"
2310
2526
  if allow_async:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micantis
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: Package to simplify Micantis API usage
5
5
  Author-email: Mykela DeLuca <mykela.deluca@micantis.io>
6
6
  License-Expression: MIT
@@ -548,6 +548,55 @@ assert result.success, result.errors
548
548
 
549
549
  > **Rename rules:** CellTest names must be unique and may only contain letters, digits, and `_ - . ( ) space`. CellData names must be unique; renaming a CellData also auto-updates the owning CellTest's DataSourceKey.
550
550
 
551
+ ## Write Data Metadata
552
+ Assign or update metadata on a **data set** (CellData record), the same way as cells, using either:
553
+ - the built-in field `"name"` (renames the data set) or `"dataSourceKey"`
554
+ - the human-readable field name of a user-defined property (e.g., "Active Cycle Test?")
555
+ - or the internal propertyDefinitionId (UUID)
556
+
557
+ User-defined property names are resolved against the data-set metadata definitions, which you can list with `api.list_data_metadata_definitions()`.
558
+
559
+ **Per-entity atomic:** all changes targeting a single data set commit or rollback together; other data sets are unaffected. Returns a `BatchUpdateResult` with `.success` and `.errors`, identical to `write_cell_metadata`.
560
+
561
+ #### 📘 Examples
562
+
563
+ ```python
564
+ # Example 1: Set a user-defined property on specific data sets
565
+ changes = [
566
+ {"id": "your-cell-data-guid-here", "field": "Active Cycle Test?", "value": True},
567
+ ]
568
+
569
+ result = api.write_data_metadata(changes=changes)
570
+ if not result:
571
+ for e in result.errors:
572
+ print(f"{e['itemId']}: {e['error']}")
573
+ ```
574
+
575
+ ```python
576
+ # Example 2: Rename a data set and update its dataSourceKey
577
+ changes = [
578
+ {"id": "your-cell-data-guid-here", "field": "name", "value": "Formation_Run_42"},
579
+ {"id": "your-cell-data-guid-here", "field": "dataSourceKey", "value": "Formation"},
580
+ ]
581
+
582
+ result = api.write_data_metadata(changes=changes)
583
+ assert result.success, result.errors
584
+ ```
585
+
586
+ ```python
587
+ # Example 3: Update using propertyDefinitionId (advanced)
588
+ changes = [
589
+ {
590
+ "id": "your-cell-data-guid-here",
591
+ "propertyDefinitionId": "your-property-definition-guid",
592
+ "value": "true"
593
+ }
594
+ ]
595
+
596
+ result = api.write_data_metadata(changes=changes)
597
+ assert result.success, result.errors
598
+ ```
599
+
551
600
  ## Stitch Data
552
601
  Combine multiple data sets into a single stitched data set. This is useful for creating continuous test data from multiple separate test runs.
553
602
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "micantis"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  description = "Package to simplify Micantis API usage"
5
5
  authors = [
6
6
  { name = "Mykela DeLuca", email = "mykela.deluca@micantis.io" }
@@ -6,12 +6,14 @@ Mocks the HTTP layer so it can run anywhere without a live server. Exercises:
6
6
  - 207 Multi-Status → BatchUpdateResult(success=False, errors=[...])
7
7
  - 400 Bad Request → BatchUpdateResult(success=False, errors=[...])
8
8
  - Truthiness (`if result:` works)
9
- - Rejection of unknown fields (client-side validation)
9
+ - User-property name resolution and propertyDefinitionId pass-through
10
10
 
11
11
  Run: pytest
12
12
  """
13
13
  from unittest.mock import MagicMock
14
14
 
15
+ import pandas as pd
16
+
15
17
  from micantis.api_wrapper import MicantisAPI, BatchUpdateResult
16
18
 
17
19
 
@@ -115,18 +117,55 @@ def test_write_data_207_returns_partial():
115
117
  print("PASS: data/updatemetadata 207 → partial")
116
118
 
117
119
 
118
- def test_write_data_rejects_unknown_field():
120
+ def test_write_data_unknown_property_name_raises():
121
+ # A non-built-in field is treated as a user-property name and resolved against the
122
+ # CellData metadata definitions; a name with no matching definition raises.
119
123
  api = _make_api()
124
+ api._list_data_metadata_definitions = lambda return_format, timeout: pd.DataFrame(
125
+ {"name": ["Active Cycle Test?"], "id": ["def-1"]}
126
+ )
120
127
  try:
121
128
  api._write_data_metadata(
122
- [{"id": "x", "field": "Technician", "value": "foo"}], timeout=5,
129
+ [{"id": "x", "field": "Nonexistent Property", "value": "foo"}], timeout=5,
123
130
  )
124
131
  except RuntimeError as e:
125
- assert "Technician" in str(e)
126
- assert "name" in str(e) and "dataSourceKey" in str(e)
127
- print("PASS: data/updatemetadata rejects unknown field client-side")
132
+ assert "Nonexistent Property" in str(e)
133
+ assert "not found in metadata definitions" in str(e)
134
+ print("PASS: data/updatemetadata raises on unknown user-property name")
128
135
  return
129
- raise AssertionError("expected RuntimeError on unknown field")
136
+ raise AssertionError("expected RuntimeError on unknown property name")
137
+
138
+
139
+ def test_write_data_resolves_property_name():
140
+ # A user-property name is resolved to its definition ID and sent as propertyDefinitionId.
141
+ api = _make_api()
142
+ api._list_data_metadata_definitions = lambda return_format, timeout: pd.DataFrame(
143
+ {"name": ["Active Cycle Test?"], "id": ["def-1"]}
144
+ )
145
+ api.session.post.return_value = _mock_response(204)
146
+ result = api._write_data_metadata(
147
+ [{"id": "x", "field": "Active Cycle Test?", "value": True}], timeout=5,
148
+ )
149
+ assert result.success is True
150
+ sent = api.session.post.call_args.kwargs["json"]["changes"][0]
151
+ assert sent == {"id": "x", "value": "True", "propertyDefinitionId": "def-1"}
152
+ print("PASS: data/updatemetadata resolves user-property name → propertyDefinitionId")
153
+
154
+
155
+ def test_write_data_accepts_property_definition_id():
156
+ # A propertyDefinitionId is forwarded directly, with no definitions lookup.
157
+ api = _make_api()
158
+ api._list_data_metadata_definitions = MagicMock(
159
+ side_effect=AssertionError("should not look up definitions when an id is supplied")
160
+ )
161
+ api.session.post.return_value = _mock_response(204)
162
+ result = api._write_data_metadata(
163
+ [{"id": "x", "propertyDefinitionId": "def-9", "value": "true"}], timeout=5,
164
+ )
165
+ assert result.success is True
166
+ sent = api.session.post.call_args.kwargs["json"]["changes"][0]
167
+ assert sent == {"id": "x", "value": "true", "propertyDefinitionId": "def-9"}
168
+ print("PASS: data/updatemetadata forwards propertyDefinitionId directly")
130
169
 
131
170
 
132
171
  def test_name_is_accepted_as_builtin():
File without changes
File without changes
File without changes
File without changes