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 products API #69

Merged
merged 50 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3c5ba5f
get products API (no tests yet)
adamarnesen Jun 3, 2024
e329420
add query products API
adamarnesen Jun 3, 2024
31359be
add create products API
adamarnesen Jun 3, 2024
2ae0bee
add get by id
adamarnesen Jun 3, 2024
e89bc9f
move create to top
adamarnesen Jun 3, 2024
561de1b
add delete by id
adamarnesen Jun 3, 2024
37c42fd
add delete multiple products api
adamarnesen Jun 3, 2024
03c0baf
add ...
adamarnesen Jun 3, 2024
4f0644c
remove silly create wrapper
adamarnesen Jun 3, 2024
fc2bb49
add update api for products
adamarnesen Jul 12, 2024
33f7e73
implement product values query
adamarnesen Jul 12, 2024
9123d25
add test for creating a single product
adamarnesen Jul 12, 2024
e11a348
add multi create test
adamarnesen Jul 12, 2024
ce94309
test get after create
adamarnesen Jul 12, 2024
ee545fc
add test for get with take
adamarnesen Jul 12, 2024
ab198ac
test count on get
adamarnesen Jul 12, 2024
82f8307
test get product by part number
adamarnesen Jul 12, 2024
c775dfd
test for query by part number
adamarnesen Aug 8, 2024
13e3f2a
test query product values
adamarnesen Aug 8, 2024
c129675
test update keywords
adamarnesen Aug 8, 2024
c49ca7a
add tests for update with properties
adamarnesen Aug 8, 2024
6e446ed
add products example
adamarnesen Aug 8, 2024
dfd9ce7
update getting started
adamarnesen Aug 8, 2024
710f281
update lint errors
adamarnesen Aug 8, 2024
ed759e7
fix type errors on products example
adamarnesen Aug 9, 2024
432e94b
fix type errors on tests
adamarnesen Aug 9, 2024
b3d0639
ignore type error for untyped decorator
adamarnesen Aug 9, 2024
d60e2af
remove unused imports
adamarnesen Aug 9, 2024
56f975a
add helper for getting linked products from a file id
adamarnesen Aug 9, 2024
539d50d
fix potential test intermittency with colliding ids
adamarnesen Aug 9, 2024
434434f
fix docs for linked products method
adamarnesen Aug 9, 2024
f22c4c3
update docs to include utiltiies
adamarnesen Aug 9, 2024
53130c3
docs experiment
adamarnesen Aug 9, 2024
0ef47d3
rename utilities and update docs
adamarnesen Aug 9, 2024
9f72f5b
remove some tags from docs
adamarnesen Aug 9, 2024
f5f4e0b
update `__init__` to get configuration from manager if not provided.
adamarnesen Aug 12, 2024
6c88fc8
update delete to return `None` instead of `Optional[ApiError]`
adamarnesen Oct 24, 2024
e3f406b
fix docstring on delete partial succes
adamarnesen Oct 24, 2024
e01bfa8
update field alias
adamarnesen Oct 24, 2024
8d4a8ee
Merge branch 'master' of https://github.com/ni/nisystemlink-clients-p…
adamarnesen Oct 24, 2024
3d35cb8
fix linting errors
adamarnesen Oct 24, 2024
51f8e5f
fix enum to use `str`
adamarnesen Nov 4, 2024
d362dd7
rename file to match class name
adamarnesen Nov 4, 2024
a045d39
move product code to its own module to keep separate from testmonitor
adamarnesen Nov 4, 2024
d11a4dc
update docs after move to product module
adamarnesen Nov 4, 2024
b1d314d
udpate toc and example for product module
adamarnesen Nov 4, 2024
d0a7a4b
update docstring header
adamarnesen Nov 5, 2024
ebf55ee
refactor into module with private file
adamarnesen Nov 5, 2024
6cc5e0e
include `_paged` modifier on current query_products method so that we…
adamarnesen Nov 7, 2024
ce68ed6
rename get to include `_paged`
adamarnesen Nov 8, 2024
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
13 changes: 12 additions & 1 deletion docs/api_reference/testmonitor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,18 @@ nisystemlink.clients.testmonitor
:exclude-members: __init__

.. automethod:: __init__
.. automethod:: api_info
.. automethod:: create_products
.. automethod:: get_products
.. automethod:: query_products
.. automethod:: query_product_values
.. automethod:: update_products
.. automethod:: delete_product
.. automethod:: delete_products

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

.. automodule:: nisystemlink.clients.testmonitor.utilities
:members:
28 changes: 28 additions & 0 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,34 @@ Subscribe to tag changes
:language: python
:linenos:


TestMonitor API
-------

Overview
~~~~~~~~

The :class:`.TestMonitorClient` class is the primary entry point of the TestMonitor API.

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

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

* Create, update, query, and delete Products

Examples
~~~~~~~~

Create, query, update, and delete some products

.. literalinclude:: ../examples/testmonitor/products.py
:language: python
:linenos:


DataFrame API
-------

Expand Down
83 changes: 83 additions & 0 deletions examples/testmonitor/products.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from nisystemlink.clients.core import HttpConfiguration
from nisystemlink.clients.testmonitor import TestMonitorClient
from nisystemlink.clients.testmonitor.models import (
Product,
ProductField,
QueryProductsRequest,
QueryProductValuesRequest,
)

name = "Example Name"
family = "Example Family"


def create_some_products():
"""Create two example products on your server."""
new_products = [
Product(
part_number="Example 123 AA",
name=name,
family=family,
keywords=["original keyword"],
properties={"original property key": "yes"},
),
Product(
part_number="Example 123 AA1",
name=name,
family=family,
keywords=["original keyword"],
properties={"original property key": "original"},
),
]
create_response = client.create_products(new_products)
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 = TestMonitorClient(configuration=server_configuration)

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

create_response = create_some_products()

# use get for first product created
created_product = client.get_product(create_response.products[0].id)

# Query products without continuation
query_request = QueryProductsRequest(
filter=f'family="{family}" && name="{name}"',
return_count=True,
order_by=ProductField.FAMILY,
)
response = client.query_products(query_request)

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

# Query for just the ids of products that match the family
values_query = QueryProductValuesRequest(
filter=f'family="{family}"', field=ProductField.ID
)
values_response = client.query_product_values(query=values_query)

# delete each created product individually by id
for product in create_response.products:
client.delete_product(product.id)

# Create some more and delete them with a single call to delete.
create_response = create_some_products()
client.delete_products([product.id for product in create_response.products])
162 changes: 159 additions & 3 deletions nisystemlink/clients/testmonitor/_test_monitor_client.py
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Implementation of TestMonitor Client"""

from typing import Optional
from typing import List, Optional

from nisystemlink.clients import core
from nisystemlink.clients.core import ApiError
from nisystemlink.clients.core._uplink._base_client import BaseClient
from nisystemlink.clients.core._uplink._methods import get
from nisystemlink.clients.core._uplink._methods import delete, get, post
from nisystemlink.clients.testmonitor.models import Product
from uplink import Field, Query, returns

from . import models

Expand All @@ -23,6 +26,159 @@ def api_info(self) -> models.ApiInfo:
Information about available API operations.

Raises:
ApiException: if unable to communicate with the `nitestmonitor` service.
ApiException: if unable to communicate with the `ni``/nitestmonitor``` service.
"""
...

@post("products", args=[Field("products")])
def create_products(
self, products: List[Product]
) -> models.CreateProductsPartialSuccess:
"""Creates one or more products and returns errors for failed creations.

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

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

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

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

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

Returns:
A list of products.

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

@get("products/{id}")
def get_product(self, id: str) -> models.Product:
"""Retrieves a single product by id.

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

Returns:
The single product matching `id`

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

@post("query-products")
def query_products(
self, query: models.QueryProductsRequest
) -> models.PagedProducts:
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
"""Queries for products 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 products.

Returns:
A paged list of products 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-product-values")
def query_product_values(
self, query: models.QueryProductValuesRequest
) -> List[str]:
"""Queries for products that match the query and returns a list of the requested field.

Args:
query : The query for the fields you want.

Returns:
A list of the values of the field you requested.

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

@post("update-products", args=[Field("products"), Field("replace")])
def update_products(
self, products: List[Product], replace: bool = False
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
) -> models.CreateProductsPartialSuccess:
"""Updates a list of products with optional field replacement.

Args:
`products`: A list of products to update. Products are matched for update by id.
`replace`: Replace the existing fields instead of merging them.
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
If this is `True`, then `keywords` and `properties` for the product will be
replaced by what is in the `products` 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 products, products that failed to update, and errors for
failures.

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

@delete("products/{id}")
def delete_product(self, id: str) -> Optional[ApiError]:
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
"""Deletes a single product by id.

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

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

@post("delete-products", args=[Field("ids")])
def delete_products(
self, ids: List[str]
) -> Optional[models.DeleteProductsPartialSuccess]:
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
"""Deletes multiple products.

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

Returns:
A partial success if any products failed to delete, or None if all
products were deleted successfully.
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved

Raises:
ApiException: if unable to communicate with the ``/nitestmonitor`` Service
or provided an invalid argument.
"""
...
9 changes: 9 additions & 0 deletions nisystemlink/clients/testmonitor/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
from ._api_info import Operation, V2Operations, ApiInfo
from ._product import Product
from ._create_products_request import CreateProductsPartialSuccess
from ._delete_products_partial_success import DeleteProductsPartialSuccess
from ._paged_products import PagedProducts
from ._query_products_request import (
QueryProductsRequest,
ProductField,
QueryProductValuesRequest,
)

# flake8: noqa
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import List, Optional

from nisystemlink.clients.core import ApiError
from nisystemlink.clients.core._uplink._json_model import JsonModel
from nisystemlink.clients.testmonitor.models import Product


class CreateProductsPartialSuccess(JsonModel):
products: List[Product]
"""The list of products that were successfully created."""

failed: Optional[List[Product]] = None
"""The list of products that were not created.

If this is `None`, then all products were successfully created.
"""

error: Optional[ApiError] = None
"""Error messages for products that were not created.

If this is `None`, then all products 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 DeleteProductsPartialSuccess(JsonModel):
"""The result of deleting multiple tables when one or more tables could not be deleted."""
adamarnesen marked this conversation as resolved.
Show resolved Hide resolved

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

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

error: Optional[ApiError]
"""The error that occurred when deleting the products."""
14 changes: 14 additions & 0 deletions nisystemlink/clients/testmonitor/models/_paged_products.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List, Optional

from nisystemlink.clients.core._uplink._with_paging import WithPaging
from nisystemlink.clients.testmonitor.models import Product


class PagedProducts(WithPaging):
"""The response for a Products query containing matched products."""

products: List[Product]
"""A list of all the products in this page."""

total_count: Optional[int]
"""The total number of products that match the query."""
Loading
Loading