Skip to content

Commit 7c88535

Browse files
authored
feat: add spec client, examples, and docs (#53)
Add APIs to get, update, delete, and query specifications
1 parent cd34232 commit 7c88535

19 files changed

+1064
-10
lines changed

docs/api_reference.rst

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ API Reference
1010
api_reference/core
1111
api_reference/tag
1212
api_reference/dataframe
13+
api_reference/spec
1314

1415
Indices and tables
1516
------------------

docs/api_reference/spec.rst

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.. _api_tag_page:
2+
3+
nisystemlink.clients.spec
4+
======================
5+
6+
.. autoclass:: nisystemlink.clients.spec.SpecClient
7+
:exclude-members: __init__
8+
9+
.. automethod:: __init__
10+
.. automethod:: api_info
11+
.. automethod:: create_specs
12+
.. automethod:: delete_specs
13+
.. automethod:: query_specs
14+
.. automethod:: update_specs
15+
16+
.. automodule:: nisystemlink.clients.spec.models
17+
:members:
18+
:imported-members:

docs/getting_started.rst

+37
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,40 @@ Export data from a table
120120
.. literalinclude:: ../examples/dataframe/export_data.py
121121
:language: python
122122
:linenos:
123+
124+
Spec API
125+
-------
126+
127+
Overview
128+
~~~~~~~~
129+
130+
131+
The :class:`.SpecClient` class is the primary entry point of the Specification Compliance API.
132+
133+
When constructing a :class:`.SpecClient`, you can pass an
134+
:class:`.HttpConfiguration` (like one retrieved from the
135+
:class:`.HttpConfigurationManager`), or let :class:`.SpecClient` use the
136+
default connection. The default connection depends on your environment.
137+
138+
With a :class:`.SpecClient` object, you can:
139+
140+
* Create and delete specifications under a product.
141+
142+
* Modify any fields of an existing specification
143+
144+
* Query for specifications on any fields using DynamicLinq syntax.
145+
146+
Examples
147+
~~~~~~~~
148+
149+
Create and Query Specifications
150+
151+
.. literalinclude:: ../examples/spec/query_specs.py
152+
:language: python
153+
:linenos:
154+
155+
Update and Delete Specifications
156+
157+
.. literalinclude:: ../examples/spec/update_and_delete_specs.py
158+
:language: python
159+
:linenos:

examples/spec/query_specs.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from nisystemlink.clients.core import HttpConfiguration
2+
from nisystemlink.clients.spec import SpecClient
3+
from nisystemlink.clients.spec.models import (
4+
Condition,
5+
ConditionRange,
6+
ConditionType,
7+
CreateSpecificationsRequest,
8+
NumericConditionValue,
9+
QuerySpecificationsRequest,
10+
SpecificationDefinition,
11+
SpecificationLimit,
12+
SpecificationType,
13+
)
14+
15+
# Setup the server configuration to point to your instance of SystemLink Enterprise
16+
server_configuration = HttpConfiguration(
17+
server_uri="https://yourserver.yourcompany.com",
18+
api_key="YourAPIKeyGeneratedFromSystemLink",
19+
)
20+
client = SpecClient(configuration=server_configuration)
21+
22+
# Create the spec requests
23+
product = "Amplifier"
24+
spec_requests = [
25+
SpecificationDefinition(
26+
product_id=product,
27+
spec_id="spec1",
28+
type=SpecificationType.PARAMETRIC,
29+
category="Parametric Specs",
30+
name="output voltage",
31+
limit=SpecificationLimit(min=1.2, max=1.5),
32+
unit="mV",
33+
),
34+
SpecificationDefinition(
35+
product_id=product,
36+
spec_id="spec2",
37+
type=SpecificationType.PARAMETRIC,
38+
category="Parametric Specs",
39+
name="input voltage",
40+
limit=SpecificationLimit(min=0.02, max=0.15),
41+
unit="mV",
42+
conditions=[
43+
Condition(
44+
name="Temperature",
45+
value=NumericConditionValue(
46+
condition_type=ConditionType.NUMERIC,
47+
range=[ConditionRange(min=-25, step=20, max=85)],
48+
unit="C",
49+
),
50+
),
51+
Condition(
52+
name="Supply Voltage",
53+
value=NumericConditionValue(
54+
condition_type=ConditionType.NUMERIC,
55+
discrete=[1.3, 1.5, 1.7],
56+
unit="mV",
57+
),
58+
),
59+
],
60+
),
61+
SpecificationDefinition(
62+
product_id=product,
63+
spec_id="spec3",
64+
type=SpecificationType.FUNCTIONAL,
65+
category="Noise Thresholds",
66+
name="noise",
67+
),
68+
]
69+
70+
# Create the specs on the server
71+
client.create_specs(CreateSpecificationsRequest(specs=spec_requests))
72+
73+
# You can query specs based on any field using DynamicLinq syntax.
74+
# These are just some representative examples.
75+
76+
response = client.query_specs(QuerySpecificationsRequest(productIds=[product]))
77+
all_product_specs = response.specs
78+
79+
# Query based on spec id
80+
response = client.query_specs(
81+
QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec2"')
82+
)
83+
if response.specs:
84+
spec2 = response.specs[0]
85+
86+
# Query based on name
87+
response = client.query_specs(
88+
QuerySpecificationsRequest(product_ids=[product], filter='name.Contains("voltage")')
89+
)
90+
voltage_specs = response.specs
91+
92+
# Query based on Category
93+
response = client.query_specs(
94+
QuerySpecificationsRequest(
95+
product_ids=[product], filter='category == "Noise Thresholds"'
96+
)
97+
)
98+
noise_category = response.specs
99+
print(noise_category)
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from nisystemlink.clients.core import HttpConfiguration
2+
from nisystemlink.clients.spec import SpecClient
3+
from nisystemlink.clients.spec.models import (
4+
QuerySpecificationsRequest,
5+
SpecificationDefinition,
6+
SpecificationType,
7+
UpdateSpecificationsRequest,
8+
)
9+
10+
# Setup the server configuration to point to your instance of SystemLink Enterprise
11+
server_configuration = HttpConfiguration(
12+
server_uri="https://yourserver.yourcompany.com",
13+
api_key="YourAPIKeyGeneratedFromSystemLink",
14+
)
15+
client = SpecClient(configuration=server_configuration)
16+
17+
# The query and delete examples assume you have created the specs from the query_specs example
18+
product = "Amplifier"
19+
20+
# update spec1 to change the block to "modifiedBlock"
21+
# query the original spec
22+
response = client.query_specs(
23+
QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
24+
)
25+
if response.specs:
26+
original_spec1 = response.specs[0]
27+
print(f"Original spec1 block: {original_spec1.block}")
28+
print(f"Original spec1 version: {original_spec1.version}")
29+
30+
# make the modifications
31+
modified_spec = SpecificationDefinition(
32+
id=original_spec1.id,
33+
product_id=original_spec1.product_id,
34+
spec_id=original_spec1.spec_id,
35+
type=SpecificationType.FUNCTIONAL,
36+
keywords=["work", "reviewed"],
37+
block="modifiedBlock",
38+
version=original_spec1.version,
39+
workspace=original_spec1.workspace,
40+
)
41+
update_response = client.update_specs(
42+
specs=UpdateSpecificationsRequest(specs=[modified_spec])
43+
)
44+
if update_response and update_response.updated_specs:
45+
print(f"New spec1 version: {update_response.updated_specs[0].version}")
46+
47+
# query again to see new version
48+
response = client.query_specs(
49+
QuerySpecificationsRequest(product_ids=[product], filter='specId == "spec1"')
50+
)
51+
if response.specs:
52+
original_spec1 = response.specs[0]
53+
print(f"Modified spec1 block: {original_spec1.block}")
54+
55+
# delete all the specs for the product
56+
# query all specs
57+
response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
58+
if response.specs:
59+
client.delete_specs(ids=[spec.id for spec in response.specs])
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from nisystemlink.clients.core._uplink._json_model import JsonModel
2+
3+
4+
class Operation(JsonModel):
5+
"""Represents an operation that can be performed on a data frame."""
6+
7+
available: bool
8+
"""Whether or not the operation is available to the caller (e.g. due to permissions)."""
9+
10+
version: int
11+
"""The version of the available operation."""

nisystemlink/clients/dataframe/models/_api_info.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
1+
from nisystemlink.clients.core._api_info import Operation
12
from nisystemlink.clients.core._uplink._json_model import JsonModel
23

34

4-
class Operation(JsonModel):
5-
"""Represents an operation that can be performed on a data frame."""
6-
7-
available: bool
8-
"""Whether or not the operation is available to the caller (e.g. due to permissions)."""
9-
10-
version: int
11-
"""The version of the available operation."""
12-
13-
145
class OperationsV1(JsonModel):
156
"""The operations available in the routes provided by the v1 HTTP API."""
167

nisystemlink/clients/spec/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from ._spec_client import SpecClient
2+
3+
# flake8: noqa
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Implementation of SpecClient"""
2+
3+
from typing import List, Optional
4+
5+
from nisystemlink.clients import core
6+
from nisystemlink.clients.core._uplink._base_client import BaseClient
7+
from nisystemlink.clients.core._uplink._methods import get, post
8+
from uplink import Field
9+
10+
from . import models
11+
12+
13+
class SpecClient(BaseClient):
14+
def __init__(self, configuration: Optional[core.HttpConfiguration]):
15+
if configuration is None:
16+
configuration = core.JupyterHttpConfiguration()
17+
super().__init__(configuration, base_path="/nispec/v1/")
18+
19+
@get("")
20+
def api_info(self) -> models.V1Operations:
21+
"""Get information about available API operations.
22+
23+
Returns:
24+
Information about available API operations.
25+
26+
Raises:
27+
ApiException: if unable to communicate with the DataFrame Service.
28+
"""
29+
...
30+
31+
@post("specs")
32+
def create_specs(
33+
self, specs: models.CreateSpecificationsRequest
34+
) -> models.CreateSpecificationsPartialSuccess:
35+
"""Creates one or more specifications.
36+
37+
Args:
38+
specs: A list of specifications to create.
39+
40+
Returns:
41+
A list of specs that were successfully created and ones that failed to be created.
42+
43+
Raises:
44+
ApiException: if unable to communicate with the `/nispec` service or if there are
45+
invalid arguments.
46+
"""
47+
...
48+
49+
@post("delete-specs", args=[Field("ids")])
50+
def delete_specs(
51+
self, ids: List[str]
52+
) -> Optional[models.DeleteSpecificationsPartialSuccess]:
53+
"""Deletes one or more specifications by global id.
54+
55+
Args:
56+
ids: a list of specification ids. Note that these are the global ids and not the
57+
`specId` that is local to a product and workspace.
58+
59+
Returns:
60+
None if all deletes succeed otherwise a list of which ids failed and which succeeded.
61+
62+
Raises:
63+
ApiException: if unable to communicate with the `nispec` service or if there are invalid
64+
arguments.
65+
"""
66+
...
67+
68+
@post("query-specs")
69+
def query_specs(
70+
self, query: models.QuerySpecificationsRequest
71+
) -> models.QuerySpecifications:
72+
"""Queries for specs that match the filters.
73+
74+
Args:
75+
query: The query contains a product id as well as a filter for specs under that product.
76+
77+
Returns:
78+
A list of specifications that match the filter.
79+
"""
80+
...
81+
82+
@post("update-specs")
83+
def update_specs(
84+
self, specs: models.UpdateSpecificationsRequest
85+
) -> Optional[models.UpdateSpecificationsPartialSuccess]:
86+
"""Updates one or more specifications.
87+
88+
Update requires that the version field matches the version being updated from.
89+
90+
Args:
91+
specs: a list of specifications that are to be updated. Must include the global id and
92+
each spec being updated must match the version currently on the server.
93+
94+
Returns
95+
A list of specs that were successfully updated and a list of ones that were not along
96+
with error messages for updates that failed.
97+
"""
98+
...
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from ._api_info import Operation, V1Operations
2+
from ._condition import (
3+
Condition,
4+
ConditionRange,
5+
ConditionType,
6+
NumericConditionValue,
7+
StringConditionValue,
8+
)
9+
from ._create_specs_request import (
10+
CreatedSpecification,
11+
CreateSpecificationsPartialSuccess,
12+
CreateSpecificationsRequest,
13+
)
14+
from ._delete_specs_request import DeleteSpecificationsPartialSuccess
15+
from ._query_specs import QuerySpecificationsRequest, QuerySpecifications
16+
from ._specification import (
17+
Specification,
18+
SpecificationCreation,
19+
SpecificationDefinition,
20+
SpecificationLimit,
21+
SpecificationServerManaged,
22+
SpecificationType,
23+
SpecificationUpdated,
24+
SpecificationUserManaged,
25+
SpecificationWithHistory,
26+
)
27+
from ._update_specs_request import (
28+
UpdatedSpecification,
29+
UpdateSpecificationsPartialSuccess,
30+
UpdateSpecificationsRequest,
31+
)
32+
33+
# flake8: noqa

0 commit comments

Comments
 (0)