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 type

      • instance_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() or delete_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’s TagData from the server when you know the tag’s path.

    • Use query() to get a collection of TagData objects based on the tags’ paths, keywords, and/or properties.

    • Use refresh() to update a list of TagData objects with fresh metadata from the server.

    • Use update() to modify the server metadata for a list of tags, using either TagData objects to overwrite the server’s tag data or TagDataUpdate objects 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’s collect_aggregates attribute is enabled on the server).

    • Use create_writer() to get a BufferedTagWriter that will buffer a set of writes, and automatically send the writes when a given buffer_size is reached or when max_buffer_time has elapsed (or when send_buffered_writes() is called).

  • Get a TagSelection that can help perform several of the above operations on several tags at once

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:

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")