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.
- {micantis-1.2.3 → micantis-1.2.4}/PKG-INFO +50 -1
- {micantis-1.2.3 → micantis-1.2.4}/README.md +49 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis/api_wrapper.py +242 -26
- {micantis-1.2.3 → micantis-1.2.4}/micantis.egg-info/PKG-INFO +50 -1
- {micantis-1.2.3 → micantis-1.2.4}/pyproject.toml +1 -1
- {micantis-1.2.3 → micantis-1.2.4}/tests/test_batch_update_result.py +46 -7
- {micantis-1.2.3 → micantis-1.2.4}/LICENSE +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis/__init__.py +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis/utils.py +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis.egg-info/SOURCES.txt +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis.egg-info/dependency_links.txt +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis.egg-info/requires.txt +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/micantis.egg-info/top_level.txt +0 -0
- {micantis-1.2.3 → micantis-1.2.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: micantis
|
|
3
|
-
Version: 1.2.
|
|
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
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
-
-
|
|
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",
|
|
1188
|
-
... {"id": cell_data_id, "field": "dataSourceKey",
|
|
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
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
"
|
|
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'.
|
|
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
|
-
|
|
2240
|
-
api.
|
|
2241
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
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
|
|
|
@@ -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
|
-
-
|
|
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
|
|
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": "
|
|
129
|
+
[{"id": "x", "field": "Nonexistent Property", "value": "foo"}], timeout=5,
|
|
123
130
|
)
|
|
124
131
|
except RuntimeError as e:
|
|
125
|
-
assert "
|
|
126
|
-
assert "
|
|
127
|
-
print("PASS: data/updatemetadata
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|