Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add client for SystemLink results API #82

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ API Reference
api_reference/core
api_reference/tag
api_reference/testmonitor
api_reference/result
api_reference/dataframe
api_reference/spec
api_reference/file
Expand Down
23 changes: 23 additions & 0 deletions docs/api_reference/result.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.. _api_tag_page:

nisystemlink.clients.result
======================

.. autoclass:: nisystemlink.clients.result.ResultClient
:exclude-members: __init__

.. automethod:: __init__
.. automethod:: create_results
.. automethod:: get_results
.. automethod:: query_results
.. automethod:: query_result_values
.. automethod:: update_results
.. automethod:: delete_result
.. automethod:: delete_results

.. automodule:: nisystemlink.clients.result.models
:members:
:imported-members:

.. automodule:: nisystemlink.clients.result.utilities
:members:
27 changes: 27 additions & 0 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,32 @@ Examples
Get the metadata of a File using its Id and download it.

.. literalinclude:: ../examples/file/download_file.py
:language: python
:linenos:


Result API
-------

Overview
~~~~~~~~

The :class:`.ResultClient` class is the primary entry point of the Result API.

When constructing a :class:`.ResultClient`, you can pass an
:class:`.HttpConfiguration` (like one retrieved from the
:class:`.HttpConfigurationManager`), or let :class:`.ResultClient` use the
default connection. The default connection depends on your environment.

With a :class:`.ResultClient` object, you can:

* Create, update, query, and delete results

Examples
~~~~~~~~

Create, query, update, and delete some results

.. literalinclude:: ../examples/result/results.py
:language: python
:linenos:
87 changes: 87 additions & 0 deletions examples/result/results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from nisystemlink.clients.core import HttpConfiguration
from nisystemlink.clients.result import ResultClient
from nisystemlink.clients.result.models import (
QueryResultsRequest,
QueryResultValuesRequest,
Result,
ResultField,
StatusObject,
StatusType,
)

program_name = "Example Name"
host_name = "Example Host"


def create_some_results():
"""Create two example results on your server."""
new_results = [
Result(
part_number="Example 123 AA",
program_name=program_name,
host_name=host_name,
status=StatusObject(status_type=StatusType.PASSED, status_name="Passed"),
keywords=["original keyword"],
properties={"original property key": "yes"},
),
Result(
part_number="Example 123 AA1",
program_name=program_name,
host_name=host_name,
status=StatusObject(status_type=StatusType.FAILED, status_name="Failed"),
keywords=["original keyword"],
properties={"original property key": "original"},
),
]
create_response = client.create_results(new_results)
return create_response


# Setup the server configuration to point to your instance of SystemLink Enterprise
server_configuration = HttpConfiguration(
server_uri="https://yourserver.yourcompany.com",
api_key="YourAPIKeyGeneratedFromSystemLink",
)
client = ResultClient(configuration=server_configuration)

create_response = create_some_results()

# Get all the results using the continuation token in batches of 100 at a time.
response = client.get_results_paged(take=100, return_count=True)
all_results = response.results
while response.continuation_token:
response = client.get_results_paged(
take=100, continuation_token=response.continuation_token, return_count=True
)
all_results.extend(response.results)

# use get for first result created
created_result = client.get_result(create_response.results[0].id)

# Query results without continuation
query_request = QueryResultsRequest(
filter=f'programName="{program_name}" && hostName="{host_name}"',
return_count=True,
order_by=ResultField.HOST_NAME,
)
response = client.query_results_paged(query_request)

# Update the first result that you just created and replace the keywords
updated_result = create_response.results[0]
updated_result.keywords = ["new keyword"]
updated_result.properties = {"new property key": "new value"}
update_response = client.update_results([create_response.results[0]], replace=True)

# Query for just the ids of results that match the family
values_query = QueryResultValuesRequest(
filter=f'programName="{program_name}"', field=ResultField.ID
)
values_response = client.query_result_values(query=values_query)

# delete each created result individually by id
for result in create_response.results:
client.delete_result(result.id)

# Create some more and delete them with a single call to delete.
create_response = create_some_results()
client.delete_results([result.id for result in create_response.results])
3 changes: 3 additions & 0 deletions nisystemlink/clients/result/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._result_client import ResultClient

# flake8: noqa
182 changes: 182 additions & 0 deletions nisystemlink/clients/result/_result_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""Implementations of the Result Client."""

from typing import List, Optional

from nisystemlink.clients import core
from nisystemlink.clients.core._uplink._base_client import BaseClient
from nisystemlink.clients.core._uplink._methods import delete, get, post
from nisystemlink.clients.result.models import Result
from uplink import Field, Query, retry, returns

from . import models


@retry(when=retry.when.status(429), stop=retry.stop.after_attempt(5))
class ResultClient(BaseClient):

def __init__(self, configuration: Optional[core.HttpConfiguration] = None):
"""Initialize an instance.

Args:
configuration: Defines the web server to connect to and information about
how to connect. If not provided, the
:class:`HttpConfigurationManager <nisystemlink.clients.core.HttpConfigurationManager>`
is used to obtain the configuration.

Raises:
ApiException: if unable to communicate with the Result Service.
"""
if configuration is None:
configuration = core.HttpConfigurationManager.get_configuration()
super().__init__(configuration, base_path="/nitestmonitor/v2/")

@post("results", args=[Field("results")])
def create_results(
self, results: List[Result]
) -> models.CreateResultsPartialSuccess:
"""Creates one or more results and returns errors for failed creations.

Args:
results: A list of results to attempt to create.

Returns: A list of created results, results that failed to create, and errors for
failures.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` service of provided invalid
arguments.
"""
...

@get(
"results",
args=[Query("continuationToken"), Query("take"), Query("returnCount")],
)
def get_results_paged(
self,
continuation_token: Optional[str] = None,
take: Optional[int] = None,
return_count: Optional[bool] = None,
) -> models.PagedResults:
"""Reads a list of results.

Args:
continuation_token: The token used to paginate results.
take: The number of results to get in this request.
return_count: Whether or not to return the total number of results available.

Returns:
A list of results.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@get("results/{id}")
def get_result(self, id: str) -> models.Result:
"""Retrieves a single result by id.

Args:
id (str): Unique ID of a result.

Returns:
The single result matching `id`

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@post("query-results")
def query_results_paged(
self, query: models.QueryResultsRequest
) -> models.PagedResults:
"""Queries for results that match the filter.

Args:
query : The query contains a DynamicLINQ query string in addition to other details
about how to filter and return the list of results.

Returns:
A paged list of results with a continuation token to get the next page.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided invalid
arguments.
"""
...

@returns.json # type: ignore
@post("query-result-values")
def query_result_values(self, query: models.QueryResultValuesRequest) -> List[str]:
"""Queries for results that match the query and returns a list of the requested field.

Args:
query : The query for the fields.

Returns:
A list of the values of the queried field.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service or provided
invalid arguments.
"""
...

@post("update-results", args=[Field("results"), Field("replace")])
def update_results(
self, results: List[Result], replace: bool = False
) -> models.CreateResultsPartialSuccess:
"""Updates a list of results with optional field replacement.

Args:
`results`: A list of results to update. Results are matched for update by id.
`replace`: Replace the existing fields instead of merging them. Defaults to `False`.
If this is `True`, then `keywords` and `properties` for the result will be
replaced by what is in the `results` provided in this request.
If this is `False`, then the `keywords` and `properties` in this request will
merge with what is already present in the server resource.

Returns: A list of updates results, results that failed to update, and errors for
failures.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@delete("results/{id}")
def delete_result(self, id: str) -> None:
"""Deletes a single result by id.

Args:
id (str): Unique ID of a result.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...

@post("delete-results", args=[Field("ids")])
def delete_results(
self, ids: List[str]
) -> Optional[models.DeleteResultsPartialSuccess]:
"""Deletes multiple results.

Args:
ids (List[str]): List of unique IDs of results.

Returns:
A partial success if any results failed to delete, or None if all
results were deleted successfully.

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...
11 changes: 11 additions & 0 deletions nisystemlink/clients/result/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from ._result import Result, StatusObject, StatusType
from ._create_results_partial_success import CreateResultsPartialSuccess
from ._delete_results_partial_success import DeleteResultsPartialSuccess
from ._paged_results import PagedResults
from ._query_results_request import (
QueryResultsRequest,
ResultField,
QueryResultValuesRequest,
)

# flake8: noqa
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List, Optional

from nisystemlink.clients.core import ApiError
from nisystemlink.clients.core._uplink._json_model import JsonModel
from nisystemlink.clients.result.models import Result


class CreateResultsPartialSuccess(JsonModel):
results: List[Result]
"""The list of results that were successfully created."""

failed: Optional[List[Result]] = None
"""The list of results that were not created.
If this is `None`, then all results were successfully created.
"""

error: Optional[ApiError] = None
"""Error messages for results that were not created.
If this is `None`, then all results were successfully created.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import List, Optional

from nisystemlink.clients.core import ApiError
from nisystemlink.clients.core._uplink._json_model import JsonModel


class DeleteResultsPartialSuccess(JsonModel):
"""The result of deleting multiple results when one or more results could not be deleted."""

ids: List[str]
"""The IDs of the results that were successfully deleted."""

failed: Optional[List[str]]
"""The IDs of the results that could not be deleted."""

error: Optional[ApiError]
"""The error that occurred when deleting the results."""
Loading