Getting Started
Alarm API
Overview
The AlarmClient class is the primary entry point of the Alarm API.
When constructing an AlarmClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let AlarmClient use the
default connection. The default connection depends on your environment.
With an AlarmClient object, you can:
Create and update alarm instances using
create_or_update_alarm()Alarms have two key identifiers:
alarm_id: A user-defined identifier for the alarm typeinstance_id: A server-generated unique identifier for each alarm occurrence
Create alarm transitions (SET, CLEAR) to track alarm state changes
Query alarms with
query_alarms()Filter alarms using Dynamic LINQ expressions
Control which transitions are returned (most recent only or all)
Sort and paginate results
Get a specific alarm by its instance_id using
get_alarm()Acknowledge alarms by its instance_id using
acknowledge_alarms()Optionally force-clear alarms when acknowledging
Delete alarms using
delete_alarm()ordelete_alarms()
Examples
Create, query, acknowledge, and delete alarms
1import uuid
2from datetime import datetime
3
4from nisystemlink.clients.alarm import AlarmClient
5from nisystemlink.clients.alarm.models import (
6 AlarmOrderBy,
7 AlarmSeverityLevel,
8 ClearAlarmTransition,
9 CreateOrUpdateAlarmRequest,
10 QueryAlarmsWithFilterRequest,
11 SetAlarmTransition,
12 TransitionInclusionOption,
13)
14from nisystemlink.clients.core import ApiException, HttpConfiguration
15
16# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
17server_configuration: HttpConfiguration | None = None
18
19# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
20# the following lines and provide your server URI and API key.
21# server_configuration = HttpConfiguration(
22# server_uri="https://yourserver.yourcompany.com",
23# api_key="",
24# )
25
26client = AlarmClient(configuration=server_configuration)
27
28# Create a unique alarm ID for this example
29# alarm_id is a user-defined identifier for this alarm
30alarm_id = f"example_alarm_{uuid.uuid1().hex}"
31
32# Create an alarm with a SET transition
33create_request = CreateOrUpdateAlarmRequest(
34 alarm_id=alarm_id,
35 transition=SetAlarmTransition(
36 occurred_at=datetime.now(),
37 severity_level=AlarmSeverityLevel.HIGH,
38 value="85",
39 condition="Greater than 80",
40 short_text="Temperature is high",
41 detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)",
42 ),
43)
44# Returns instance_id - a server-generated unique identifier for this specific alarm occurrence
45id = client.create_or_update_alarm(create_request)
46
47# Get the alarm by its instance ID (the unique occurrence identifier)
48alarm = client.get_alarm(instance_id=id)
49print(f"Retrieved alarm: {alarm.alarm_id}, Condition: {alarm.condition}")
50
51# Update the alarm with a higher severity (same alarm_id, updates the same instance)
52update_request = CreateOrUpdateAlarmRequest(
53 alarm_id=alarm_id,
54 transition=SetAlarmTransition(
55 occurred_at=datetime.now(),
56 severity_level=AlarmSeverityLevel.CRITICAL,
57 value="95",
58 condition="Greater than 90",
59 short_text="Temperature is critical",
60 detail_text="Temperature sensor reading is 95°C (higher than the configured threshold of 90°C)",
61 ),
62)
63client.create_or_update_alarm(update_request)
64
65# Query alarms with a filter (can filter by alarm_id to find all instances)
66# Include all transitions to see the full alarm history
67query_request = QueryAlarmsWithFilterRequest(
68 filter="alarmId=@0",
69 substitutions=[alarm_id],
70 order_by=AlarmOrderBy.UPDATED_AT,
71 order_by_descending=True,
72 transition_inclusion_option=TransitionInclusionOption.ALL,
73 return_count=True,
74)
75query_response = client.query_alarms(query_request)
76
77# Display query results
78print(f"Total alarms found: {query_response.total_count}")
79for alarm in query_response.alarms:
80 print(f" Alarm ID: {alarm.alarm_id}, Transitions: {len(alarm.transitions)}")
81 for transition in alarm.transitions:
82 print(f"- {transition.transition_type}: {transition.condition}")
83
84# Acknowledge the alarm
85client.acknowledge_alarms(instance_ids=[id])
86
87# Clear the alarm with 409 conflict handling - Method 1: Manual exception handling
88# A 409 Conflict response indicates that the requested transition would not change the alarm's state.
89# This allows stateless applications to simply attempt state transitions without first checking
90# the current state. For example, a monitoring system can repeatedly try to CLEAR an alarm
91# when conditions return to normal, and the API will return 409 if already cleared.
92clear_request = CreateOrUpdateAlarmRequest(
93 alarm_id=alarm_id,
94 transition=ClearAlarmTransition(
95 occurred_at=datetime.now(),
96 condition="Temperature returned to normal",
97 ),
98)
99try:
100 client.create_or_update_alarm(clear_request)
101 print("Alarm cleared successfully")
102except ApiException as e:
103 if e.http_status_code == 409:
104 print("Alarm is already in the requested state (409 Conflict)")
105 else:
106 raise
107
108# Clear the alarm with 409 conflict handling - Method 2: Using ignore_conflict parameter
109# This approach is cleaner for stateless applications that don't care about 409 errors.
110# Returns None if the alarm is already in the requested state.
111result = client.create_or_update_alarm(clear_request, ignore_conflict=True)
112if result is None:
113 print("No state change needed (alarm already in requested state)")
114else:
115 print(f"Alarm cleared successfully: {result}")
116
117# Delete the alarm by its instance ID
118client.delete_alarm(instance_id=id)
Tag API
Overview
The TagManager class is the primary entry point of the Tag API.
When constructing a TagManager, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let TagManager use the
default connection. The default connection depends on your SystemLink Client
settings.
With a TagManager object, you can:
Query, create, modify, and delete tags from the server
Use
open()to get a tag’sTagDatafrom the server when you know the tag’spath.Use
query()to get acollectionofTagDataobjects based on the tags’paths,keywords, and/orproperties.Use
refresh()to update a list ofTagDataobjects with fresh metadata from the server.Use
update()to modify the server metadata for a list of tags, using eitherTagDataobjects to overwrite the server’s tag data orTagDataUpdateobjects to selectively update specific fields.Use
delete()to delete one or more tags from the server.
Read and write tag values
Use
read()to get a tag’s current value. Via method parameters, you can also request the timestamp indicating when that value was last written and/or the aggregate data stored for the tag (if the tag’scollect_aggregatesattribute is enabled on the server).Use
create_writer()to get aBufferedTagWriterthat will buffer a set of writes, and automatically send the writes when a givenbuffer_sizeis reached or whenmax_buffer_timehas elapsed (or whensend_buffered_writes()is called).
Get a
TagSelectionthat can help perform several of the above operations on several tags at onceUse
TagManager.create_selection()if you already have a list ofTagDataobjects that you want to perform a set of operations on.Use
TagManager.open_selection()if you just have a list ofpaths– optionally including glob-style wildcards! – with which to create the selection.
If you have a TagSelection, you can use it to create a TagSubscription that will
trigger a tag_changed event any time one of the tags’
values is changed.
Examples
Read and write individual tags
1from datetime import timedelta
2
3from nisystemlink.clients.tag import DataType, TagManager
4
5mgr = TagManager()
6tag = mgr.open("MyTags.Example Tag", DataType.DOUBLE, create=True)
7
8with mgr.create_writer(buffer_size=10, max_buffer_time=timedelta(seconds=3)) as writer:
9 writer.write(tag.path, tag.data_type, 3.5)
10# Note: Exiting the "with" block automatically calls writer.send_buffered_writes()
11
12read_result = mgr.read(tag.path)
13assert read_result is not None
14assert read_result.value == 3.5
Subscribe to tag changes
1from contextlib import ExitStack
2from time import sleep
3
4from nisystemlink.clients.tag import DataType, TagData, TagManager, TagValueReader
5
6SIMULATE_EXTERNAL_TAG_CHANGES = True
7
8
9def on_tag_changed(tag: TagData, reader: TagValueReader) -> None:
10 """Callback for tag_changed events."""
11 path = tag.path
12 data_type = tag.data_type.name
13
14 if reader is not None:
15 read_result = reader.read()
16 # A read_result of None means that the tag has no value, but it *must*
17 # have a value, because we got a tag_changed event!
18 assert read_result is not None
19 value = read_result.value
20 else:
21 value = "???" # tag has unknown data type
22
23 print(f'Tag changed: "{path}" = {value} ({data_type})')
24
25
26mgr = TagManager()
27if SIMULATE_EXTERNAL_TAG_CHANGES:
28 mgr.open("MyTags.Example Tag", DataType.DOUBLE, create=True)
29 writer = mgr.create_writer(buffer_size=1)
30
31with ExitStack() as stack:
32 # Notes:
33 # 1. The tags are assumed to already exist before this example is run, but
34 # setting SIMULATE_EXTERNAL_TAG_CHANGES to True will ensure there is one.
35 # 2. Any tags that get added later will NOT automatically appear in the
36 # selection just because the path matches the wildcard used below; you
37 # must call one of the selection's refresh methods to update the tag list
38 # from the server. But even if you do that:
39 # 3. The subscription will only contain the tags that were in the selection
40 # when the subscription was created. If you want the subscription to add
41 # new tags that were added to the selection, you must recreate it.
42 paths = ["MyTags.*"]
43 selection = stack.enter_context(mgr.open_selection(paths))
44 if not selection.metadata:
45 print(f"Found no tags that match {paths}")
46 else:
47 print("Matching tags:")
48 for path in selection.metadata.keys():
49 print(f" - {path}")
50 print()
51
52 subscription = stack.enter_context(selection.create_subscription())
53 subscription.tag_changed += on_tag_changed
54
55 # Wait forever, until a KeyboardInterrupt (Ctrl+C)
56 print("Watching for tag changes; hit Ctrl+C to stop")
57 try:
58 i = 0
59 while True:
60 sleep(1)
61 if SIMULATE_EXTERNAL_TAG_CHANGES:
62 writer.write("MyTags.Example Tag", DataType.DOUBLE, i)
63 i += 1
64 except KeyboardInterrupt:
65 pass
Product API
Overview
The ProductClient class is the primary entry point of the Product API.
When constructing a ProductClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let ProductClient use the
default connection. The default connection depends on your environment.
With a ProductClient object, you can:
Create, update, query, and delete Products
Examples
Create, query, update, and delete some products
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.product import ProductClient
3from nisystemlink.clients.product.models import (
4 CreateProductRequest,
5 ProductField,
6 ProductOrderBy,
7 QueryProductsRequest,
8 QueryProductValuesRequest,
9)
10
11name = "Example Name"
12family = "Example Family"
13
14
15def create_some_products():
16 """Create two example products on your server."""
17 new_products = [
18 CreateProductRequest(
19 part_number="Example 123 AA",
20 name=name,
21 family=family,
22 keywords=["original keyword"],
23 properties={"original property key": "yes"},
24 ),
25 CreateProductRequest(
26 part_number="Example 123 AA1",
27 name=name,
28 family=family,
29 keywords=["original keyword"],
30 properties={"original property key": "original"},
31 ),
32 ]
33 create_response = client.create_products(new_products)
34 return create_response
35
36
37# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
38server_configuration: HttpConfiguration | None = None
39
40# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
41# the following lines and provide your server URI and API key.
42# server_configuration = HttpConfiguration(
43# server_uri="https://yourserver.yourcompany.com",
44# api_key="",
45# )
46
47client = ProductClient(configuration=server_configuration)
48
49# Get all the products using the continuation token in batches of 100 at a time.
50response = client.get_products_paged(take=100, return_count=True)
51all_products = response.products
52while response.continuation_token:
53 response = client.get_products_paged(
54 take=100, continuation_token=response.continuation_token, return_count=True
55 )
56 all_products.extend(response.products)
57
58create_response = create_some_products()
59
60# use get for first product created
61created_product = client.get_product(create_response.products[0].id)
62
63# Query products without continuation
64query_request = QueryProductsRequest(
65 filter=f'family="{family}" && name="{name}"',
66 return_count=True,
67 order_by=ProductOrderBy.FAMILY,
68)
69query_response = client.query_products_paged(query_request)
70
71# Update the first product that you just created and replace the keywords
72updated_product = create_response.products[0]
73updated_product.keywords = ["new keyword"]
74updated_product.properties = {"new property key": "new value"}
75update_response = client.update_products([create_response.products[0]], replace=True)
76
77# Query for just the ids of products that match the family
78values_query = QueryProductValuesRequest(
79 filter=f'family="{family}"', field=ProductField.ID
80)
81values_response = client.query_product_values(query=values_query)
82
83# delete each created product individually by id
84for product in create_response.products:
85 client.delete_product(product.id)
86
87# Create some more and delete them with a single call to delete.
88create_response = create_some_products()
89client.delete_products([product.id for product in create_response.products])
DataFrame API
Overview
The DataFrameClient class is the primary entry point of the DataFrame API.
When constructing a DataFrameClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let DataFrameClient use the
default connection. The default connection depends on your environment.
With a DataFrameClient object, you can:
Create and delete data tables.
Modify table metadata and query for tables by their metadata.
Append rows of data to a table, query for rows of data from a table, and decimate table data.
Export table data in a comma-separated values (CSV) format.
Examples
Create and write data to a table
1import random
2from datetime import datetime
3
4try:
5 import pyarrow as pa # type: ignore
6except Exception:
7 pa = None
8try:
9 import pandas as pd # type: ignore
10except Exception:
11 pd = None
12from nisystemlink.clients.core import HttpConfiguration
13from nisystemlink.clients.dataframe import DataFrameClient
14from nisystemlink.clients.dataframe.models import (
15 AppendTableDataRequest,
16 Column,
17 ColumnType,
18 CreateTableRequest,
19 DataFrame,
20 DataType,
21)
22
23# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
24server_configuration: HttpConfiguration | None = None
25
26# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
27# the following lines and provide your server URI and API key.
28# server_configuration = HttpConfiguration(
29# server_uri="https://yourserver.yourcompany.com",
30# api_key="",
31# )
32
33client = DataFrameClient(configuration=server_configuration)
34
35# Create table
36table_id = client.create_table(
37 CreateTableRequest(
38 name="Example Table",
39 columns=[
40 Column(name="ix", data_type=DataType.Int32, column_type=ColumnType.Index),
41 Column(name="Float_Column", data_type=DataType.Float32),
42 Column(name="Timestamp_Column", data_type=DataType.Timestamp),
43 ],
44 )
45)
46
47
48print(f"Created table with ID: {table_id}")
49
50print("Appending data to table...")
51
52# Append via explicit AppendTableDataRequest (JSON)
53frame_request = DataFrame(
54 data=[[str(i), str(random.random()), datetime.now().isoformat()] for i in range(3)]
55)
56client.append_table_data(table_id, AppendTableDataRequest(frame=frame_request))
57
58# Append via DataFrame model directly (JSON)
59frame_direct = DataFrame(
60 data=[
61 [str(i + 3), str(random.random()), datetime.now().isoformat()] for i in range(3)
62 ]
63)
64client.append_table_data(table_id, frame_direct)
65
66if pa is not None:
67 print("Appending data to table via Arrow RecordBatches...")
68 # Append via single RecordBatch (Arrow)
69 batch_single = pa.record_batch(
70 [
71 pa.array([6, 7, 8], type=pa.int32()),
72 pa.array([0.1, 0.2, 0.3], type=pa.float32()),
73 pa.array([datetime.now() for _ in range(3)], pa.timestamp("ms")),
74 ],
75 names=["ix", "Float_Column", "Timestamp_Column"],
76 )
77 client.append_table_data(table_id, batch_single)
78
79 # Append via iterable of RecordBatches (Arrow)
80 batch_list = [
81 pa.record_batch(
82 [
83 pa.array([9, 10], type=pa.int32()),
84 pa.array([0.4, 0.5], type=pa.float32()),
85 pa.array([datetime.now() for _ in range(2)], pa.timestamp("ms")),
86 ],
87 names=["ix", "Float_Column", "Timestamp_Column"],
88 )
89 ]
90 client.append_table_data(table_id, batch_list)
91
92if pa is not None and pd is not None:
93 print("Appending data to table via Pandas DataFrame...")
94 # Append via DataFrame (Pandas)
95 df = pd.DataFrame(
96 {
97 "ix": [11, 12, 13],
98 "Float_Column": [0.6, 0.7, 0.8],
99 "Timestamp_Column": [datetime.now() for _ in range(3)],
100 }
101 )
102
103 # Optional - coerce df types to the dataframe table schema
104 df = df.astype(
105 {
106 "ix": "Int32",
107 "Float_Column": "float32",
108 "Timestamp_Column": "datetime64[ns]",
109 }
110 )
111
112 # convert Pandas DataFrame to Arrow RecordBatch and append
113 record_batch = pa.RecordBatch.from_pandas(df)
114 client.append_table_data(table_id, record_batch)
115
116# Mark end_of_data for the table
117# Supply `None` and `end_of_data=True`
118print("Finished appending data.")
119client.append_table_data(table_id, None, end_of_data=True)
Query and read data from a table
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.dataframe import DataFrameClient
3from nisystemlink.clients.dataframe.models import (
4 ColumnType,
5 DecimationMethod,
6 DecimationOptions,
7 QueryDecimatedDataRequest,
8)
9
10# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
11server_configuration: HttpConfiguration | None = None
12
13# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
14# the following lines and provide your server URI and API key.
15# server_configuration = HttpConfiguration(
16# server_uri="https://yourserver.yourcompany.com",
17# api_key="",
18# )
19
20client = DataFrameClient(configuration=server_configuration)
21
22# List a table
23response = client.list_tables(take=1)
24table = response.tables[0]
25y_column = next(
26 (col for col in table.columns if col.column_type != ColumnType.Index),
27 table.columns[0],
28)
29
30# Get table metadata by table id
31client.get_table_metadata(table.id)
32
33# Query decimated table data
34request = QueryDecimatedDataRequest(
35 decimation=DecimationOptions(
36 x_column=None,
37 y_columns=[y_column.name],
38 intervals=1,
39 method=DecimationMethod.MaxMin,
40 )
41)
42rows = client.query_decimated_data(table.id, request)
43print(rows.frame.model_dump())
Export data from a table
1from shutil import copyfileobj
2
3from nisystemlink.clients.core import HttpConfiguration
4from nisystemlink.clients.dataframe import DataFrameClient
5from nisystemlink.clients.dataframe.models import (
6 ColumnFilter,
7 ColumnOrderBy,
8 ColumnType,
9 DataType,
10 ExportFormat,
11 ExportTableDataRequest,
12 FilterOperation,
13)
14
15# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
16server_configuration: HttpConfiguration | None = None
17
18# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
19# the following lines and provide your server URI and API key.
20# server_configuration = HttpConfiguration(
21# server_uri="https://yourserver.yourcompany.com",
22# api_key="",
23# )
24
25client = DataFrameClient(configuration=server_configuration)
26
27# List a table
28response = client.list_tables(take=1)
29table = response.tables[0]
30index_column = next(
31 (col for col in table.columns if col.column_type == ColumnType.Index)
32)
33order_column = next(
34 (col for col in table.columns if col.name != index_column.name), index_column
35)
36filter_value = (
37 "0001-01-01T00:00:00Z" if index_column.data_type == DataType.Timestamp else "0"
38)
39
40# Export the first 100,000 rows of table data with query options
41request = ExportTableDataRequest(
42 columns=[index_column.name],
43 order_by=[ColumnOrderBy(column=order_column.name, descending=True)],
44 filters=[
45 ColumnFilter(
46 column=index_column.name,
47 operation=FilterOperation.NotEquals,
48 value=filter_value,
49 )
50 ],
51 take=100000,
52 response_format=ExportFormat.CSV,
53)
54
55data = client.export_table_data(id=table.id, query=request)
56
57# Write the export data to a file
58with open(f"{table.name}.csv", "wb") as f:
59 copyfileobj(data, f)
60
61# Alternatively, load the export data into a pandas dataframe
62# import pandas as pd
63# df = pd.read_csv(data)
Spec API
Overview
The SpecClient class is the primary entry point of the Specification Compliance API.
When constructing a SpecClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let SpecClient use the
default connection. The default connection depends on your environment.
With a SpecClient object, you can:
Create and delete specifications under a product.
Modify any fields of an existing specification
Query for specifications on any fields using DynamicLinq syntax.
Get a specification using an Id.
Examples
Create, Get and Query Specifications
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.spec import SpecClient
3from nisystemlink.clients.spec.models import (
4 Condition,
5 ConditionRange,
6 ConditionType,
7 CreateSpecificationsRequest,
8 CreateSpecificationsRequestObject,
9 NumericConditionValue,
10 QuerySpecificationsRequest,
11 SpecificationLimit,
12 SpecificationType,
13)
14
15# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
16server_configuration: HttpConfiguration | None = None
17
18# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
19# the following lines and provide your server URI and API key.
20# server_configuration = HttpConfiguration(
21# server_uri="https://yourserver.yourcompany.com",
22# api_key="",
23# )
24
25client = SpecClient(configuration=server_configuration)
26
27# Create the spec requests
28product = "Amplifier"
29spec_requests = [
30 CreateSpecificationsRequestObject(
31 product_id=product,
32 spec_id="spec1",
33 type=SpecificationType.PARAMETRIC,
34 category="Parametric Specs",
35 name="output voltage",
36 limit=SpecificationLimit(min=1.2, max=1.5),
37 unit="mV",
38 ),
39 CreateSpecificationsRequestObject(
40 product_id=product,
41 spec_id="spec2",
42 type=SpecificationType.PARAMETRIC,
43 category="Parametric Specs",
44 name="input voltage",
45 limit=SpecificationLimit(min=0.02, max=0.15),
46 unit="mV",
47 conditions=[
48 Condition(
49 name="Temperature",
50 value=NumericConditionValue(
51 condition_type=ConditionType.NUMERIC,
52 range=[ConditionRange(min=-25, step=20, max=85)],
53 unit="C",
54 ),
55 ),
56 Condition(
57 name="Supply Voltage",
58 value=NumericConditionValue(
59 condition_type=ConditionType.NUMERIC,
60 discrete=[1.3, 1.5, 1.7],
61 unit="mV",
62 ),
63 ),
64 ],
65 ),
66 CreateSpecificationsRequestObject(
67 product_id=product,
68 spec_id="spec3",
69 type=SpecificationType.FUNCTIONAL,
70 category="Noise Thresholds",
71 name="noise",
72 ),
73]
74
75# Create the specs on the server
76created_response = client.create_specs(CreateSpecificationsRequest(specs=spec_requests))
77
78# use get for first spec created
79if created_response.created_specs and len(created_response.created_specs) > 0:
80 created_spec = client.get_spec(created_response.created_specs[0].id)
81
82# You can query specs based on any field using DynamicLinq syntax.
83# These are just some representative examples.
84
85response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
86all_product_specs = response.specs
87
88# Query based on spec id
89response = client.query_specs(
90 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec2"')
91)
92if response.specs:
93 spec2 = response.specs[0]
94
95# Query based on name
96response = client.query_specs(
97 QuerySpecificationsRequest(product_ids=[product], filter='name.Contains("voltage")')
98)
99voltage_specs = response.specs
100
101# Query based on Category
102response = client.query_specs(
103 QuerySpecificationsRequest(
104 product_ids=[product], filter='category == "Noise Thresholds"'
105 )
106)
107noise_category = response.specs
108print(noise_category)
Update and Delete Specifications
1from typing import cast
2
3from nisystemlink.clients.core import HttpConfiguration
4from nisystemlink.clients.spec import SpecClient
5from nisystemlink.clients.spec.models import (
6 QuerySpecificationsRequest,
7 SpecificationType,
8 UpdateSpecificationsRequest,
9 UpdateSpecificationsRequestObject,
10)
11
12# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
13server_configuration: HttpConfiguration | None = None
14
15# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
16# the following lines and provide your server URI and API key.
17# server_configuration = HttpConfiguration(
18# server_uri="https://yourserver.yourcompany.com",
19# api_key="",
20# )
21
22client = SpecClient(configuration=server_configuration)
23
24# The query and delete examples assume you have created the specs from the query_specs example
25product = "Amplifier"
26
27# update spec1 to change the block to "modifiedBlock"
28# query the original spec
29response = client.query_specs(
30 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
31)
32if response.specs:
33 original_spec1 = response.specs[0]
34 print(f"Original spec1 block: {original_spec1.block}")
35 print(f"Original spec1 version: {original_spec1.version}")
36
37 # make the modifications
38 modified_spec = UpdateSpecificationsRequestObject(
39 id=cast(str, original_spec1.id),
40 product_id=cast(str, original_spec1.product_id),
41 spec_id=cast(str, original_spec1.spec_id),
42 type=SpecificationType.FUNCTIONAL,
43 keywords=["work", "reviewed"],
44 block="modifiedBlock",
45 version=cast(int, original_spec1.version),
46 workspace=cast(str, original_spec1.workspace),
47 )
48 update_response = client.update_specs(
49 specs=UpdateSpecificationsRequest(specs=[modified_spec])
50 )
51 if update_response and update_response.updated_specs:
52 print(f"New spec1 version: {update_response.updated_specs[0].version}")
53
54# query again to see new version
55response = client.query_specs(
56 QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
57)
58if response.specs:
59 original_spec1 = response.specs[0]
60 print(f"Modified spec1 block: {original_spec1.block}")
61
62# delete all the specs for the product
63# query all specs
64response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
65if response.specs:
66 client.delete_specs(ids=[spec.id for spec in response.specs if spec.id])
File API
Overview
The FileClient class is the primary entry point of the File API.
When constructing a FileClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let FileClient use the
default connection. The default connection depends on your environment.
With a FileClient object, you can:
Get the list of files, query and search for files, download and delete files.
Start upload sessions, upload file chunks, and finish sessions for large file uploads.
Examples
Get the metadata of a File using its Id and download it.
1"""Example to download a file from SystemLink."""
2
3from shutil import copyfileobj
4
5from nisystemlink.clients.core import HttpConfiguration
6from nisystemlink.clients.file import FileClient
7
8# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
9server_configuration: HttpConfiguration | None = None
10
11# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
12# the following lines and provide your server URI and API key.
13# server_configuration = HttpConfiguration(
14# server_uri="https://yourserver.yourcompany.com",
15# api_key="",
16# )
17
18client = FileClient(configuration=server_configuration)
19
20file_id = "a55adc7f-5068-4202-9d70-70ca6a06bee9"
21
22# Fetch the file metadata to get the name
23files = client.get_files(ids=[file_id])
24
25if not files.available_files:
26 raise Exception(f"File ID {file_id} not found.")
27
28
29file_name = "Untitled"
30
31file_properties = files.available_files[0].properties
32
33if file_properties:
34 file_name = file_properties["Name"]
35
36# Download the file using FileId with content inline
37content = client.download_file(id=file_id)
38
39# Write the content to a file
40with open(file_name, "wb") as f:
41 copyfileobj(content, f)
Upload a File from disk or memory to SystemLink
1"""Example to upload a file to SystemLink."""
2
3import io
4
5from nisystemlink.clients.core import HttpConfiguration
6from nisystemlink.clients.file import FileClient
7
8# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
9server_configuration: HttpConfiguration | None = None
10
11# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
12# the following lines and provide your server URI and API key.
13# server_configuration = HttpConfiguration(
14# server_uri="https://yourserver.yourcompany.com",
15# api_key="",
16# )
17
18client = FileClient(configuration=server_configuration)
19
20workspace_id = None # Upload to default workspace of the auth key
21
22# Upload file from disk
23file_path = "path/to/your/file"
24with open(file_path, "rb") as fp:
25 file_id = client.upload_file(file=fp, workspace=workspace_id)
26 print(f"Uploaded file from {file_path} to SystemLink with FileID - {file_id}")
27
28# Upload file-like object from memory
29test_file = io.BytesIO(b"This is an example file content.")
30test_file.name = "File_From_Memory.txt" # assign a name to the file object
31file_id = client.upload_file(file=test_file)
32print(f"Uploaded file from memory to SystemLink with FileID - {file_id}")
Search for files with filtering and pagination
1"""Example demonstrating how to search for files using the File API.
2
3Note:
4 This example requires Elasticsearch to be configured in the SystemLink cluster.
5 If Elasticsearch is not configured, this example will fail with an ApiException.
6 For deployments without Elasticsearch, use query_files_linq() instead.
7"""
8
9import io
10import time
11
12from nisystemlink.clients.core import HttpConfiguration
13from nisystemlink.clients.file import FileClient, models
14from nisystemlink.clients.file.models import SearchFilesOrderBy, UpdateMetadataRequest
15
16# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
17server_configuration: HttpConfiguration | None = None
18
19# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
20# the following lines and provide your server URI and API key.
21# server_configuration = HttpConfiguration(
22# server_uri="https://yourserver.yourcompany.com",
23# api_key="",
24# )
25
26client = FileClient(configuration=server_configuration)
27
28# Upload a test file first
29print("Uploading test file...")
30test_file_content = b"This is a test file for search demonstration."
31test_file = io.BytesIO(test_file_content)
32test_file.name = "search-example-test-file.txt"
33
34file_id = client.upload_file(file=test_file)
35print(f"Uploaded test file with ID: {file_id}")
36
37# Wait for the file to be indexed for search
38# Note: Files may take a few seconds to appear in search results after upload
39time.sleep(5)
40print()
41
42# Example 1: Basic file search with filter - search for the uploaded file
43print("Example 1: Search for the uploaded test file")
44search_request = models.SearchFilesRequest(
45 filter='name:("search-example-test-file.txt")',
46 skip=0,
47 take=10,
48)
49
50response = client.search_files(search_request)
51print(
52 f"Found {response.total_count.value if response.total_count else 0} file(s) matching the filter"
53)
54if response.available_files:
55 for file in response.available_files:
56 if file.properties:
57 print(
58 f"- {file.properties.get('Name')} (ID: {file.id}, Size: {file.size} bytes)"
59 )
60
61# Example 2: Search with wildcard pattern
62print("\nExample 2: Search with wildcard pattern")
63search_request = models.SearchFilesRequest(
64 filter='name:("search-example*")',
65 skip=0,
66 take=20,
67 order_by=SearchFilesOrderBy.CREATED,
68 order_by_descending=True,
69)
70
71response = client.search_files(search_request)
72print(
73 f"Found {response.total_count.value if response.total_count else 0} file(s) starting with 'search-example'"
74)
75if response.available_files:
76 for file in response.available_files:
77 if file.properties:
78 print(
79 f"- {file.properties.get('Name')} created at {file.created} (Size: {file.size} bytes)"
80 )
81
82# Example 3: Search by size range
83print("\nExample 3: Search by size range")
84search_request = models.SearchFilesRequest(
85 filter="size:([1 TO 1000])",
86 skip=0,
87 take=10,
88)
89
90response = client.search_files(search_request)
91print(
92 f"Found {response.total_count.value if response.total_count else 0} file(s) between 1 and 1000 bytes"
93)
94if response.available_files:
95 for file in response.available_files:
96 if file.properties:
97 print(f"- {file.properties.get('Name')} (Size: {file.size} bytes)")
98
99# Example 4: Search by multiple custom properties
100print("\nExample 4: Search by multiple custom properties")
101print("Adding custom properties to existing file...")
102
103# Update the existing file with custom properties
104custom_metadata = UpdateMetadataRequest(
105 replace_existing=False,
106 properties={
107 "TestProperty1": "TestValue1",
108 "TestProperty2": "TestValue2",
109 },
110)
111client.update_metadata(metadata=custom_metadata, id=file_id)
112
113# Wait for indexing
114time.sleep(5)
115
116# Search by multiple custom properties using AND operator
117search_request = models.SearchFilesRequest(
118 filter='(properties.TestProperty1:"TestValue1") AND (properties.TestProperty2:"TestValue2")',
119 skip=0,
120 take=10,
121)
122
123response = client.search_files(search_request)
124print(
125 f"Found {response.total_count.value if response.total_count else 0} file(s) with "
126 "TestProperty1=TestValue1 AND TestProperty2=TestValue2"
127)
128if response.available_files:
129 for file in response.available_files:
130 if file.properties:
131 print(f"- {file.properties.get('Name')}")
132 print(f" TestProperty1: {file.properties.get('TestProperty1')}")
133 print(f" TestProperty2: {file.properties.get('TestProperty2')}")
134
135# Clean up: delete the test file
136print("\nCleaning up...")
137client.delete_file(id=file_id)
138print(f"Deleted test file with ID: {file_id}")
Feeds API
Overview
The FeedsClient class is the primary entry point of the Feeds API.
When constructing a FeedsClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let FeedsClient use the
default connection. The default connection depends on your environment.
With a FeedsClient object, you can:
Get the list of feeds, create feed, upload package to feed and delete feed.
Examples
Create a new feed.
1"""Functionality of creating feeds APIs."""
2
3from nisystemlink.clients.core import ApiException, HttpConfiguration
4from nisystemlink.clients.feeds import FeedsClient
5from nisystemlink.clients.feeds.models import (
6 CreateFeedRequest,
7 Platform,
8)
9
10# Update the constants.
11FEED_NAME = ""
12FEED_DESCRIPTION = ""
13PLATFORM = Platform.WINDOWS
14WORKSPACE_ID = (
15 None # None uses Default workspace. Replace with Systemlink workspace id.
16)
17
18# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
19server_configuration: HttpConfiguration | None = None
20
21# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
22# the following lines and provide your server URI and API key.
23# server_configuration = HttpConfiguration(
24# server_uri="https://yourserver.yourcompany.com",
25# api_key="",
26# )
27
28client = FeedsClient(configuration=server_configuration)
29
30# Creating Feeds.
31try:
32 feed_request = CreateFeedRequest(
33 name=FEED_NAME,
34 description=FEED_DESCRIPTION,
35 platform=PLATFORM,
36 workspace=WORKSPACE_ID,
37 )
38 feed_details = client.create_feed(feed=feed_request)
39
40 print("Feed created Successfully.")
41 print(f"Created feed details: {feed_details}")
42
43except ApiException as exp:
44 print(exp)
45except Exception as exp:
46 print(exp)
Query feeds and upload a package to feed.
1"""Functionality of uploading & querying feeds APIs."""
2
3from nisystemlink.clients.core import ApiException, HttpConfiguration
4from nisystemlink.clients.feeds import FeedsClient
5from nisystemlink.clients.feeds.models import Platform
6from nisystemlink.clients.feeds.utilities import get_feed_by_name
7
8# Update the constants.
9FEED_NAME = ""
10PLATFORM = None
11FEED_DESCRIPTION = ""
12PLATFORM = Platform.WINDOWS
13WORKSPACE_ID = (
14 None # None uses Default workspace. Replace with Systemlink workspace id.
15)
16PACKAGE_PATH = ""
17
18# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
19server_configuration: HttpConfiguration | None = None
20
21# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
22# the following lines and provide your server URI and API key.
23# server_configuration = HttpConfiguration(
24# server_uri="https://yourserver.yourcompany.com",
25# api_key="",
26# )
27
28client = FeedsClient(configuration=server_configuration)
29
30# To upload a package to feed.
31try:
32 # Get ID of the Feed to upload by name
33 feeds = client.query_feeds(platform=PLATFORM, workspace=WORKSPACE_ID)
34 feed = get_feed_by_name(feeds=feeds, name=FEED_NAME)
35 feed_id = feed.id if feed else None
36
37 # Upload the package to Feed by ID
38 if feed_id:
39 client.upload_package(
40 feed_id=feed_id,
41 overwrite=True,
42 package_file_path=PACKAGE_PATH,
43 )
44 print("Package uploaded sucessfully.")
45
46except ApiException as exp:
47 print(exp)
48
49except Exception as exp:
50 print(exp)
Delete a feed.
1"""Functionality of deleting feed API."""
2
3from nisystemlink.clients.core import ApiException, HttpConfiguration
4from nisystemlink.clients.feeds import FeedsClient
5from nisystemlink.clients.feeds.models import Platform
6from nisystemlink.clients.feeds.utilities import get_feed_by_name
7
8# Update the constants.
9FEED_NAME = ""
10PLATFORM = Platform.WINDOWS
11WORKSPACE_ID = (
12 None # None uses Default workspace. Replace with Systemlink workspace id.
13)
14
15# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
16server_configuration: HttpConfiguration | None = None
17
18# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
19# the following lines and provide your server URI and API key.
20# server_configuration = HttpConfiguration(
21# server_uri="https://yourserver.yourcompany.com",
22# api_key="",
23# )
24
25client = FeedsClient(configuration=server_configuration)
26
27# Deleting Feed.
28try:
29 # Get ID of the Feed to delete by name
30 feeds = client.query_feeds(platform=PLATFORM, workspace=WORKSPACE_ID)
31 feed = get_feed_by_name(feeds=feeds, name=FEED_NAME)
32 feed_id = feed.id if feed else None
33
34 # Delete the Feed by ID
35 if feed_id:
36 client.delete_feed(id=feed_id)
37 print("Feed deleted successfully.")
38
39except ApiException as exp:
40 print(exp)
41except Exception as exp:
42 print(exp)
TestMonitor API (Results and Steps)
Overview
The TestMonitorClient class is the primary entry point of the Test Monitor API
used to interact with test results (Results) and test steps (Steps).
When constructing a TestMonitorClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let TestMonitorClient use the
default connection. The default connection depends on your environment.
With a TestMonitorClient object, you can:
Create, update, query, and delete results
Create, update, query, and delete steps
Examples
Create, query, update, and delete some results
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.testmonitor import TestMonitorClient
3from nisystemlink.clients.testmonitor.models import (
4 CreateResultRequest,
5 QueryResultsRequest,
6 QueryResultValuesRequest,
7 ResultField,
8 Status,
9 StatusType,
10 UpdateResultRequest,
11)
12
13program_name = "Example Name"
14host_name = "Example Host"
15status_type = StatusType.PASSED
16
17
18def create_some_results():
19 """Create two example results on your server."""
20 new_results = [
21 CreateResultRequest(
22 part_number="Example 123 AA",
23 program_name=program_name,
24 host_name=host_name,
25 status=Status.PASSED(),
26 keywords=["original keyword"],
27 properties={"original property key": "yes"},
28 ),
29 CreateResultRequest(
30 part_number="Example 123 AA1",
31 program_name=program_name,
32 host_name=host_name,
33 status=Status(status_type=StatusType.CUSTOM, status_name="Custom"),
34 keywords=["original keyword"],
35 properties={"original property key": "original"},
36 ),
37 ]
38 create_response = client.create_results(new_results)
39 return create_response
40
41
42# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
43server_configuration: HttpConfiguration | None = None
44
45# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
46# the following lines and provide your server URI and API key.
47# server_configuration = HttpConfiguration(
48# server_uri="https://yourserver.yourcompany.com",
49# api_key="",
50# )
51
52client = TestMonitorClient(configuration=server_configuration)
53
54create_response = create_some_results()
55
56# Get all the results using the continuation token in batches of 100 at a time.
57response = client.get_results(take=100, return_count=True)
58all_results = response.results
59while response.continuation_token:
60 response = client.get_results(
61 take=100, continuation_token=response.continuation_token, return_count=True
62 )
63 all_results.extend(response.results)
64
65# use get for first result created
66created_result = client.get_result(create_response.results[0].id)
67
68# Query results without continuation
69query_request = QueryResultsRequest(
70 filter=f'status.statusType="{status_type.value}"', return_count=True
71)
72response = client.query_results(query_request)
73
74# Update the first result that you just created and replace the keywords
75updated_result = create_response.results[0]
76updated_result = UpdateResultRequest(
77 id=create_response.results[0].id,
78 keywords=["new keyword"],
79 properties={"new property key": "new value"},
80)
81update_response = client.update_results([updated_result], replace=True)
82
83# Query for just the ids of results that match the family
84values_query = QueryResultValuesRequest(
85 filter=f'programName="{program_name}"', field=ResultField.ID
86)
87values_response = client.query_result_values(query=values_query)
88
89# delete each created result individually by id
90for result in create_response.results:
91 client.delete_result(result.id)
92
93# Create some more and delete them with a single call to delete.
94create_response = create_some_results()
95client.delete_results([result.id for result in create_response.results])
Create, update, query, and delete steps
1from typing import cast
2
3from nisystemlink.clients.core import HttpConfiguration
4from nisystemlink.clients.testmonitor import TestMonitorClient
5from nisystemlink.clients.testmonitor.models import (
6 CreateResultRequest,
7 CreateStepRequest,
8 Measurement,
9 NamedValue,
10 QueryStepsRequest,
11 QueryStepValuesRequest,
12 Status,
13 StepData,
14 StepField,
15 StepIdResultIdPair,
16 UpdateStepRequest,
17)
18
19
20def create_test_result():
21 """Create example result on your server."""
22 new_results = [
23 CreateResultRequest(
24 part_number="Example 123 AA",
25 program_name="Example Name",
26 host_name="Example Host",
27 status=Status.PASSED(),
28 keywords=["original keyword"],
29 properties={"original property key": "yes"},
30 )
31 ]
32 create_response = client.create_results(new_results)
33 return create_response
34
35
36# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
37server_configuration: HttpConfiguration | None = None
38
39# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
40# the following lines and provide your server URI and API key.
41# server_configuration = HttpConfiguration(
42# server_uri="https://yourserver.yourcompany.com",
43# api_key="",
44# )
45
46client = TestMonitorClient(configuration=server_configuration)
47
48# create a result to attach the steps to
49create_response = create_test_result()
50
51# Create the step requests
52result_id = cast(str, create_response.results[0].id)
53step_requests = [
54 CreateStepRequest(
55 step_id="step1",
56 name="step1",
57 result_id=result_id,
58 inputs=[
59 NamedValue(name="Temperature", value="35"),
60 NamedValue(name="Voltage", value="5"),
61 ],
62 ),
63 CreateStepRequest(
64 step_id="step2",
65 name="step2",
66 result_id=result_id,
67 ),
68]
69
70# Create the steps
71create_response = client.create_steps(steps=step_requests)
72created_steps = create_response.steps
73print(create_response)
74
75# You can query steps based on any field using DynamicLinq syntax.
76# These are just some representative examples.
77# Query based on result id
78query_response = client.query_steps(
79 QueryStepsRequest(filter=f'resultId == "{result_id}"')
80)
81queried_steps = query_response.steps
82
83# query step name using query step values
84query_values_response = client.query_step_values(
85 QueryStepValuesRequest(
86 filter=f'resultId == "{result_id}"',
87 field=StepField.NAME,
88 )
89)
90
91# update the data of the step
92# extra properties of the measurements will be converted to string if not already a string
93update_response = client.update_steps(
94 steps=[
95 UpdateStepRequest(
96 step_id=step.step_id,
97 result_id=cast(str, step.result_id),
98 data=StepData(
99 text="My output string",
100 parameters=[
101 Measurement(
102 name="Temperature",
103 status="Passed",
104 measurement="35",
105 lowLimit="30",
106 highLimit="40",
107 units="C",
108 comparisonType="Numeric",
109 spec_id="spec1",
110 )
111 ],
112 ),
113 )
114 for step in created_steps
115 ]
116)
117
118# delete all steps at once
119delete_response = client.delete_steps(
120 steps=[
121 StepIdResultIdPair(
122 step_id=cast(str, step.step_id), result_id=cast(str, step.result_id)
123 )
124 for step in queried_steps
125 ]
126)
127
128create_response = client.create_steps(steps=step_requests)
129created_steps = create_response.steps
130
131# delete steps one by one
132for step in created_steps:
133 if step.step_id and step.result_id:
134 client.delete_step(result_id=step.result_id, step_id=step.step_id)
Notebook API
Overview
The NotebookClient class is the primary entry point of the Notebook API.
When constructing a NotebookClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let NotebookClient use the
default connection. The default connection depends on your environment.
With a NotebookClient object, you can:
Create, update, query, and delete Notebooks
Create, get and query Notebook Executions
Examples
Create, query, update, and delete some notebooks.
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.notebook import NotebookClient
3from nisystemlink.clients.notebook.models import NotebookMetadata, QueryNotebookRequest
4
5# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
6server_configuration: HttpConfiguration | None = None
7
8# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
9# the following lines and provide your server URI and API key.
10# server_configuration = HttpConfiguration(
11# server_uri="https://yourserver.yourcompany.com",
12# api_key="",
13# )
14
15client = NotebookClient(configuration=server_configuration)
16
17# Create a notebook with metadata and content
18metadata = NotebookMetadata(
19 name="Example Notebook",
20 parameters={"param1": "value1"},
21 properties={"property1": "value1"},
22)
23
24with open("example.ipynb", "rb") as file:
25 notebook_response = client.create_notebook(metadata=metadata, content=file)
26
27# Get the notebook by ID
28notebook = client.get_notebook("your_notebook_id")
29
30# Update the notebook with new metadata and content
31metadata = NotebookMetadata(
32 name="Updated Example Notebook",
33 parameters={"param1": "value2"},
34 properties={"property1": "value2"},
35)
36
37with open("example_updated.ipynb", "rb") as file:
38 notebook_response = client.update_notebook(
39 id="your_notebook_id",
40 metadata=metadata,
41 content=file,
42 )
43
44# Get notebook content by ID
45notebook_content = client.get_notebook_content("your_notebook_id")
46
47# Query notebook by name
48query_request = QueryNotebookRequest(
49 filter='name="Example Notebook"',
50)
51
52query_response = client.query_notebooks(query_request)
53
54# Query notebooks by take
55query_request = QueryNotebookRequest(take=2)
56query_response = client.query_notebooks(query_request)
57
58query_request = QueryNotebookRequest(
59 continuation_token=query_response.continuation_token,
60 take=1,
61)
62query_response = client.query_notebooks(query_request)
63
64# Delete the notebook by ID
65client.delete_notebook("your_notebook_id")
Create, query, retry, and cancel notebook executions.
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.notebook import NotebookClient
3from nisystemlink.clients.notebook.models import (
4 CreateExecutionRequest,
5 ExecutionField,
6 ExecutionPriority,
7 ExecutionResourceProfile,
8 ExecutionSortField,
9 ExecutionStatus,
10 QueryExecutionsRequest,
11 ReportSettings,
12 ReportType,
13)
14
15# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
16server_configuration: HttpConfiguration | None = None
17
18# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
19# the following lines and provide your server URI and API key.
20# server_configuration = HttpConfiguration(
21# server_uri="https://yourserver.yourcompany.com",
22# api_key="",
23# )
24
25client = NotebookClient(configuration=server_configuration)
26
27
28# Create a notebook execution
29execution_request = CreateExecutionRequest(
30 notebook_id="your_notebook_id",
31 parameters={"param1": "value1"},
32 workspace_id="your_workspace_id",
33 timeout=300,
34 result_cache_period=3600,
35 report_settings=ReportSettings(
36 format=ReportType.HTML,
37 exclude_code=False,
38 ),
39 client_requests_id="your_client_request_id",
40 priority=ExecutionPriority.HIGH,
41 resource_profile=ExecutionResourceProfile.DEFAULT,
42)
43
44# Pass the list of execution requests to the create_executions method
45create_execution_response = client.create_executions([execution_request])
46
47# Get the execution by ID
48execution = client.get_execution_by_id("your_execution_id")
49
50# Query executions
51query_request = QueryExecutionsRequest(
52 filter=f"(status = {ExecutionStatus.FAILED.value}))",
53 order_by=ExecutionSortField.COMPLETED_AT,
54 descending=True,
55 projection=[
56 ExecutionField.ID,
57 ExecutionField.NOTEBOOK_ID,
58 ExecutionField.STATUS,
59 ],
60)
61
62query_executions_response = client.query_executions(query_request)
63
64# Cancel execution
65cancel_execution_response = client.cancel_executions(["your_execution_id"])
66
67# Retry execution
68retry_execution_response = client.retry_executions(["your_execution_id"])
69
70# Create execution from existing one
71run_new = client.create_executions_from_existing(["your_execution_id"])
Asset Management API
Overview
The AssetManagementClient class is the primary entry point of the Asset Management API.
When constructing a AssetManagementClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let AssetManagementClient use the
default connection. The default connection depends on your environment.
With a AssetManagementClient object, you can:
Create, delete, query assets and link files to assets.
Track asset utilization with start, heartbeat, end, and query history operations.
Examples
Create, delete, and query assets and link files to assets.
1from datetime import datetime, timezone
2
3from nisystemlink.clients.assetmanagement import AssetManagementClient
4from nisystemlink.clients.assetmanagement.models import (
5 AssetBusType,
6 AssetDiscoveryType,
7 AssetLocationForCreate,
8 AssetPresence,
9 AssetPresenceStatus,
10 AssetType,
11 CreateAssetRequest,
12 ExternalCalibration,
13 QueryAssetsRequest,
14 SelfCalibration,
15 TemperatureSensor,
16)
17from nisystemlink.clients.core import HttpConfiguration
18
19# workspace where the asset will be created
20workspace_id = "yourWorkspaceId"
21
22# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
23server_configuration: HttpConfiguration | None = None
24
25# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
26# the following lines and provide your server URI and API key.
27# server_configuration = HttpConfiguration(
28# server_uri="https://yourserver.yourcompany.com",
29# api_key="",
30# )
31
32client = AssetManagementClient(configuration=server_configuration)
33
34create_assets_request = [
35 CreateAssetRequest(
36 model_number=4000,
37 model_name="NI PXIe-6368",
38 serial_number="01BB877A",
39 vendor_name="NI",
40 vendor_number=4244,
41 bus_type=AssetBusType.ACCESSORY,
42 name="PCISlot2",
43 asset_type=AssetType.DEVICE_UNDER_TEST,
44 firmware_version="A1",
45 hardware_version="12A",
46 visa_resource_name="vs-3144",
47 temperature_sensors=[TemperatureSensor(name="Sensor0", reading=25.8)],
48 supports_self_calibration=True,
49 supports_external_calibration=True,
50 custom_calibration_interval=24,
51 self_calibration=SelfCalibration(
52 temperature_sensors=[TemperatureSensor(name="Sensor0", reading=25.8)],
53 is_limited=False,
54 date=datetime(2022, 6, 7, 18, 58, 5, tzinfo=timezone.utc),
55 ),
56 is_NI_asset=True,
57 workspace=workspace_id,
58 location=AssetLocationForCreate(
59 state=AssetPresence(asset_presence=AssetPresenceStatus.PRESENT)
60 ),
61 external_calibration=ExternalCalibration(
62 temperature_sensors=[TemperatureSensor(name="Sensor0", reading=25.8)],
63 date=datetime(2022, 6, 7, 18, 58, 5, tzinfo=timezone.utc),
64 recommended_interval=10,
65 next_recommended_date=datetime(
66 2023, 11, 14, 20, 42, 11, 583000, tzinfo=timezone.utc
67 ),
68 next_custom_due_date=datetime(
69 2024, 11, 14, 20, 42, 11, 583000, tzinfo=timezone.utc
70 ),
71 resolved_due_date=datetime(2022, 6, 7, 18, 58, 5, tzinfo=timezone.utc),
72 ),
73 properties={"Key1": "Value1"},
74 keywords=["Keyword1"],
75 discovery_type=AssetDiscoveryType.MANUAL,
76 file_ids=["608a5684800e325b48837c2a"],
77 supports_self_test=True,
78 supports_reset=True,
79 part_number="A1234 B5",
80 )
81]
82
83# Create an asset.
84create_assets_response = client.create_assets(assets=create_assets_request)
85
86created_asset_id = None
87if create_assets_response.assets and len(create_assets_response.assets) > 0:
88 created_asset_id = str(create_assets_response.assets[0].id)
89
90# Query assets using id.
91query_asset_request = QueryAssetsRequest(
92 filter=f'AssetIdentifier = "{created_asset_id}"',
93 skip=0,
94 take=1,
95 descending=False,
96 return_count=False,
97)
98client.query_assets(query=query_asset_request)
99
100# Link files to the created asset.
101file_ids = ["sample-file-id"]
102if created_asset_id:
103 link_files_response = client.link_files(
104 asset_id=created_asset_id, file_ids=file_ids
105 )
106
107# Delete the created asset.
108if created_asset_id is not None:
109 client.delete_assets(ids=[created_asset_id])
Track asset utilization.
1import threading
2import time
3from datetime import datetime
4from uuid import uuid4
5
6from nisystemlink.clients.assetmanagement import AssetManagementClient
7from nisystemlink.clients.assetmanagement.models import (
8 AssetBusType,
9 AssetIdentification,
10 AssetLocationForCreate,
11 AssetPresence,
12 AssetPresenceStatus,
13 CreateAssetRequest,
14 StartUtilizationRequest,
15)
16from nisystemlink.clients.core import HttpConfiguration
17from nisystemlink.clients.core.helpers import read_minion_id
18
19# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
20server_configuration: HttpConfiguration | None = None
21
22# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
23# the following lines and provide your server URI and API key.
24# server_configuration = HttpConfiguration(
25# server_uri="https://yourserver.yourcompany.com",
26# api_key="",
27# )
28
29client = AssetManagementClient(configuration=server_configuration)
30
31# Generate a unique identifier for this utilization session
32utilization_id = str(uuid4())
33
34# Create the assets first
35# Define the assets to be created and used in the test
36create_assets_request = [
37 CreateAssetRequest(
38 model_name="NI PXIe-6368",
39 model_number=4000,
40 serial_number="01BB877A",
41 vendor_name="NI",
42 vendor_number=4244,
43 bus_type=AssetBusType.ACCESSORY,
44 name="DAQ Device - 01BB877A",
45 location=AssetLocationForCreate(
46 state=AssetPresence(asset_presence=AssetPresenceStatus.PRESENT)
47 ),
48 ),
49 CreateAssetRequest(
50 model_name="NI PXIe-5163",
51 model_number=5000,
52 serial_number="02CC988B",
53 vendor_name="NI",
54 vendor_number=4244,
55 bus_type=AssetBusType.ACCESSORY,
56 name="Oscilloscope - 02CC988B",
57 location=AssetLocationForCreate(
58 state=AssetPresence(asset_presence=AssetPresenceStatus.PRESENT)
59 ),
60 ),
61]
62
63# Create the assets in SystemLink
64create_assets_response = client.create_assets(assets=create_assets_request)
65
66if create_assets_response.assets:
67 print(f"Created {len(create_assets_response.assets)} asset(s)")
68 created_asset_ids = [
69 asset.id for asset in create_assets_response.assets if asset.id
70 ]
71else:
72 print("Failed to create assets")
73 exit(1)
74
75# Define the asset identifications for utilization tracking
76test_assets = [
77 AssetIdentification(
78 model_name="NI PXIe-6368",
79 model_number=4000,
80 serial_number="01BB877A",
81 vendor_name="NI",
82 vendor_number=4244,
83 bus_type=AssetBusType.ACCESSORY,
84 ),
85 AssetIdentification(
86 model_name="NI PXIe-5163",
87 model_number=5000,
88 serial_number="02CC988B",
89 vendor_name="NI",
90 vendor_number=4244,
91 bus_type=AssetBusType.ACCESSORY,
92 ),
93]
94
95# Start asset utilization tracking
96# This marks the assets as "in use" in the SystemLink UI
97# Read the minion ID from the Salt configuration
98minion_id = read_minion_id() or "test-station-01" # Fallback minion ID if not found
99
100start_utilization_request = StartUtilizationRequest(
101 utilization_identifier=utilization_id,
102 minion_id=minion_id,
103 asset_identifications=test_assets,
104 utilization_category="Automated Testing",
105 task_name="DUT Validation Suite",
106 user_name="automation_user",
107 utilization_timestamp=datetime.now(),
108)
109
110start_utilization_response = client.start_utilization(request=start_utilization_request)
111
112print(start_utilization_response)
113
114# Verify utilization started successfully
115if start_utilization_response.assets_with_started_utilization:
116 print(
117 f"Utilization started for {len(start_utilization_response.assets_with_started_utilization)} asset(s)"
118 )
119else:
120 print("Failed to start utilization")
121
122
123# Heartbeat mechanism using a background thread
124# IMPORTANT: Heartbeats are for UI purposes only, to keep assets visually
125# marked as "in use" in the SystemLink UI. This applies only to utilizations
126# that have not been ended. While the standard heartbeat interval is 5 minutes,
127# the UI requires heartbeats at least every 10 minutes to continue showing
128# assets as actively utilized. If heartbeats stop, the assets will no longer
129# appear as "in use" in the UI.
130heartbeat_interval = 300 # 5 minutes in seconds
131stop_event = threading.Event()
132
133
134def heartbeat_loop():
135 """Background thread that sends periodic heartbeats.
136
137 This keeps the asset visually marked as "in use" in the SystemLink UI
138 (for UI purposes only). Applies only to utilizations that have not been ended.
139 The UI requires heartbeats at least every 10 minutes to continue displaying
140 the asset as actively utilized.
141 """
142 while not stop_event.wait(heartbeat_interval):
143 heartbeat_response = client.utilization_heartbeat(
144 ids=[utilization_id],
145 timestamp=datetime.now(),
146 )
147
148 if heartbeat_response.updated_utilization_ids:
149 print(
150 f"Heartbeat sent at {datetime.now().strftime('%H:%M:%S')} - asset remains 'in use'"
151 )
152
153
154# Start the heartbeat thread
155heartbeat_thread = threading.Thread(target=heartbeat_loop, daemon=True)
156heartbeat_thread.start()
157
158# Simulate a long-running operation where assets are in use
159# In a real scenario, this would be your actual test or operation
160print("\nAssets are now in use. Heartbeats will be sent every 5 minutes...")
161print("Waiting for 10 minutes to demonstrate heartbeats...\n")
162
163time.sleep(600) # Wait 10 minutes
164
165# Stop the heartbeat thread
166stop_event.set()
167heartbeat_thread.join()
168
169# End asset utilization tracking
170end_utilization_response = client.end_utilization(
171 ids=[utilization_id],
172 timestamp=datetime.now(),
173)
174
175if end_utilization_response.updated_utilization_ids:
176 print("\nUtilization ended - asset(s) released")
177
178# Clean up: delete the created assets
179if created_asset_ids:
180 client.delete_assets(ids=created_asset_ids)
181 print(f"Deleted {len(created_asset_ids)} asset(s)")
Systems API
Overview
The SystemsClient class is the primary entry point of the Systems API.
When constructing a SystemsClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let SystemsClient use the
default connection. The default connection depends on your environment.
With a SystemsClient object, you can:
Create, query, and remove systems.
Examples
Create, query, and remove some systems.
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.systems import SystemsClient
3from nisystemlink.clients.systems.models import (
4 CreateVirtualSystemRequest,
5 QuerySystemsRequest,
6)
7
8# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
9server_configuration: HttpConfiguration | None = None
10
11# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
12# the following lines and provide your server URI and API key.
13# server_configuration = HttpConfiguration(
14# server_uri="https://yourserver.yourcompany.com",
15# api_key="",
16# )
17
18client = SystemsClient(configuration=server_configuration)
19
20# Systems request metadata.
21create_virtual_system_request: CreateVirtualSystemRequest = CreateVirtualSystemRequest(
22 alias="Python integration virtual system",
23 workspace="your-workspace-id",
24)
25
26# Create a virtual system.
27create_virtual_system_response = client.create_virtual_system(
28 create_virtual_system_request=create_virtual_system_request
29)
30
31minion_id = None
32
33if create_virtual_system_response and create_virtual_system_response.minionId:
34 minion_id = create_virtual_system_response.minionId
35
36# Query systems using id.
37query_systems_request = QuerySystemsRequest(
38 filter=f'id="{minion_id}"', projection="new(id, alias)"
39)
40
41client.query_systems(query=query_systems_request)
42
43# Delete the created systems.
44if minion_id is not None:
45 remove_systems = [minion_id]
46 client.remove_systems(tgt=remove_systems)
WorkItem API
Overview
The WorkItemClient class is the primary entry point of the WorkItem API.
When constructing a WorkItemClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let WorkItemClient use the
default connection. The default connection depends on your environment.
With a WorkItemClient object, you can:
Create, query, get, update, schedule and delete work items
Create, query, update and delete work item templates
Examples
Create, query, get, update, schedule and delete work items
1from datetime import datetime
2
3from nisystemlink.clients.core import HttpConfiguration
4from nisystemlink.clients.work_item import WorkItemClient
5from nisystemlink.clients.work_item.models import (
6 CreateWorkItemRequest,
7 Dashboard,
8 Job,
9 JobExecution,
10 ManualExecution,
11 QueryWorkItemsRequest,
12 ResourceDefinition,
13 ResourcesDefinition,
14 ResourceSelectionDefinition,
15 ScheduleDefinition,
16 ScheduleResourcesDefinition,
17 ScheduleSystemResourceDefinition,
18 ScheduleWorkItemRequest,
19 ScheduleWorkItemsRequest,
20 SystemResourceDefinition,
21 SystemResourceSelectionDefinition,
22 TimelineDefinition,
23 UpdateWorkItemRequest,
24 UpdateWorkItemsRequest,
25)
26
27# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
28server_configuration: HttpConfiguration | None = None
29
30# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
31# the following lines and provide your server URI and API key.
32# server_configuration = HttpConfiguration(
33# server_uri="https://yourserver.yourcompany.com",
34# api_key="",
35# )
36
37client = WorkItemClient(configuration=server_configuration)
38
39create_work_items_request = [
40 CreateWorkItemRequest(
41 name="Python integration work item",
42 type="testplan",
43 state="NEW",
44 description="Work item for verifying integration flow",
45 assigned_to="test.user@example.com",
46 requested_by="test.manager@example.com",
47 properties={"env": "staging", "priority": "high"},
48 part_number="px40482",
49 test_program="TP-Integration-001",
50 workspace="your_workspace_id",
51 timeline=TimelineDefinition(
52 earliest_start_date_time=datetime.strptime(
53 "2024-01-15T08:00:00Z", "%Y-%m-%dT%H:%M:%SZ"
54 ),
55 due_date_time=datetime.strptime(
56 "2024-01-20T17:00:00Z", "%Y-%m-%dT%H:%M:%SZ"
57 ),
58 estimated_duration_in_seconds=86400,
59 ),
60 resources=ResourcesDefinition(
61 systems=SystemResourceDefinition(
62 selections=[
63 SystemResourceSelectionDefinition(
64 id="system-001",
65 target_location_id="location-001",
66 ),
67 ],
68 filter='properties.data["Lab"] = "Battery Pack Lab"',
69 ),
70 duts=ResourceDefinition(
71 selections=[
72 ResourceSelectionDefinition(
73 id="dut-001",
74 target_location_id="location-001",
75 target_system_id="system-001",
76 target_parent_id="parent-001",
77 ),
78 ],
79 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"',
80 ),
81 assets=ResourceDefinition(
82 selections=[
83 ResourceSelectionDefinition(
84 id="asset-001",
85 target_location_id="location-001",
86 target_system_id="system-001",
87 target_parent_id="parent-001",
88 ),
89 ],
90 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"',
91 ),
92 fixtures=ResourceDefinition(
93 selections=[
94 ResourceSelectionDefinition(
95 id="fixture-001",
96 target_location_id="location-001",
97 target_system_id="system-001",
98 target_parent_id="parent-001",
99 ),
100 ],
101 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"',
102 ),
103 ),
104 file_ids_from_template=["file1", "file2"],
105 dashboard=Dashboard(
106 id="DashboardId", variables={"product": "PXIe-4080", "location": "Lab1"}
107 ),
108 execution_actions=[
109 ManualExecution(action="boot", type="MANUAL"),
110 JobExecution(
111 action="run",
112 type="JOB",
113 jobs=[
114 Job(
115 functions=["run_test_suite"],
116 arguments=[["test_suite.py"]],
117 metadata={"env": "staging"},
118 )
119 ],
120 systemId="system-001",
121 ),
122 ],
123 )
124]
125
126# create work item
127create_work_items_response = client.create_work_items(
128 work_items=create_work_items_request
129)
130
131if create_work_items_response.created_work_items:
132 created_work_item_id = create_work_items_response.created_work_items[0].id
133 print(f"Created work item: {created_work_item_id}")
134
135# Query work items using id.
136query_work_items_request = QueryWorkItemsRequest(
137 filter=f'id = "{created_work_item_id}"',
138 take=1,
139 descending=False,
140 return_count=False,
141)
142query_work_items_response = client.query_work_items(
143 query_work_items=query_work_items_request
144)
145if query_work_items_response.work_items:
146 print(f"Found work item: {query_work_items_response.work_items[0].name}")
147
148# Get work item
149if created_work_item_id is not None:
150 get_work_item_response = client.get_work_item(work_item_id=created_work_item_id)
151 print(f"Retrieved work item: {get_work_item_response.name}")
152
153# Update work item
154if created_work_item_id is not None:
155 update_work_items_request = UpdateWorkItemsRequest(
156 work_items=[
157 UpdateWorkItemRequest(id=created_work_item_id, name="Updated work item")
158 ]
159 )
160 update_work_items_response = client.update_work_items(
161 update_work_items=update_work_items_request
162 )
163 if update_work_items_response.updated_work_items:
164 print(
165 f"Work item name updated to: {update_work_items_response.updated_work_items[0].name}"
166 )
167
168# Schedule work item
169if created_work_item_id is not None:
170 schedule_work_items_request = ScheduleWorkItemsRequest(
171 work_items=[
172 ScheduleWorkItemRequest(
173 id=created_work_item_id,
174 schedule=ScheduleDefinition(
175 planned_start_date_time=datetime.strptime(
176 "2025-05-20T15:07:42.527Z", "%Y-%m-%dT%H:%M:%S.%fZ"
177 ),
178 planned_end_date_time=datetime.strptime(
179 "2025-05-22T15:07:42.527Z", "%Y-%m-%dT%H:%M:%S.%fZ"
180 ),
181 planned_duration_in_seconds=172800,
182 ),
183 resources=ScheduleResourcesDefinition(
184 systems=ScheduleSystemResourceDefinition(
185 selections=[
186 SystemResourceSelectionDefinition(
187 id="system-123",
188 target_location_id="location-456",
189 )
190 ]
191 )
192 ),
193 )
194 ],
195 replace=True,
196 )
197 schedule_work_items_response = client.schedule_work_items(
198 schedule_work_items=schedule_work_items_request
199 )
200 if schedule_work_items_response.scheduled_work_items:
201 print(
202 f"Scheduled work item with ID: {schedule_work_items_response.scheduled_work_items[0].id}"
203 )
204
205# Delete work item
206if created_work_item_id is not None:
207 client.delete_work_items(ids=[created_work_item_id])
208 print("Work item deleted successfully.")
Create, query, update and delete work item templates.
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.work_item import WorkItemClient
3from nisystemlink.clients.work_item.models import (
4 CreateWorkItemTemplateRequest,
5 Dashboard,
6 Job,
7 JobExecution,
8 ManualExecution,
9 QueryWorkItemTemplatesRequest,
10 TemplateResourceDefinition,
11 TemplateResourcesDefinition,
12 TemplateTimelineDefinition,
13 UpdateWorkItemTemplateRequest,
14 UpdateWorkItemTemplatesRequest,
15)
16
17# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
18server_configuration: HttpConfiguration | None = None
19
20# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
21# the following lines and provide your server URI and API key.
22# server_configuration = HttpConfiguration(
23# server_uri="https://yourserver.yourcompany.com",
24# api_key="",
25# )
26
27client = WorkItemClient(configuration=server_configuration)
28
29create_work_item_template_request = [
30 CreateWorkItemTemplateRequest(
31 name="Python integration work item template",
32 template_group="sample template group",
33 type="testplan",
34 product_families=["FamilyA", "FamilyB"],
35 part_numbers=["PN-1001", "PN-1002"],
36 summary="Template for running integration work items",
37 description="This template defines execution steps for integration workflows.",
38 test_program="TP-INT-002",
39 timeline=TemplateTimelineDefinition(estimated_duration_in_seconds=86400),
40 resources=TemplateResourcesDefinition(
41 systems=TemplateResourceDefinition(
42 filter='properties.data["Lab"] = "Battery Pack Lab" && state = "Available"'
43 ),
44 duts=TemplateResourceDefinition(
45 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"'
46 ),
47 assets=TemplateResourceDefinition(
48 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"'
49 ),
50 fixtures=TemplateResourceDefinition(
51 filter='modelName = "cRIO-9045" && serialNumber = "01E82ED0"'
52 ),
53 ),
54 execution_actions=[
55 ManualExecution(action="boot", type="MANUAL"),
56 JobExecution(
57 action="run",
58 type="JOB",
59 jobs=[
60 Job(
61 functions=["run_test_suite"],
62 arguments=[["test_suite.py"]],
63 metadata={"env": "staging"},
64 )
65 ],
66 systemId="system-001",
67 ),
68 ],
69 file_ids=["file1", "file2"],
70 workspace="your_workspace_id",
71 properties={"env": "staging", "priority": "high"},
72 dashboard=Dashboard(
73 id="DashboardId", variables={"product": "PXIe-4080", "location": "Lab1"}
74 ),
75 )
76]
77
78# Create work item template
79create_work_item_templates_response = client.create_work_item_templates(
80 work_item_templates=create_work_item_template_request
81)
82
83if create_work_item_templates_response.created_work_item_templates:
84 create_work_item_template_id = (
85 create_work_item_templates_response.created_work_item_templates[0].id
86 )
87 print(f"Created work item template: {create_work_item_template_id}")
88
89# Query work item templates using id
90query_work_item_template_request = QueryWorkItemTemplatesRequest(
91 filter=f'id="{create_work_item_template_id}"', take=1
92)
93query_work_item_templates_response = client.query_work_item_templates(
94 query_work_item_templates=query_work_item_template_request
95)
96if query_work_item_templates_response.work_item_templates:
97 print(
98 f"Found work item template: {query_work_item_templates_response.work_item_templates[0].name}"
99 )
100
101# Update work item template
102if create_work_item_template_id is not None:
103 update_work_item_template_request = UpdateWorkItemTemplatesRequest(
104 work_item_templates=[
105 UpdateWorkItemTemplateRequest(
106 id=create_work_item_template_id, name="Updated work item template"
107 )
108 ]
109 )
110 update_work_item_templates_response = client.update_work_item_templates(
111 update_work_item_templates=update_work_item_template_request
112 )
113 if update_work_item_templates_response.updated_work_item_templates:
114 print(
115 "Work item template name updated to: "
116 f"{update_work_item_templates_response.updated_work_item_templates[0].name}"
117 )
118
119# Delete work item template
120if create_work_item_template_id is not None:
121 client.delete_work_item_templates(ids=[create_work_item_template_id])
122 print("Work item template deleted successfully.")
TestPlan API
Overview
The TestPlanClient class is the primary entry point of the TestPlan API.
When constructing a TestPlanClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let TestPlanClient use the
default connection. The default connection depends on your environment.
With a TestPlanClient object, you can:
Create, query, get, update, schedule and delete TestPlans
Create, query and delete test plan templates
Examples
Create, query, get, update, schedule and delete TestPlans
1from datetime import datetime
2
3from nisystemlink.clients.core import HttpConfiguration
4from nisystemlink.clients.test_plan import TestPlanClient
5from nisystemlink.clients.test_plan.models import (
6 CreateTestPlanRequest,
7 Dashboard,
8 Job,
9 JobExecution,
10 ManualExecution,
11 QueryTestPlansRequest,
12 ScheduleTestPlanRequest,
13 ScheduleTestPlansRequest,
14 UpdateTestPlanRequest,
15 UpdateTestPlansRequest,
16)
17
18# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
19server_configuration: HttpConfiguration | None = None
20
21# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
22# the following lines and provide your server URI and API key.
23# server_configuration = HttpConfiguration(
24# server_uri="https://yourserver.yourcompany.com",
25# api_key="",
26# )
27
28client = TestPlanClient(configuration=server_configuration)
29
30create_test_plans_request = [
31 CreateTestPlanRequest(
32 name="Python integration test plan",
33 state="NEW",
34 description="Test plan for verifying integration flow",
35 assigned_to="test.user@example.com",
36 estimated_duration_in_seconds=86400,
37 properties={"env": "staging", "priority": "high"},
38 part_number="px40482",
39 dut_id="Sample-Dut_Id",
40 dut_serial_number="serial_number_123",
41 test_program="TP-Integration-001",
42 system_filter="os:linux AND arch:x64",
43 dut_filter="modelName = 'cRIO-9045' AND serialNumber = '01E82ED0'",
44 workspace="your_workspace_id",
45 file_ids_from_template=["file1", "file2"],
46 dashboard=Dashboard(
47 id="DashBoardId", variables={"product": "PXIe-4080", "location": "Lab1"}
48 ),
49 execution_actions=[
50 ManualExecution(action="boot", type="MANUAL"),
51 JobExecution(
52 action="run",
53 type="JOB",
54 jobs=[
55 Job(
56 functions=["run_test_suite"],
57 arguments=[["test_suite.py"]],
58 metadata={"env": "staging"},
59 )
60 ],
61 systemId="system-001",
62 ),
63 ],
64 )
65]
66
67# create a test plan
68created_test_plans_response = client.create_test_plans(
69 test_plans=create_test_plans_request
70)
71
72if created_test_plans_response.created_test_plans:
73 created_test_plan_id = created_test_plans_response.created_test_plans[0].id
74
75# Query test plan using id.
76query_test_plans_request = QueryTestPlansRequest(
77 take=1, descending=False, return_count=False
78)
79client.query_test_plans(query_request=query_test_plans_request)
80
81# Get test plan
82get_test_plan = client.get_test_plan(test_plan_id=created_test_plan_id)
83
84# Update test plan
85update_test_plans_request = UpdateTestPlansRequest(
86 test_plans=[
87 UpdateTestPlanRequest(
88 id=created_test_plan_id,
89 name="Updated Test Plan",
90 )
91 ]
92)
93updated_test_plan = client.update_test_plans(update_request=update_test_plans_request)
94
95# Schedule the test plan
96schedule_test_plans_request = ScheduleTestPlansRequest(
97 test_plans=[
98 ScheduleTestPlanRequest(
99 id=created_test_plan_id,
100 planned_start_date_time=datetime.strptime(
101 "2025-05-20T15:07:42.527Z", "%Y-%m-%dT%H:%M:%S.%fZ"
102 ),
103 estimated_end_date_time=datetime.strptime(
104 "2025-05-22T15:07:42.527Z", "%Y-%m-%dT%H:%M:%S.%fZ"
105 ),
106 system_id="fake-system",
107 )
108 ]
109)
110schedule_test_plan_response = client.schedule_test_plans(
111 schedule_request=schedule_test_plans_request
112)
113
114# Delete test plan
115client.delete_test_plans(ids=[created_test_plan_id])
Create, query and delete test plan templates.
1from nisystemlink.clients.core import HttpConfiguration
2from nisystemlink.clients.test_plan import TestPlanClient
3from nisystemlink.clients.test_plan.models import (
4 CreateTestPlanTemplateRequest,
5 Dashboard,
6 Job,
7 JobExecution,
8 ManualExecution,
9 QueryTestPlanTemplatesRequest,
10)
11
12# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
13server_configuration: HttpConfiguration | None = None
14
15# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
16# the following lines and provide your server URI and API key.
17# server_configuration = HttpConfiguration(
18# server_uri="https://yourserver.yourcompany.com",
19# api_key="",
20# )
21
22client = TestPlanClient(configuration=server_configuration)
23
24# Test plan template request metadata
25create_test_plan_template_request = [
26 CreateTestPlanTemplateRequest(
27 name="Python integration test plan template",
28 template_group="sample template group",
29 product_families=["FamilyA", "FamilyB"],
30 part_numbers=["PN-1001", "PN-1002"],
31 summary="Template for running integration test plans",
32 description="This template defines execution steps for integration workflows.",
33 test_program="TP-INT-002",
34 estimated_duration_in_seconds=86400,
35 system_filter="os:linux AND arch:x64",
36 dut_filter="modelName = 'cRIO-9045' AND serialNumber = '01E82ED0'",
37 execution_actions=[
38 ManualExecution(action="boot", type="MANUAL"),
39 JobExecution(
40 action="run",
41 type="JOB",
42 jobs=[
43 Job(
44 functions=["run_test_suite"],
45 arguments=[["test_suite.py"]],
46 metadata={"env": "staging"},
47 )
48 ],
49 systemId="system-001",
50 ),
51 ],
52 file_ids=["file1", "file2"],
53 workspace="your_workspace_id",
54 properties={"env": "staging", "priority": "high"},
55 dashboard=Dashboard(
56 id="DashBoardId", variables={"product": "PXIe-4080", "location": "Lab1"}
57 ),
58 )
59]
60
61# Create a test plan template
62create_test_plan_template_response = client.create_test_plan_templates(
63 test_plan_templates=create_test_plan_template_request
64)
65
66create_test_plan_template_id = None
67
68if (
69 create_test_plan_template_response.created_test_plan_templates
70 and create_test_plan_template_response.created_test_plan_templates[0].id
71):
72 create_test_plan_template_id = str(
73 create_test_plan_template_response.created_test_plan_templates[0].id
74 )
75
76# Query test plan templates using id
77query_test_plan_template_request = QueryTestPlanTemplatesRequest(
78 filter=f'id="{create_test_plan_template_id}"', take=1
79)
80
81client.query_test_plan_templates(
82 query_test_plan_templates=query_test_plan_template_request
83)
84
85# Delete the created test plan template.
86if create_test_plan_template_id is not None:
87 client.delete_test_plan_templates(ids=[create_test_plan_template_id])
Notification API
Overview
The NotificationClient class is the primary entry point of the Notification API.
When constructing a NotificationClient, you can pass an
HttpConfiguration (like one retrieved from the
HttpConfigurationManager), or let NotificationClient use the
default connection. The default connection depends on your environment.
With a NotificationClient object, you can:
Apply dynamic notification strategy using
apply_dynamic_notification_strategy()
Examples
Apply a notification strategy
1import uuid
2from datetime import datetime
3
4from nisystemlink.clients.alarm import AlarmClient
5from nisystemlink.clients.alarm.models._alarm import Alarm, AlarmSeverityLevel
6from nisystemlink.clients.alarm.models._create_or_update_alarm_request import (
7 CreateOrUpdateAlarmRequest,
8 SetAlarmTransition,
9)
10from nisystemlink.clients.core import HttpConfiguration
11from nisystemlink.clients.notification import NotificationClient
12from nisystemlink.clients.notification.models import (
13 DynamicNotificationConfiguration,
14 DynamicNotificationStrategy,
15 DynamicStrategyRequest,
16 SmtpAddressFields,
17 SmtpAddressGroup,
18 SmtpMessageTemplate,
19 SmtpMessageTemplateFields,
20)
21
22# Server configuration is not required when used with SystemLink Client or run through Jupyter on SystemLink
23server_configuration: HttpConfiguration | None = None
24
25# To set up the server configuration to point to your instance of SystemLink Enterprise, uncomment
26# the following lines and provide your server URI and API key.
27# server_configuration = HttpConfiguration(
28# server_uri="https://yourserver.yourcompany.com",
29# api_key="",
30# )
31
32
33# Create request for applying strategy
34def create_notification_request_for_alarm(
35 alarm: Alarm,
36 address_group: SmtpAddressGroup,
37 message_template: SmtpMessageTemplate,
38) -> DynamicStrategyRequest:
39 """Creates and returns a dynamic strategy request."""
40 occurred_at = alarm.most_recent_transition_occurred_at
41
42 return DynamicStrategyRequest(
43 message_template_substitution_fields={
44 "alarm_id": alarm.alarm_id,
45 "alarm_condition": alarm.condition,
46 "alarm_description": alarm.description,
47 "alarm_severity": str(alarm.current_severity_level),
48 "alarm_occurred_at": occurred_at.isoformat() if occurred_at else "",
49 },
50 notification_strategy=DynamicNotificationStrategy(
51 notification_configurations=[
52 DynamicNotificationConfiguration(
53 address_group=address_group,
54 message_template=message_template,
55 )
56 ]
57 ),
58 )
59
60
61# Create clients for Notification and Alarm services
62notification_client = NotificationClient(configuration=server_configuration)
63alarm_client = AlarmClient(configuration=server_configuration)
64
65# Create a unique alarm ID for this example
66alarm_id = f"example_alarm_{uuid.uuid1().hex}"
67
68# Create an alarm with a SET transition
69create_alarm_request = CreateOrUpdateAlarmRequest(
70 alarm_id=alarm_id,
71 transition=SetAlarmTransition(
72 occurred_at=datetime.now(),
73 severity_level=AlarmSeverityLevel.HIGH,
74 value="85",
75 condition="Greater than 80",
76 short_text="Temperature is high",
77 detail_text="Temperature sensor reading is 85°C (higher than the configured threshold of 80°C)",
78 ),
79 description="Example alarm for notification",
80)
81id = alarm_client.create_or_update_alarm(create_alarm_request)
82print("Alarm created successfully")
83
84# Get the alarm by its instance ID (the unique occurrence identifier)
85retrieved_alarm = alarm_client.get_alarm(instance_id=id)
86
87# Define recipients to notify
88recipients = SmtpAddressFields(to_addresses=["sample1@example.com"])
89
90# Create address group
91address_group = SmtpAddressGroup(
92 display_name="Alarm Notification Recipients",
93 properties={"address group": "Alarm"},
94 fields=recipients,
95)
96
97# Create mail template for alarm creation notification
98alarm_creation_template = SmtpMessageTemplate(
99 display_name="Alarm Creation Template",
100 fields=SmtpMessageTemplateFields(
101 subject_template="Alarm Created: <alarm_id>",
102 body_template="An alarm with ID <alarm_id> has been created.\n"
103 "Condition: <alarm_condition>\n"
104 "Description: <alarm_description>\n"
105 "Current severity: <alarm_severity>\n"
106 "Occurred At: <alarm_occurred_at>",
107 ),
108)
109
110# Send notification for alarm creation
111notification_for_alarm_creation = create_notification_request_for_alarm(
112 alarm=retrieved_alarm,
113 address_group=address_group,
114 message_template=alarm_creation_template,
115)
116notification_client.apply_dynamic_notification_strategy(
117 request=notification_for_alarm_creation
118)
119print("Notification sent for alarm creation")