Skip to content

Commit e9feff6

Browse files
authored
feat: Add and resolve query projection for products and specs respectively (#97)
Co-authored-by: Sam rishi <sam.rishi@solitontech.local> Co-authored-by: sam-rishi <155733081+sam-rishi@users.noreply.github.com> Co-authored-by: Sam Rishi <sam.rishi@ni.com> BREAKING CHANGE: Product and Specification models have been updated to support projections. - Product client changes-- - `models.Product` now defines all fields as Optional. - `ProductClient.create_product`'s `products` parameter is now typed as `models.CreateProductRequest` - `ProductClient.update_product`'s `products` parameter is now typed as `models.UpdateProductRequest` - Specifications client changes-- - `models.Specification` and `models.SpecificationDefinition` now define all fields as Optional. - `models.QuerySpecifications` has been renamed `models.PagedSpecifications` to better align to other clients. - `models.CreateSpecificationeRequest.specs` is now typed as `models.CreateSpecificationsRequestObject` instead of `models.SpecificationDefinition` - `models.UpdateSpecificationeRequest.specs` is now typed as `models.UpdateSpecificationsRequestObject` instead of `models.Specification`
1 parent 68a42a2 commit e9feff6

18 files changed

+411
-130
lines changed

examples/product/products.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from nisystemlink.clients.core import HttpConfiguration
22
from nisystemlink.clients.product import ProductClient
33
from nisystemlink.clients.product.models import (
4-
Product,
4+
CreateProductRequest,
55
ProductField,
6+
ProductOrderBy,
67
QueryProductsRequest,
78
QueryProductValuesRequest,
89
)
@@ -14,14 +15,14 @@
1415
def create_some_products():
1516
"""Create two example products on your server."""
1617
new_products = [
17-
Product(
18+
CreateProductRequest(
1819
part_number="Example 123 AA",
1920
name=name,
2021
family=family,
2122
keywords=["original keyword"],
2223
properties={"original property key": "yes"},
2324
),
24-
Product(
25+
CreateProductRequest(
2526
part_number="Example 123 AA1",
2627
name=name,
2728
family=family,
@@ -58,9 +59,9 @@ def create_some_products():
5859
query_request = QueryProductsRequest(
5960
filter=f'family="{family}" && name="{name}"',
6061
return_count=True,
61-
order_by=ProductField.FAMILY,
62+
order_by=ProductOrderBy.FAMILY,
6263
)
63-
response = client.query_products_paged(query_request)
64+
query_response = client.query_products_paged(query_request)
6465

6566
# Update the first product that you just created and replace the keywords
6667
updated_product = create_response.products[0]

examples/spec/update_and_delete_specs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@
5656
# query all specs
5757
response = client.query_specs(QuerySpecificationsRequest(product_ids=[product]))
5858
if response.specs:
59-
client.delete_specs(ids=[spec.id for spec in response.specs])
59+
client.delete_specs(ids=[spec.id for spec in response.specs if spec.id])

nisystemlink/clients/product/_product_client.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from nisystemlink.clients import core
66
from nisystemlink.clients.core._uplink._base_client import BaseClient
77
from nisystemlink.clients.core._uplink._methods import delete, get, post
8-
from nisystemlink.clients.product.models import Product
9-
from uplink import Field, Query, returns
8+
from uplink import Field, Query, retry, returns
109

1110
from . import models
1211

1312

13+
@retry(
14+
when=retry.when.status([408, 429, 502, 503, 504]), stop=retry.stop.after_attempt(5)
15+
)
1416
class ProductClient(BaseClient):
1517
def __init__(self, configuration: Optional[core.HttpConfiguration] = None):
1618
"""Initialize an instance.
@@ -30,7 +32,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None):
3032

3133
@post("products", args=[Field("products")])
3234
def create_products(
33-
self, products: List[Product]
35+
self, products: List[models.CreateProductRequest]
3436
) -> models.CreateProductsPartialSuccess:
3537
"""Creates one or more products and returns errors for failed creations.
3638
@@ -128,7 +130,7 @@ def query_product_values(
128130

129131
@post("update-products", args=[Field("products"), Field("replace")])
130132
def update_products(
131-
self, products: List[Product], replace: bool = False
133+
self, products: List[models.UpdateProductRequest], replace: bool = False
132134
) -> models.CreateProductsPartialSuccess:
133135
"""Updates a list of products with optional field replacement.
134136
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from ._product import Product
21
from ._create_products_partial_success import CreateProductsPartialSuccess
32
from ._delete_products_partial_success import DeleteProductsPartialSuccess
43
from ._paged_products import PagedProducts
54
from ._query_products_request import (
6-
QueryProductsRequest,
75
ProductField,
6+
ProductOrderBy,
7+
ProductProjection,
8+
QueryProductsRequest,
89
QueryProductValuesRequest,
910
)
11+
from ._product import Product
12+
from ._product_request import CreateProductRequest, UpdateProductRequest
1013

1114
# flake8: noqa

nisystemlink/clients/product/models/_create_products_partial_success.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@
22

33
from nisystemlink.clients.core import ApiError
44
from nisystemlink.clients.core._uplink._json_model import JsonModel
5-
from nisystemlink.clients.product.models import Product
5+
from nisystemlink.clients.product.models._product import Product
6+
from nisystemlink.clients.product.models._product_request import CreateProductRequest
67

78

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

12-
failed: Optional[List[Product]] = None
13+
failed: Optional[List[CreateProductRequest]]
1314
"""The list of products that were not created.
1415
1516
If this is `None`, then all products were successfully created.
1617
"""
1718

18-
error: Optional[ApiError] = None
19+
error: Optional[ApiError]
1920
"""Error messages for products that were not created.
2021
2122
If this is `None`, then all products were successfully created.

nisystemlink/clients/product/models/_paged_products.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from typing import List, Optional
22

33
from nisystemlink.clients.core._uplink._with_paging import WithPaging
4-
from nisystemlink.clients.product.models import Product
4+
from nisystemlink.clients.product.models._product import Product
55

66

77
class PagedProducts(WithPaging):
8-
"""The response for a Products query containing matched products."""
8+
"""The response containing the list of products, total count of products and the continuation
9+
token if applicable.
10+
"""
911

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

nisystemlink/clients/product/models/_product.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class Product(JsonModel):
1010
id: Optional[str]
1111
"""The globally unique id of the product."""
1212

13-
part_number: str
13+
part_number: Optional[str]
1414
"""The part number is the unique identifier of a product within a single org.
1515
1616
Usually the part number refers to a specific revision or version of a given product."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Dict, List, Optional
2+
3+
from nisystemlink.clients.core._uplink._json_model import JsonModel
4+
5+
6+
class BaseProductRequest(JsonModel):
7+
"""Contains information about a product."""
8+
9+
name: Optional[str]
10+
"""The name of the product.
11+
12+
Usually the name is used to refer to several part numbers that all have the same name but
13+
different revisions or versions.
14+
"""
15+
16+
family: Optional[str]
17+
"""The family that that this product belongs to.
18+
19+
Usually the family is a grouping above product name. A family usually has multiple product
20+
names within it.
21+
"""
22+
23+
file_ids: Optional[List[str]]
24+
"""A list of file ids that are attached to this product."""
25+
26+
keywords: Optional[List[str]]
27+
"""A list of keywords that categorize this product."""
28+
29+
properties: Optional[Dict[str, str]]
30+
"""A list of custom properties for this product."""
31+
32+
workspace: Optional[str]
33+
"""The id of the workspace that this product belongs to."""
34+
35+
36+
class CreateProductRequest(BaseProductRequest):
37+
38+
part_number: str
39+
"""The part number is the unique identifier of a product within a single org.
40+
41+
Usually the part number refers to a specific revision or version of a given product."""
42+
43+
44+
class UpdateProductRequest(BaseProductRequest):
45+
"""This is the request model to update a product."""
46+
47+
id: str
48+
"""The globally unique id of the product."""

nisystemlink/clients/product/models/_query_products_request.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import Field
66

77

8-
class ProductField(str, Enum):
8+
class ProductOrderBy(str, Enum):
99
"""The valid ways to order a product query."""
1010

1111
ID = "ID"
@@ -15,6 +15,32 @@ class ProductField(str, Enum):
1515
UPDATED_AT = "UPDATED_AT"
1616

1717

18+
class ProductField(str, Enum):
19+
"""An enumeration of product fields for which the values can be queried for."""
20+
21+
ID = "ID"
22+
FAMILY = "FAMILY"
23+
PART_NUMBER = "PART_NUMBER"
24+
NAME = "NAME"
25+
UPDATED_AT = "UPDATED_AT"
26+
27+
28+
class ProductProjection(str, Enum):
29+
"""An enumeration of all fields in a Product. These are used to project the required fields
30+
from the API response.
31+
"""
32+
33+
ID = "ID"
34+
FAMILY = "FAMILY"
35+
PART_NUMBER = "PART_NUMBER"
36+
NAME = "NAME"
37+
UPDATED_AT = "UPDATED_AT"
38+
WORKSPACE = "WORKSPACE"
39+
KEYWORDS = "KEYWORDS"
40+
PROPERTIES = "PROPERTIES"
41+
FILE_IDS = "FILE_IDS"
42+
43+
1844
class QueryProductsBase(JsonModel):
1945
filter: Optional[str] = None
2046
"""
@@ -50,7 +76,7 @@ class QueryProductsBase(JsonModel):
5076

5177
class QueryProductsRequest(QueryProductsBase):
5278

53-
order_by: Optional[ProductField] = Field(None, alias="orderBy")
79+
order_by: Optional[ProductOrderBy] = Field(None, alias="orderBy")
5480
"""Specifies the fields to use to sort the products.
5581
5682
By default, products are sorted by `id`
@@ -61,11 +87,21 @@ class QueryProductsRequest(QueryProductsBase):
6187
6288
By default, this value is `false` and products are sorted in ascending order.
6389
"""
90+
91+
projection: Optional[List[ProductProjection]] = None
92+
"""Specifies the product fields to project.
93+
94+
When a field value is given here, the corresponding field will be present in all returned products,
95+
and all unspecified fields will be excluded. If no projection is specified, all product fields
96+
will be returned.
97+
"""
98+
6499
take: Optional[int] = None
65100
"""Maximum number of products to return in the current API response.
66101
67102
Uses the default if the specified value is negative. The default value is `1000` products.
68103
"""
104+
69105
continuation_token: Optional[str] = None
70106
"""Allows users to continue the query at the next product that matches the given criteria.
71107

nisystemlink/clients/product/utilities/_file_utilities.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from nisystemlink.clients.product._product_client import ProductClient
44
from nisystemlink.clients.product.models._paged_products import PagedProducts
5-
from nisystemlink.clients.product.models._product import Product
5+
from nisystemlink.clients.product.models._product import (
6+
Product,
7+
)
68
from nisystemlink.clients.product.models._query_products_request import (
79
QueryProductsRequest,
810
)

nisystemlink/clients/spec/_spec_client.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
from nisystemlink.clients import core
66
from nisystemlink.clients.core._uplink._base_client import BaseClient
77
from nisystemlink.clients.core._uplink._methods import get, post
8-
from uplink import Field
8+
from uplink import Field, retry
99

1010
from . import models
1111

1212

13+
@retry(
14+
when=retry.when.status([408, 429, 502, 503, 504]), stop=retry.stop.after_attempt(5)
15+
)
1316
class SpecClient(BaseClient):
1417
def __init__(self, configuration: Optional[core.HttpConfiguration] = None):
1518
"""Initialize an instance.
@@ -80,7 +83,7 @@ def delete_specs(
8083
@post("query-specs")
8184
def query_specs(
8285
self, query: models.QuerySpecificationsRequest
83-
) -> models.QuerySpecifications:
86+
) -> models.PagedSpecifications:
8487
"""Queries for specs that match the filters.
8588
8689
Args:

nisystemlink/clients/spec/models/__init__.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,25 @@
1010
CreatedSpecification,
1111
CreateSpecificationsPartialSuccess,
1212
CreateSpecificationsRequest,
13+
CreateSpecificationsRequestObject,
1314
)
1415
from ._delete_specs_request import DeleteSpecificationsPartialSuccess
15-
from ._query_specs import QuerySpecificationsRequest, QuerySpecifications
16+
from ._query_specs import (
17+
QuerySpecificationsRequest,
18+
PagedSpecifications,
19+
SpecificationProjection,
20+
)
1621
from ._specification import (
1722
Specification,
18-
SpecificationCreation,
1923
SpecificationDefinition,
2024
SpecificationLimit,
21-
SpecificationServerManaged,
2225
SpecificationType,
23-
SpecificationUpdated,
24-
SpecificationUserManaged,
25-
SpecificationWithHistory,
2626
)
2727
from ._update_specs_request import (
2828
UpdatedSpecification,
2929
UpdateSpecificationsPartialSuccess,
3030
UpdateSpecificationsRequest,
31+
UpdateSpecificationsRequestObject,
3132
)
3233

3334
# flake8: noqa

0 commit comments

Comments
 (0)