From 35406ef378a070824173950c28c5639852e663e7 Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Mon, 24 Feb 2025 09:54:49 +0530 Subject: [PATCH 01/24] fix:QuerySpecsOptionalResponseModel --- .../clients/spec/models/_query_specs.py | 25 +++++++++++++++++-- tests/integration/spec/test_spec.py | 16 ++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 61300ac1..025a1302 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -3,7 +3,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.spec.models._specification import SpecificationWithHistory +from nisystemlink.clients.spec.models._specification import SpecificationType, SpecificationWithHistory class Projection(str, Enum): @@ -103,10 +103,31 @@ class QuerySpecificationsRequest(JsonModel): """ +class SpecificationWithOnlyOptionalFields(SpecificationWithHistory): + """A full specification with update and create history with only optional fields""" + + product_id: Optional[str] = None + """Id of the product to which the specification will be associated.""" + + spec_id: Optional[str] = None + + type: Optional[SpecificationType] = None + """Type of the specification.""" + + id : Optional[str] = None + """The global Id of the specification.""" + + version: Optional[int] = None + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + class QuerySpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" - specs: Optional[List[SpecificationWithHistory]] = None + specs: Optional[List[SpecificationWithOnlyOptionalFields]] = None """List of queried specifications. An empty list indicates that there are no specifications meeting the criteria provided in the diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 4d126c6d..38f674d3 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -276,3 +276,19 @@ def test__query_input_voltage__conditions_match( voltage_spec = response.specs[0] assert voltage_spec.conditions assert len(voltage_spec.conditions) == 2 + + def test__query_spec_projection_columns__columns_returned( + self, client: SpecClient, create_specs, create_specs_for_query, product + ): + requset = QuerySpecificationsRequest( + product_ids=[product], projection=["SPEC_ID", "NAME"] + ) + response = client.query_specs(requset) + assert response.specs + assert len(response.specs) == 3 + specs = [vars(spec) for spec in response.specs] + spec_columns = list(set(key for spec in specs for key in spec.keys() if spec[key] is not None)) + assert len(spec_columns) == 2 + assert "spec_id" in spec_columns + assert "name" in spec_columns + \ No newline at end of file From 52dfaeea6c4f5af5d7739681f3965799caa0ed56 Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Mon, 24 Feb 2025 11:53:14 +0530 Subject: [PATCH 02/24] fix:PR Comments --- nisystemlink/clients/spec/models/_query_specs.py | 12 ++++++++---- tests/integration/spec/test_spec.py | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 025a1302..fa4ba964 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -3,7 +3,10 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.spec.models._specification import SpecificationType, SpecificationWithHistory +from nisystemlink.clients.spec.models._specification import ( + SpecificationType, + SpecificationWithHistory, +) class Projection(str, Enum): @@ -103,7 +106,7 @@ class QuerySpecificationsRequest(JsonModel): """ -class SpecificationWithOnlyOptionalFields(SpecificationWithHistory): +class SpecificationWithOptionalFields(SpecificationWithHistory): """A full specification with update and create history with only optional fields""" product_id: Optional[str] = None @@ -114,7 +117,7 @@ class SpecificationWithOnlyOptionalFields(SpecificationWithHistory): type: Optional[SpecificationType] = None """Type of the specification.""" - id : Optional[str] = None + id: Optional[str] = None """The global Id of the specification.""" version: Optional[int] = None @@ -124,10 +127,11 @@ class SpecificationWithOnlyOptionalFields(SpecificationWithHistory): When an update is applied, the version is automatically incremented. """ + class QuerySpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" - specs: Optional[List[SpecificationWithOnlyOptionalFields]] = None + specs: Optional[List[SpecificationWithOptionalFields]] = None """List of queried specifications. An empty list indicates that there are no specifications meeting the criteria provided in the diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 38f674d3..ed5055d1 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -280,15 +280,16 @@ def test__query_input_voltage__conditions_match( def test__query_spec_projection_columns__columns_returned( self, client: SpecClient, create_specs, create_specs_for_query, product ): - requset = QuerySpecificationsRequest( + request = QuerySpecificationsRequest( product_ids=[product], projection=["SPEC_ID", "NAME"] ) - response = client.query_specs(requset) + response = client.query_specs(request) assert response.specs assert len(response.specs) == 3 specs = [vars(spec) for spec in response.specs] - spec_columns = list(set(key for spec in specs for key in spec.keys() if spec[key] is not None)) + spec_columns = list( + set(key for spec in specs for key in spec.keys() if spec[key] is not None) + ) assert len(spec_columns) == 2 assert "spec_id" in spec_columns assert "name" in spec_columns - \ No newline at end of file From 58e5bd8fd797fef9093fd262d6cd577259d58747 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Tue, 25 Feb 2025 02:39:55 +0530 Subject: [PATCH 03/24] feat: add projection and update response models --- examples/product/products.py | 2 +- .../clients/product/_product_client.py | 2 +- .../clients/product/models/__init__.py | 6 ++- .../clients/product/models/_paged_products.py | 20 +++++++- .../product/models/_query_products_request.py | 20 +++++++- .../models/_query_products_response.py | 13 ++++++ .../product/utilities/_file_utilities.py | 14 ++++-- .../product/test_product_client.py | 46 ++++++++++++++++++- 8 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 nisystemlink/clients/product/models/_query_products_response.py diff --git a/examples/product/products.py b/examples/product/products.py index 756cc54c..9de6f9a7 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -60,7 +60,7 @@ def create_some_products(): return_count=True, order_by=ProductField.FAMILY, ) -response = client.query_products_paged(query_request) +query_response = client.query_products_paged(query_request) # Update the first product that you just created and replace the keywords updated_product = create_response.products[0] diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index de627c5e..f8689bc4 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -91,7 +91,7 @@ def get_product(self, id: str) -> models.Product: @post("query-products") def query_products_paged( self, query: models.QueryProductsRequest - ) -> models.PagedProducts: + ) -> models.PagedQueryProductsResponse: """Queries for products that match the filter. Args: diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 88f6f68d..9049f273 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -1,11 +1,15 @@ from ._product import Product from ._create_products_partial_success import CreateProductsPartialSuccess from ._delete_products_partial_success import DeleteProductsPartialSuccess -from ._paged_products import PagedProducts +from ._paged_products import ( + PagedProducts, + PagedQueryProductsResponse, +) from ._query_products_request import ( QueryProductsRequest, ProductField, QueryProductValuesRequest, ) +from ._query_products_response import QueryProductsResponse # flake8: noqa diff --git a/nisystemlink/clients/product/models/_paged_products.py b/nisystemlink/clients/product/models/_paged_products.py index 7af39716..5f1decd5 100644 --- a/nisystemlink/clients/product/models/_paged_products.py +++ b/nisystemlink/clients/product/models/_paged_products.py @@ -2,13 +2,31 @@ from nisystemlink.clients.core._uplink._with_paging import WithPaging from nisystemlink.clients.product.models import Product +from nisystemlink.clients.product.models._query_products_response import ( + QueryProductsResponse, +) class PagedProducts(WithPaging): - """The response for a Products query containing matched products.""" + """The response containing the list of products, total count of products and the continuation + token if applicable. + """ 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.""" + + +class PagedQueryProductsResponse(WithPaging): + """The response for a Products query containing matched products. + + The response fields are all optional and are based on the projection if given. + """ + + products: List[QueryProductsResponse] + """A list of all the products in this page.""" + + total_count: Optional[int] + """The total number of products that match the query.""" diff --git a/nisystemlink/clients/product/models/_query_products_request.py b/nisystemlink/clients/product/models/_query_products_request.py index aca8b919..0f132aaa 100644 --- a/nisystemlink/clients/product/models/_query_products_request.py +++ b/nisystemlink/clients/product/models/_query_products_request.py @@ -6,13 +6,21 @@ class ProductField(str, Enum): - """The valid ways to order a product query.""" + """An enumeration of all fields in a Product. + + These are all the valid ways to order a product query. Also, this can be helpful in the + product result projection. + """ ID = "ID" FAMILY = "FAMILY" PART_NUMBER = "PART_NUMBER" NAME = "NAME" UPDATED_AT = "UPDATED_AT" + WORKSPACE = "WORKSPACE" + KEYWORDS = "KEYWORDS" + PROPERTIES = "PROPERTIES" + FILE_IDS = "FILE_IDS" class QueryProductsBase(JsonModel): @@ -61,11 +69,21 @@ class QueryProductsRequest(QueryProductsBase): By default, this value is `false` and products are sorted in ascending order. """ + + projection: Optional[List[ProductField]] = None + """Specifies the product fields to project. + + When a field value is given here, the corresponding field will be present in all returned products, + and all unspecified fields will be excluded. If no projection is specified, all product fields + will be returned. + """ + take: Optional[int] = None """Maximum number of products to return in the current API response. Uses the default if the specified value is negative. The default value is `1000` products. """ + continuation_token: Optional[str] = None """Allows users to continue the query at the next product that matches the given criteria. diff --git a/nisystemlink/clients/product/models/_query_products_response.py b/nisystemlink/clients/product/models/_query_products_response.py new file mode 100644 index 00000000..d4f2f393 --- /dev/null +++ b/nisystemlink/clients/product/models/_query_products_response.py @@ -0,0 +1,13 @@ +from typing import Optional + +from nisystemlink.clients.product.models._product import Product + + +class QueryProductsResponse(Product): + """This model extends the Product model and converts any non-optional fields into optional fields. + + This is because when we are using query products' projection, user can request for any of the available + fields. So, we are making sure that all the available fields are optional. + """ + + part_number: Optional[str] # type: ignore[assignment] diff --git a/nisystemlink/clients/product/utilities/_file_utilities.py b/nisystemlink/clients/product/utilities/_file_utilities.py index 70f8189b..942f6a5c 100644 --- a/nisystemlink/clients/product/utilities/_file_utilities.py +++ b/nisystemlink/clients/product/utilities/_file_utilities.py @@ -1,14 +1,20 @@ from typing import List from nisystemlink.clients.product._product_client import ProductClient -from nisystemlink.clients.product.models._paged_products import PagedProducts -from nisystemlink.clients.product.models._product import Product +from nisystemlink.clients.product.models._paged_products import ( + PagedQueryProductsResponse, +) from nisystemlink.clients.product.models._query_products_request import ( QueryProductsRequest, ) +from nisystemlink.clients.product.models._query_products_response import ( + QueryProductsResponse, +) -def get_products_linked_to_file(client: ProductClient, file_id: str) -> List[Product]: +def get_products_linked_to_file( + client: ProductClient, file_id: str +) -> List[QueryProductsResponse]: """Gets a list of all the products that are linked to the file. Args: @@ -21,7 +27,7 @@ def get_products_linked_to_file(client: ProductClient, file_id: str) -> List[Pro query_request = QueryProductsRequest( filter=f'fileIds.Contains("{file_id}")', take=100 ) - response: PagedProducts = client.query_products_paged(query_request) + response: PagedQueryProductsResponse = client.query_products_paged(query_request) products = response.products while response.continuation_token: query_request.continuation_token = response.continuation_token diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index e4b159e2..0bf85db4 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -8,7 +8,10 @@ CreateProductsPartialSuccess, Product, ) -from nisystemlink.clients.product.models._paged_products import PagedProducts +from nisystemlink.clients.product.models._paged_products import ( + PagedProducts, + PagedQueryProductsResponse, +) from nisystemlink.clients.product.models._query_products_request import ( ProductField, QueryProductsRequest, @@ -145,7 +148,9 @@ def test__query_product_by_part_number__matches_expected( query_request = QueryProductsRequest( filter=f'partNumber="{part_number}"', return_count=True ) - query_response: PagedProducts = client.query_products_paged(query_request) + query_response: PagedQueryProductsResponse = client.query_products_paged( + query_request + ) assert query_response.total_count == 1 assert query_response.products[0].part_number == part_number @@ -286,3 +291,40 @@ def test__query_products_linked_to_files_correct_products_returned( linked_products = get_products_linked_to_file(client, file_id) names = [product.name for product in linked_products] assert product_name_with_file in names + + def test__query_products_with_projection__returns_only_specified_fields( + self, client: ProductClient, create_products, unique_identifier + ): + part_number = unique_identifier + name = "Test Name" + family = "Example Family" + keywords = ["testing"] + properties = {"test_property": "yes"} + product = Product( + part_number=part_number, + name=name, + family=family, + keywords=keywords, + properties=properties, + ) + + response: CreateProductsPartialSuccess = create_products([product]) + assert response is not None + + query_request = QueryProductsRequest( + filter=f'partNumber=="{part_number}"', + projection=[ProductField.FAMILY, ProductField.NAME], + ) + query_response: PagedQueryProductsResponse = client.query_products_paged( + query_request + ) + queried_product = query_response.products[0] + + # Assert that the projected fields are returned as expected. + assert queried_product.family == family + assert queried_product.name == name + + # Assert that non-projected fields are not returned. + assert queried_product.part_number is None + assert queried_product.keywords is None + assert queried_product.properties is None From b6df853a45e4d0a46ad0941ec0fa5bc924677817 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Tue, 25 Feb 2025 10:57:07 +0530 Subject: [PATCH 04/24] refactor: update query products response model --- .../models/_query_products_response.py | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/nisystemlink/clients/product/models/_query_products_response.py b/nisystemlink/clients/product/models/_query_products_response.py index d4f2f393..865259b4 100644 --- a/nisystemlink/clients/product/models/_query_products_response.py +++ b/nisystemlink/clients/product/models/_query_products_response.py @@ -1,13 +1,49 @@ -from typing import Optional +from datetime import datetime +from typing import Dict, List, Optional -from nisystemlink.clients.product.models._product import Product +from nisystemlink.clients.core._uplink._json_model import JsonModel -class QueryProductsResponse(Product): - """This model extends the Product model and converts any non-optional fields into optional fields. +class QueryProductsResponse(JsonModel): + """Contains information about a product, where all the fields are optional. This is because when we are using query products' projection, user can request for any of the available fields. So, we are making sure that all the available fields are optional. """ - part_number: Optional[str] # type: ignore[assignment] + id: Optional[str] + """The globally unique id of the product.""" + + part_number: Optional[str] + """The part number is the unique identifier of a product within a single org. + + Usually the part number refers to a specific revision or version of a given product.""" + + name: Optional[str] + """The name of the product. + + Usually the name is used to refer to several part numbers that all have the same name but + different revisions or versions. + """ + + family: Optional[str] + """The family that that this product belongs to. + + Usually the family is a grouping above product name. A family usually has multiple product + names within it. + """ + + updated_at: Optional[datetime] + """The last time that this product was updated.""" + + file_ids: Optional[List[str]] + """A list of file ids that are attached to this product.""" + + keywords: Optional[List[str]] + """A list of keywords that categorize this product.""" + + properties: Optional[Dict[str, str]] + """A list of custom properties for this product.""" + + workspace: Optional[str] + """The id of the workspace that this product belongs to.""" From fee23c0aa93131b0199bba2181f0cc4a781008a6 Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Tue, 25 Feb 2025 11:59:55 +0530 Subject: [PATCH 05/24] feat:NewResponseModel --- examples/spec/update_and_delete_specs.py | 2 +- .../clients/spec/models/_query_specs.py | 74 ++++++++++++++++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/examples/spec/update_and_delete_specs.py b/examples/spec/update_and_delete_specs.py index 03c09a56..0cbc4c2f 100644 --- a/examples/spec/update_and_delete_specs.py +++ b/examples/spec/update_and_delete_specs.py @@ -56,4 +56,4 @@ # query all specs response = client.query_specs(QuerySpecificationsRequest(product_ids=[product])) if response.specs: - client.delete_specs(ids=[spec.id for spec in response.specs]) + client.delete_specs(ids=[spec.id for spec in response.specs if spec.id]) diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index fa4ba964..0f59591c 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -1,11 +1,13 @@ +from datetime import datetime from enum import Enum -from typing import List, Optional +from typing import Dict, List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging from nisystemlink.clients.spec.models._specification import ( + Condition, + SpecificationLimit, SpecificationType, - SpecificationWithHistory, ) @@ -106,27 +108,81 @@ class QuerySpecificationsRequest(JsonModel): """ -class SpecificationWithOptionalFields(SpecificationWithHistory): +class SpecificationWithOptionalFields(JsonModel): """A full specification with update and create history with only optional fields""" + id: Optional[str] = None + """The global Id of the specification.""" + + version: Optional[int] = None + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + product_id: Optional[str] = None """Id of the product to which the specification will be associated.""" spec_id: Optional[str] = None + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + + workspace: Optional[str] = None + """Id of the workspace to which the specification will be associated. + + Default workspace will be taken if the value is not given. + """ + + name: Optional[str] = None + """Name of the specification.""" + + category: Optional[str] = None + """Category of the specification.""" type: Optional[SpecificationType] = None """Type of the specification.""" - id: Optional[str] = None - """The global Id of the specification.""" + symbol: Optional[str] = None + """Short form identifier of the specification.""" - version: Optional[int] = None - """ - Current version of the specification. + block: Optional[str] = None + """Block name of the specification. - When an update is applied, the version is automatically incremented. + Typically a block is one of the subsystems of the overall product being specified. """ + limit: Optional[SpecificationLimit] = None + """The limits for this spec.""" + + unit: Optional[str] = None + """Unit of the specification.""" + + conditions: Optional[List[Condition]] = None + """Conditions associated with the specification.""" + + keywords: Optional[List[str]] = None + """Keywords or phrases associated with the specification.""" + + properties: Optional[Dict[str, str]] = None + """Additional properties associated with the specification.""" + + """When the spec was created and when.""" + + created_at: Optional[datetime] = None + """ISO-8601 formatted timestamp indicating when the specification was created.""" + + created_by: Optional[str] = None + """Id of the user who created the specification.""" + + updated_at: Optional[datetime] = None + """ISO-8601 formatted timestamp indicating when the specification was last updated.""" + + updated_by: Optional[str] = None + """Id of the user who last updated the specification.""" + class QuerySpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" From f94c805fe09820231422a5401ed91cb11f4de4f4 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Tue, 25 Feb 2025 15:20:15 +0530 Subject: [PATCH 06/24] refactor: include a common products response --- .../clients/product/_product_client.py | 2 +- .../clients/product/models/__init__.py | 7 ++----- .../clients/product/models/_paged_products.py | 20 ++----------------- ...ducts_response.py => _product_response.py} | 6 ++++-- .../product/utilities/_file_utilities.py | 12 +++++------ .../product/test_product_client.py | 9 ++------- 6 files changed, 17 insertions(+), 39 deletions(-) rename nisystemlink/clients/product/models/{_query_products_response.py => _product_response.py} (85%) diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index f8689bc4..de627c5e 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -91,7 +91,7 @@ def get_product(self, id: str) -> models.Product: @post("query-products") def query_products_paged( self, query: models.QueryProductsRequest - ) -> models.PagedQueryProductsResponse: + ) -> models.PagedProducts: """Queries for products that match the filter. Args: diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 9049f273..5b8e40d1 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -1,15 +1,12 @@ from ._product import Product from ._create_products_partial_success import CreateProductsPartialSuccess from ._delete_products_partial_success import DeleteProductsPartialSuccess -from ._paged_products import ( - PagedProducts, - PagedQueryProductsResponse, -) +from ._paged_products import PagedProducts from ._query_products_request import ( QueryProductsRequest, ProductField, QueryProductValuesRequest, ) -from ._query_products_response import QueryProductsResponse +from ._product_response import ProductResponse # flake8: noqa diff --git a/nisystemlink/clients/product/models/_paged_products.py b/nisystemlink/clients/product/models/_paged_products.py index 5f1decd5..9ad92017 100644 --- a/nisystemlink/clients/product/models/_paged_products.py +++ b/nisystemlink/clients/product/models/_paged_products.py @@ -1,10 +1,7 @@ from typing import List, Optional from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.product.models import Product -from nisystemlink.clients.product.models._query_products_response import ( - QueryProductsResponse, -) +from nisystemlink.clients.product.models._product_response import ProductResponse class PagedProducts(WithPaging): @@ -12,20 +9,7 @@ class PagedProducts(WithPaging): token if applicable. """ - 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.""" - - -class PagedQueryProductsResponse(WithPaging): - """The response for a Products query containing matched products. - - The response fields are all optional and are based on the projection if given. - """ - - products: List[QueryProductsResponse] + products: List[ProductResponse] """A list of all the products in this page.""" total_count: Optional[int] diff --git a/nisystemlink/clients/product/models/_query_products_response.py b/nisystemlink/clients/product/models/_product_response.py similarity index 85% rename from nisystemlink/clients/product/models/_query_products_response.py rename to nisystemlink/clients/product/models/_product_response.py index 865259b4..0f228fd3 100644 --- a/nisystemlink/clients/product/models/_query_products_response.py +++ b/nisystemlink/clients/product/models/_product_response.py @@ -4,11 +4,13 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class QueryProductsResponse(JsonModel): +class ProductResponse(JsonModel): """Contains information about a product, where all the fields are optional. - This is because when we are using query products' projection, user can request for any of the available + - This is because when we are using query products' projection, user can request for any of the available fields. So, we are making sure that all the available fields are optional. + + - Also while creating a product it is not mandatory that the product should be created with all the fields. """ id: Optional[str] diff --git a/nisystemlink/clients/product/utilities/_file_utilities.py b/nisystemlink/clients/product/utilities/_file_utilities.py index 942f6a5c..a901d0be 100644 --- a/nisystemlink/clients/product/utilities/_file_utilities.py +++ b/nisystemlink/clients/product/utilities/_file_utilities.py @@ -2,19 +2,19 @@ from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models._paged_products import ( - PagedQueryProductsResponse, + PagedProducts, +) +from nisystemlink.clients.product.models._product_response import ( + ProductResponse, ) from nisystemlink.clients.product.models._query_products_request import ( QueryProductsRequest, ) -from nisystemlink.clients.product.models._query_products_response import ( - QueryProductsResponse, -) def get_products_linked_to_file( client: ProductClient, file_id: str -) -> List[QueryProductsResponse]: +) -> List[ProductResponse]: """Gets a list of all the products that are linked to the file. Args: @@ -27,7 +27,7 @@ def get_products_linked_to_file( query_request = QueryProductsRequest( filter=f'fileIds.Contains("{file_id}")', take=100 ) - response: PagedQueryProductsResponse = client.query_products_paged(query_request) + response: PagedProducts = client.query_products_paged(query_request) products = response.products while response.continuation_token: query_request.continuation_token = response.continuation_token diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index 0bf85db4..13f80601 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -10,7 +10,6 @@ ) from nisystemlink.clients.product.models._paged_products import ( PagedProducts, - PagedQueryProductsResponse, ) from nisystemlink.clients.product.models._query_products_request import ( ProductField, @@ -148,9 +147,7 @@ def test__query_product_by_part_number__matches_expected( query_request = QueryProductsRequest( filter=f'partNumber="{part_number}"', return_count=True ) - query_response: PagedQueryProductsResponse = client.query_products_paged( - query_request - ) + query_response: PagedProducts = client.query_products_paged(query_request) assert query_response.total_count == 1 assert query_response.products[0].part_number == part_number @@ -315,9 +312,7 @@ def test__query_products_with_projection__returns_only_specified_fields( filter=f'partNumber=="{part_number}"', projection=[ProductField.FAMILY, ProductField.NAME], ) - query_response: PagedQueryProductsResponse = client.query_products_paged( - query_request - ) + query_response: PagedProducts = client.query_products_paged(query_request) queried_product = query_response.products[0] # Assert that the projected fields are returned as expected. From 256b80fa0bc6303f38988ca430ac5fb098da848b Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Tue, 25 Feb 2025 17:23:32 +0530 Subject: [PATCH 07/24] fix:DeleteUnwantedResponseModel --- nisystemlink/clients/spec/models/__init__.py | 1 - nisystemlink/clients/spec/models/_specification.py | 6 ------ 2 files changed, 7 deletions(-) diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index 7a7fff4b..f3168507 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -22,7 +22,6 @@ SpecificationType, SpecificationUpdated, SpecificationUserManaged, - SpecificationWithHistory, ) from ._update_specs_request import ( UpdatedSpecification, diff --git a/nisystemlink/clients/spec/models/_specification.py b/nisystemlink/clients/spec/models/_specification.py index 813612ae..a711a3ec 100644 --- a/nisystemlink/clients/spec/models/_specification.py +++ b/nisystemlink/clients/spec/models/_specification.py @@ -127,9 +127,3 @@ class SpecificationUpdated(JsonModel): updated_by: Optional[str] = None """Id of the user who last updated the specification.""" - - -class SpecificationWithHistory( - Specification, SpecificationCreation, SpecificationUpdated -): - """A full specification with update and create history.""" From c84db1603a49ed7cf9ec857fc81c160a77096662 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Tue, 25 Feb 2025 17:40:26 +0530 Subject: [PATCH 08/24] refactor: include unqiue models for product request and response --- examples/product/products.py | 6 +- .../clients/product/_product_client.py | 8 +-- .../clients/product/models/__init__.py | 2 +- .../_create_products_partial_success.py | 6 +- .../{_product.py => _product_request.py} | 2 +- .../product/test_product_client.py | 69 ++++++++++++------- 6 files changed, 56 insertions(+), 37 deletions(-) rename nisystemlink/clients/product/models/{_product.py => _product_request.py} (97%) diff --git a/examples/product/products.py b/examples/product/products.py index 9de6f9a7..d8b6ef8a 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -1,8 +1,8 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.product import ProductClient from nisystemlink.clients.product.models import ( - Product, ProductField, + ProductRequest, QueryProductsRequest, QueryProductValuesRequest, ) @@ -14,14 +14,14 @@ def create_some_products(): """Create two example products on your server.""" new_products = [ - Product( + ProductRequest( part_number="Example 123 AA", name=name, family=family, keywords=["original keyword"], properties={"original property key": "yes"}, ), - Product( + ProductRequest( part_number="Example 123 AA1", name=name, family=family, diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index de627c5e..a1c022b3 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -5,7 +5,7 @@ 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.product.models import Product +from nisystemlink.clients.product.models import ProductRequest from uplink import Field, Query, returns from . import models @@ -30,7 +30,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): @post("products", args=[Field("products")]) def create_products( - self, products: List[Product] + self, products: List[ProductRequest] ) -> models.CreateProductsPartialSuccess: """Creates one or more products and returns errors for failed creations. @@ -73,7 +73,7 @@ def get_products_paged( ... @get("products/{id}") - def get_product(self, id: str) -> models.Product: + def get_product(self, id: str) -> models.ProductResponse: """Retrieves a single product by id. Args: @@ -128,7 +128,7 @@ def query_product_values( @post("update-products", args=[Field("products"), Field("replace")]) def update_products( - self, products: List[Product], replace: bool = False + self, products: List[ProductRequest], replace: bool = False ) -> models.CreateProductsPartialSuccess: """Updates a list of products with optional field replacement. diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 5b8e40d1..2690502c 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -1,4 +1,4 @@ -from ._product import Product +from ._product_request import ProductRequest from ._create_products_partial_success import CreateProductsPartialSuccess from ._delete_products_partial_success import DeleteProductsPartialSuccess from ._paged_products import PagedProducts diff --git a/nisystemlink/clients/product/models/_create_products_partial_success.py b/nisystemlink/clients/product/models/_create_products_partial_success.py index f8eee6d7..8fc77d76 100644 --- a/nisystemlink/clients/product/models/_create_products_partial_success.py +++ b/nisystemlink/clients/product/models/_create_products_partial_success.py @@ -2,14 +2,14 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.product.models import Product +from nisystemlink.clients.product.models._product_response import ProductResponse class CreateProductsPartialSuccess(JsonModel): - products: List[Product] + products: List[ProductResponse] """The list of products that were successfully created.""" - failed: Optional[List[Product]] = None + failed: Optional[List[ProductResponse]] = None """The list of products that were not created. If this is `None`, then all products were successfully created. diff --git a/nisystemlink/clients/product/models/_product.py b/nisystemlink/clients/product/models/_product_request.py similarity index 97% rename from nisystemlink/clients/product/models/_product.py rename to nisystemlink/clients/product/models/_product_request.py index 23058d65..4b781b67 100644 --- a/nisystemlink/clients/product/models/_product.py +++ b/nisystemlink/clients/product/models/_product_request.py @@ -4,7 +4,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class Product(JsonModel): +class ProductRequest(JsonModel): """Contains information about a product.""" id: Optional[str] diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index 13f80601..4b66c882 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -6,7 +6,8 @@ from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models import ( CreateProductsPartialSuccess, - Product, + ProductRequest, + ProductResponse, ) from nisystemlink.clients.product.models._paged_products import ( PagedProducts, @@ -37,14 +38,16 @@ def create_products(client: ProductClient): """Fixture to return a factory that creates specs.""" responses: List[CreateProductsPartialSuccess] = [] - def _create_products(products: List[Product]) -> CreateProductsPartialSuccess: + def _create_products( + products: List[ProductRequest], + ) -> CreateProductsPartialSuccess: response = client.create_products(products) responses.append(response) return response yield _create_products - created_products: List[Product] = [] + created_products: List[ProductResponse] = [] for response in responses: if response.products: created_products = created_products + response.products @@ -63,7 +66,7 @@ def test__create_single_product__one_product_created_with_right_field_values( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = Product( + product = ProductRequest( part_number=part_number, name=name, family=family, @@ -85,8 +88,8 @@ def test__create_multiple_products__multiple_creates_succeed( self, client: ProductClient, create_products ): products = [ - Product(part_number=uuid.uuid1().hex), - Product(part_number=uuid.uuid1().hex), + ProductRequest(part_number=uuid.uuid1().hex), + ProductRequest(part_number=uuid.uuid1().hex), ] response: CreateProductsPartialSuccess = create_products(products) assert response is not None @@ -95,7 +98,7 @@ def test__create_multiple_products__multiple_creates_succeed( def test__create_single_product_and_get_products__at_least_one_product_exists( self, client: ProductClient, create_products, unique_identifier ): - products = [Product(part_number=unique_identifier)] + products = [ProductRequest(part_number=unique_identifier)] create_products(products) get_response = client.get_products_paged() assert get_response is not None @@ -105,8 +108,8 @@ def test__create_multiple_products_and_get_products_with_take__only_take_returne self, client: ProductClient, create_products, unique_identifier ): products = [ - Product(part_number=unique_identifier), - Product(part_number=unique_identifier), + ProductRequest(part_number=unique_identifier), + ProductRequest(part_number=unique_identifier), ] create_products(products) get_response = client.get_products_paged(take=1) @@ -117,8 +120,8 @@ def test__create_multiple_products_and_get_products_with_count_at_least_one_coun self, client: ProductClient, create_products, unique_identifier ): products = [ - Product(part_number=unique_identifier), - Product(part_number=unique_identifier), + ProductRequest(part_number=unique_identifier), + ProductRequest(part_number=unique_identifier), ] create_products(products) get_response: PagedProducts = client.get_products_paged(return_count=True) @@ -129,7 +132,7 @@ def test__get_product_by_id__product_matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [Product(part_number=part_number)] + products = [ProductRequest(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None id = str(create_response.products[0].id) @@ -141,7 +144,7 @@ def test__query_product_by_part_number__matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [Product(part_number=part_number)] + products = [ProductRequest(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None query_request = QueryProductsRequest( @@ -157,7 +160,7 @@ def test__query_product_values_for_name__name_matches( part_number = unique_identifier test_name = "query values test" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=part_number, name=test_name)] + [ProductRequest(part_number=part_number, name=test_name)] ) assert create_response is not None query_request = QueryProductValuesRequest( @@ -174,13 +177,15 @@ def test__update_keywords_with_replace__keywords_replaced( original_keyword = "originalKeyword" updated_keyword = "updatedKeyword" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, keywords=[original_keyword])] + [ProductRequest(part_number=unique_identifier, keywords=[original_keyword])] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.keywords = [updated_keyword] - update_response = client.update_products([updated_product], replace=True) + update_response = client.update_products( + [ProductRequest(**updated_product.dict())], replace=True + ) assert update_response is not None assert len(update_response.products) == 1 assert ( @@ -195,13 +200,15 @@ def test__update_keywords_no_replace__keywords_appended( original_keyword = "originalKeyword" additional_keyword = "additionalKeyword" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, keywords=[original_keyword])] + [ProductRequest(part_number=unique_identifier, keywords=[original_keyword])] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.keywords = [additional_keyword] - update_response = client.update_products([updated_product], replace=False) + update_response = client.update_products( + [ProductRequest(**updated_product.dict())], replace=False + ) assert update_response is not None assert len(update_response.products) == 1 assert ( @@ -220,13 +227,19 @@ def test__update_properties_with_replace__properties_replaced( original_properties = {"originalKey": "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, properties=original_properties)] + [ + ProductRequest( + part_number=unique_identifier, properties=original_properties + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.properties = new_properties - update_response = client.update_products([updated_product], replace=True) + update_response = client.update_products( + [ProductRequest(**updated_product.dict())], replace=True + ) assert update_response is not None assert len(update_response.products) == 1 assert ( @@ -246,13 +259,19 @@ def test__update_properties_append__properties_appended( original_properties = {original_key: "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, properties=original_properties)] + [ + ProductRequest( + part_number=unique_identifier, properties=original_properties + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.properties = new_properties - update_response = client.update_products([updated_product], replace=False) + update_response = client.update_products( + [ProductRequest(**updated_product.dict())], replace=False + ) assert update_response is not None assert len(update_response.products) == 1 updated_product = update_response.products[0] @@ -274,12 +293,12 @@ def test__query_products_linked_to_files_correct_products_returned( file_id = uuid.uuid1().hex product_name_with_file = "Has File" products = [ - Product( + ProductRequest( part_number=uuid.uuid1().hex, name=product_name_with_file, file_ids=[file_id], ), - Product(part_number=uuid.uuid1().hex, name="No File Link"), + ProductRequest(part_number=uuid.uuid1().hex, name="No File Link"), ] print(products) create_response: CreateProductsPartialSuccess = create_products(products) @@ -297,7 +316,7 @@ def test__query_products_with_projection__returns_only_specified_fields( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = Product( + product = ProductRequest( part_number=part_number, name=name, family=family, From 06118e044177f521a5b7d644d6b767396fb19509 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Wed, 26 Feb 2025 12:00:02 +0530 Subject: [PATCH 09/24] refactor: change from ProductRequest to Product --- examples/product/products.py | 6 +- .../clients/product/_product_client.py | 6 +- .../clients/product/models/__init__.py | 2 +- .../{_product_request.py => _product.py} | 2 +- .../product/utilities/_file_utilities.py | 4 +- .../product/test_product_client.py | 62 ++++++++----------- 6 files changed, 34 insertions(+), 48 deletions(-) rename nisystemlink/clients/product/models/{_product_request.py => _product.py} (97%) diff --git a/examples/product/products.py b/examples/product/products.py index d8b6ef8a..9de6f9a7 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -1,8 +1,8 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.product import ProductClient from nisystemlink.clients.product.models import ( + Product, ProductField, - ProductRequest, QueryProductsRequest, QueryProductValuesRequest, ) @@ -14,14 +14,14 @@ def create_some_products(): """Create two example products on your server.""" new_products = [ - ProductRequest( + Product( part_number="Example 123 AA", name=name, family=family, keywords=["original keyword"], properties={"original property key": "yes"}, ), - ProductRequest( + Product( part_number="Example 123 AA1", name=name, family=family, diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index a1c022b3..c3405b91 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -5,7 +5,7 @@ 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.product.models import ProductRequest +from nisystemlink.clients.product.models import Product from uplink import Field, Query, returns from . import models @@ -30,7 +30,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): @post("products", args=[Field("products")]) def create_products( - self, products: List[ProductRequest] + self, products: List[Product] ) -> models.CreateProductsPartialSuccess: """Creates one or more products and returns errors for failed creations. @@ -128,7 +128,7 @@ def query_product_values( @post("update-products", args=[Field("products"), Field("replace")]) def update_products( - self, products: List[ProductRequest], replace: bool = False + self, products: List[Product], replace: bool = False ) -> models.CreateProductsPartialSuccess: """Updates a list of products with optional field replacement. diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 2690502c..5b8e40d1 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -1,4 +1,4 @@ -from ._product_request import ProductRequest +from ._product import Product from ._create_products_partial_success import CreateProductsPartialSuccess from ._delete_products_partial_success import DeleteProductsPartialSuccess from ._paged_products import PagedProducts diff --git a/nisystemlink/clients/product/models/_product_request.py b/nisystemlink/clients/product/models/_product.py similarity index 97% rename from nisystemlink/clients/product/models/_product_request.py rename to nisystemlink/clients/product/models/_product.py index 4b781b67..23058d65 100644 --- a/nisystemlink/clients/product/models/_product_request.py +++ b/nisystemlink/clients/product/models/_product.py @@ -4,7 +4,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class ProductRequest(JsonModel): +class Product(JsonModel): """Contains information about a product.""" id: Optional[str] diff --git a/nisystemlink/clients/product/utilities/_file_utilities.py b/nisystemlink/clients/product/utilities/_file_utilities.py index a901d0be..2c6f0a58 100644 --- a/nisystemlink/clients/product/utilities/_file_utilities.py +++ b/nisystemlink/clients/product/utilities/_file_utilities.py @@ -1,9 +1,7 @@ from typing import List from nisystemlink.clients.product._product_client import ProductClient -from nisystemlink.clients.product.models._paged_products import ( - PagedProducts, -) +from nisystemlink.clients.product.models._paged_products import PagedProducts from nisystemlink.clients.product.models._product_response import ( ProductResponse, ) diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index 4b66c882..680ee9ce 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -6,12 +6,10 @@ from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models import ( CreateProductsPartialSuccess, - ProductRequest, + Product, ProductResponse, ) -from nisystemlink.clients.product.models._paged_products import ( - PagedProducts, -) +from nisystemlink.clients.product.models._paged_products import PagedProducts from nisystemlink.clients.product.models._query_products_request import ( ProductField, QueryProductsRequest, @@ -38,9 +36,7 @@ def create_products(client: ProductClient): """Fixture to return a factory that creates specs.""" responses: List[CreateProductsPartialSuccess] = [] - def _create_products( - products: List[ProductRequest], - ) -> CreateProductsPartialSuccess: + def _create_products(products: List[Product]) -> CreateProductsPartialSuccess: response = client.create_products(products) responses.append(response) return response @@ -66,7 +62,7 @@ def test__create_single_product__one_product_created_with_right_field_values( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = ProductRequest( + product = Product( part_number=part_number, name=name, family=family, @@ -88,8 +84,8 @@ def test__create_multiple_products__multiple_creates_succeed( self, client: ProductClient, create_products ): products = [ - ProductRequest(part_number=uuid.uuid1().hex), - ProductRequest(part_number=uuid.uuid1().hex), + Product(part_number=uuid.uuid1().hex), + Product(part_number=uuid.uuid1().hex), ] response: CreateProductsPartialSuccess = create_products(products) assert response is not None @@ -98,7 +94,7 @@ def test__create_multiple_products__multiple_creates_succeed( def test__create_single_product_and_get_products__at_least_one_product_exists( self, client: ProductClient, create_products, unique_identifier ): - products = [ProductRequest(part_number=unique_identifier)] + products = [Product(part_number=unique_identifier)] create_products(products) get_response = client.get_products_paged() assert get_response is not None @@ -108,8 +104,8 @@ def test__create_multiple_products_and_get_products_with_take__only_take_returne self, client: ProductClient, create_products, unique_identifier ): products = [ - ProductRequest(part_number=unique_identifier), - ProductRequest(part_number=unique_identifier), + Product(part_number=unique_identifier), + Product(part_number=unique_identifier), ] create_products(products) get_response = client.get_products_paged(take=1) @@ -120,8 +116,8 @@ def test__create_multiple_products_and_get_products_with_count_at_least_one_coun self, client: ProductClient, create_products, unique_identifier ): products = [ - ProductRequest(part_number=unique_identifier), - ProductRequest(part_number=unique_identifier), + Product(part_number=unique_identifier), + Product(part_number=unique_identifier), ] create_products(products) get_response: PagedProducts = client.get_products_paged(return_count=True) @@ -132,7 +128,7 @@ def test__get_product_by_id__product_matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [ProductRequest(part_number=part_number)] + products = [Product(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None id = str(create_response.products[0].id) @@ -144,7 +140,7 @@ def test__query_product_by_part_number__matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [ProductRequest(part_number=part_number)] + products = [Product(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None query_request = QueryProductsRequest( @@ -160,7 +156,7 @@ def test__query_product_values_for_name__name_matches( part_number = unique_identifier test_name = "query values test" create_response: CreateProductsPartialSuccess = create_products( - [ProductRequest(part_number=part_number, name=test_name)] + [Product(part_number=part_number, name=test_name)] ) assert create_response is not None query_request = QueryProductValuesRequest( @@ -177,14 +173,14 @@ def test__update_keywords_with_replace__keywords_replaced( original_keyword = "originalKeyword" updated_keyword = "updatedKeyword" create_response: CreateProductsPartialSuccess = create_products( - [ProductRequest(part_number=unique_identifier, keywords=[original_keyword])] + [Product(part_number=unique_identifier, keywords=[original_keyword])] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.keywords = [updated_keyword] update_response = client.update_products( - [ProductRequest(**updated_product.dict())], replace=True + [Product(**updated_product.dict())], replace=True ) assert update_response is not None assert len(update_response.products) == 1 @@ -200,14 +196,14 @@ def test__update_keywords_no_replace__keywords_appended( original_keyword = "originalKeyword" additional_keyword = "additionalKeyword" create_response: CreateProductsPartialSuccess = create_products( - [ProductRequest(part_number=unique_identifier, keywords=[original_keyword])] + [Product(part_number=unique_identifier, keywords=[original_keyword])] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.keywords = [additional_keyword] update_response = client.update_products( - [ProductRequest(**updated_product.dict())], replace=False + [Product(**updated_product.dict())], replace=False ) assert update_response is not None assert len(update_response.products) == 1 @@ -227,18 +223,14 @@ def test__update_properties_with_replace__properties_replaced( original_properties = {"originalKey": "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [ - ProductRequest( - part_number=unique_identifier, properties=original_properties - ) - ] + [Product(part_number=unique_identifier, properties=original_properties)] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.properties = new_properties update_response = client.update_products( - [ProductRequest(**updated_product.dict())], replace=True + [Product(**updated_product.dict())], replace=True ) assert update_response is not None assert len(update_response.products) == 1 @@ -259,18 +251,14 @@ def test__update_properties_append__properties_appended( original_properties = {original_key: "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [ - ProductRequest( - part_number=unique_identifier, properties=original_properties - ) - ] + [Product(part_number=unique_identifier, properties=original_properties)] ) assert create_response is not None assert len(create_response.products) == 1 updated_product = create_response.products[0] updated_product.properties = new_properties update_response = client.update_products( - [ProductRequest(**updated_product.dict())], replace=False + [Product(**updated_product.dict())], replace=False ) assert update_response is not None assert len(update_response.products) == 1 @@ -293,12 +281,12 @@ def test__query_products_linked_to_files_correct_products_returned( file_id = uuid.uuid1().hex product_name_with_file = "Has File" products = [ - ProductRequest( + Product( part_number=uuid.uuid1().hex, name=product_name_with_file, file_ids=[file_id], ), - ProductRequest(part_number=uuid.uuid1().hex, name="No File Link"), + Product(part_number=uuid.uuid1().hex, name="No File Link"), ] print(products) create_response: CreateProductsPartialSuccess = create_products(products) @@ -316,7 +304,7 @@ def test__query_products_with_projection__returns_only_specified_fields( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = ProductRequest( + product = Product( part_number=part_number, name=name, family=family, From b619beb52ac411c57de6ed7712cbc7af8fbf1e94 Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Wed, 26 Feb 2025 12:14:40 +0530 Subject: [PATCH 10/24] fix:PRComments --- .../clients/spec/models/_query_specs.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 0f59591c..7a6b8330 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -1,4 +1,3 @@ -from datetime import datetime from enum import Enum from typing import Dict, List, Optional @@ -6,8 +5,10 @@ from nisystemlink.clients.core._uplink._with_paging import WithPaging from nisystemlink.clients.spec.models._specification import ( Condition, + SpecificationCreation, SpecificationLimit, SpecificationType, + SpecificationUpdated, ) @@ -108,7 +109,7 @@ class QuerySpecificationsRequest(JsonModel): """ -class SpecificationWithOptionalFields(JsonModel): +class SpecificationWithOptionalFields(SpecificationCreation, SpecificationUpdated): """A full specification with update and create history with only optional fields""" id: Optional[str] = None @@ -171,18 +172,6 @@ class SpecificationWithOptionalFields(JsonModel): """When the spec was created and when.""" - created_at: Optional[datetime] = None - """ISO-8601 formatted timestamp indicating when the specification was created.""" - - created_by: Optional[str] = None - """Id of the user who created the specification.""" - - updated_at: Optional[datetime] = None - """ISO-8601 formatted timestamp indicating when the specification was last updated.""" - - updated_by: Optional[str] = None - """Id of the user who last updated the specification.""" - class QuerySpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" From 10714adea46eead9f7fec6533fae1434ffc9b89f Mon Sep 17 00:00:00 2001 From: Sam rishi Date: Wed, 26 Feb 2025 17:34:06 +0530 Subject: [PATCH 11/24] fix:PRComments --- nisystemlink/clients/spec/models/__init__.py | 7 ++++++- tests/integration/spec/test_spec.py | 9 +++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index f3168507..6a13942a 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -12,7 +12,12 @@ CreateSpecificationsRequest, ) from ._delete_specs_request import DeleteSpecificationsPartialSuccess -from ._query_specs import QuerySpecificationsRequest, QuerySpecifications +from ._query_specs import ( + Projection, + QuerySpecificationsRequest, + QuerySpecifications, + SpecificationWithOptionalFields, +) from ._specification import ( Specification, SpecificationCreation, diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index ed5055d1..1f3a38cf 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -12,6 +12,7 @@ CreateSpecificationsPartialSuccess, CreateSpecificationsRequest, NumericConditionValue, + Projection, QuerySpecificationsRequest, Specification, SpecificationDefinition, @@ -281,15 +282,15 @@ def test__query_spec_projection_columns__columns_returned( self, client: SpecClient, create_specs, create_specs_for_query, product ): request = QuerySpecificationsRequest( - product_ids=[product], projection=["SPEC_ID", "NAME"] + product_ids=[product], projection=[Projection.SPEC_ID, Projection.NAME] ) response = client.query_specs(request) assert response.specs assert len(response.specs) == 3 specs = [vars(spec) for spec in response.specs] - spec_columns = list( - set(key for spec in specs for key in spec.keys() if spec[key] is not None) - ) + spec_columns = { + key for spec in specs for key in spec.keys() if spec[key] is not None + } assert len(spec_columns) == 2 assert "spec_id" in spec_columns assert "name" in spec_columns From 3cf7a28c234a399741a6f746e9462d994d3d1c98 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Wed, 26 Feb 2025 18:22:44 +0530 Subject: [PATCH 12/24] refactor: add separate projection and order-by enums and add update request model --- examples/product/products.py | 7 ++- .../clients/product/_product_client.py | 2 +- .../clients/product/models/__init__.py | 4 +- .../_create_products_partial_success.py | 5 +- .../clients/product/models/_product.py | 7 --- .../product/models/_product_response.py | 2 +- .../product/models/_query_products_request.py | 22 ++++--- .../product/models/_update_product_request.py | 12 ++++ .../product/test_product_client.py | 61 +++++++++++++++---- 9 files changed, 87 insertions(+), 35 deletions(-) create mode 100644 nisystemlink/clients/product/models/_update_product_request.py diff --git a/examples/product/products.py b/examples/product/products.py index 9de6f9a7..c4f5a28c 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -1,8 +1,9 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.product import ProductClient from nisystemlink.clients.product.models import ( + OrderByFields, Product, - ProductField, + ProjectionFields, QueryProductsRequest, QueryProductValuesRequest, ) @@ -58,7 +59,7 @@ def create_some_products(): query_request = QueryProductsRequest( filter=f'family="{family}" && name="{name}"', return_count=True, - order_by=ProductField.FAMILY, + order_by=OrderByFields.FAMILY, ) query_response = client.query_products_paged(query_request) @@ -70,7 +71,7 @@ def create_some_products(): # Query for just the ids of products that match the family values_query = QueryProductValuesRequest( - filter=f'family="{family}"', field=ProductField.ID + filter=f'family="{family}"', field=ProjectionFields.ID ) values_response = client.query_product_values(query=values_query) diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index c3405b91..93466f6e 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -128,7 +128,7 @@ def query_product_values( @post("update-products", args=[Field("products"), Field("replace")]) def update_products( - self, products: List[Product], replace: bool = False + self, products: List[models.UpdateProductRequest], replace: bool = False ) -> models.CreateProductsPartialSuccess: """Updates a list of products with optional field replacement. diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 5b8e40d1..4c41b995 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -3,10 +3,12 @@ from ._delete_products_partial_success import DeleteProductsPartialSuccess from ._paged_products import PagedProducts from ._query_products_request import ( + OrderByFields, + ProjectionFields, QueryProductsRequest, - ProductField, QueryProductValuesRequest, ) from ._product_response import ProductResponse +from ._update_product_request import UpdateProductRequest # flake8: noqa diff --git a/nisystemlink/clients/product/models/_create_products_partial_success.py b/nisystemlink/clients/product/models/_create_products_partial_success.py index 8fc77d76..45d9d0ab 100644 --- a/nisystemlink/clients/product/models/_create_products_partial_success.py +++ b/nisystemlink/clients/product/models/_create_products_partial_success.py @@ -2,6 +2,7 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.product.models._product import Product from nisystemlink.clients.product.models._product_response import ProductResponse @@ -9,13 +10,13 @@ class CreateProductsPartialSuccess(JsonModel): products: List[ProductResponse] """The list of products that were successfully created.""" - failed: Optional[List[ProductResponse]] = None + failed: Optional[List[Product]] """The list of products that were not created. If this is `None`, then all products were successfully created. """ - error: Optional[ApiError] = None + error: Optional[ApiError] """Error messages for products that were not created. If this is `None`, then all products were successfully created. diff --git a/nisystemlink/clients/product/models/_product.py b/nisystemlink/clients/product/models/_product.py index 23058d65..e38d11f5 100644 --- a/nisystemlink/clients/product/models/_product.py +++ b/nisystemlink/clients/product/models/_product.py @@ -1,4 +1,3 @@ -from datetime import datetime from typing import Dict, List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel @@ -7,9 +6,6 @@ class Product(JsonModel): """Contains information about a product.""" - id: Optional[str] - """The globally unique id of the product.""" - part_number: str """The part number is the unique identifier of a product within a single org. @@ -29,9 +25,6 @@ class Product(JsonModel): names within it. """ - updated_at: Optional[datetime] - """The last time that this product was updated.""" - file_ids: Optional[List[str]] """A list of file ids that are attached to this product.""" diff --git a/nisystemlink/clients/product/models/_product_response.py b/nisystemlink/clients/product/models/_product_response.py index 0f228fd3..99a989f7 100644 --- a/nisystemlink/clients/product/models/_product_response.py +++ b/nisystemlink/clients/product/models/_product_response.py @@ -7,7 +7,7 @@ class ProductResponse(JsonModel): """Contains information about a product, where all the fields are optional. - - This is because when we are using query products' projection, user can request for any of the available + - This is because when using query products' projection, user can request for any of the available fields. So, we are making sure that all the available fields are optional. - Also while creating a product it is not mandatory that the product should be created with all the fields. diff --git a/nisystemlink/clients/product/models/_query_products_request.py b/nisystemlink/clients/product/models/_query_products_request.py index 0f132aaa..3889e1c7 100644 --- a/nisystemlink/clients/product/models/_query_products_request.py +++ b/nisystemlink/clients/product/models/_query_products_request.py @@ -5,11 +5,19 @@ from pydantic import Field -class ProductField(str, Enum): - """An enumeration of all fields in a Product. +class OrderByFields(str, Enum): + """The valid ways to order a product query.""" - These are all the valid ways to order a product query. Also, this can be helpful in the - product result projection. + ID = "ID" + FAMILY = "FAMILY" + PART_NUMBER = "PART_NUMBER" + NAME = "NAME" + UPDATED_AT = "UPDATED_AT" + + +class ProjectionFields(str, Enum): + """An enumeration of all fields in a Product. These are used to project the required fields + from the API response. """ ID = "ID" @@ -58,7 +66,7 @@ class QueryProductsBase(JsonModel): class QueryProductsRequest(QueryProductsBase): - order_by: Optional[ProductField] = Field(None, alias="orderBy") + order_by: Optional[OrderByFields] = Field(None, alias="orderBy") """Specifies the fields to use to sort the products. By default, products are sorted by `id` @@ -70,7 +78,7 @@ class QueryProductsRequest(QueryProductsBase): By default, this value is `false` and products are sorted in ascending order. """ - projection: Optional[List[ProductField]] = None + projection: Optional[List[ProjectionFields]] = None """Specifies the product fields to project. When a field value is given here, the corresponding field will be present in all returned products, @@ -102,7 +110,7 @@ class QueryProductsRequest(QueryProductsBase): class QueryProductValuesRequest(QueryProductsBase): - field: Optional[ProductField] = None + field: Optional[ProjectionFields] = None """The product field to return for this query.""" starts_with: Optional[str] = None diff --git a/nisystemlink/clients/product/models/_update_product_request.py b/nisystemlink/clients/product/models/_update_product_request.py new file mode 100644 index 00000000..2e37390d --- /dev/null +++ b/nisystemlink/clients/product/models/_update_product_request.py @@ -0,0 +1,12 @@ +from nisystemlink.clients.product.models._product import Product + + +class UpdateProductRequest(Product): + """This is the request model to update a product. + + It extends the Product model, which is generally used for creating a product. Besides, `id` is added + as a field here. This is to infer which product to be updated. + """ + + id: str + """The globally unique id of the product.""" diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index 680ee9ce..cc3370af 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -8,10 +8,11 @@ CreateProductsPartialSuccess, Product, ProductResponse, + ProjectionFields, + UpdateProductRequest, ) from nisystemlink.clients.product.models._paged_products import PagedProducts from nisystemlink.clients.product.models._query_products_request import ( - ProductField, QueryProductsRequest, QueryProductValuesRequest, ) @@ -31,6 +32,22 @@ def unique_identifier() -> str: return product_id +@pytest.fixture +def create_update_product_request(): + """Fixture to create a request object for updating products, from a product response""" + + def _create_update_product_request( + product_response: ProductResponse, + ) -> UpdateProductRequest: + product_data = product_response.dict() + + product_data.pop("updated_at", None) + + return UpdateProductRequest(**product_data) + + return _create_update_product_request + + @pytest.fixture def create_products(client: ProductClient): """Fixture to return a factory that creates specs.""" @@ -160,7 +177,7 @@ def test__query_product_values_for_name__name_matches( ) assert create_response is not None query_request = QueryProductValuesRequest( - filter=f'partNumber="{part_number}"', field=ProductField.NAME + filter=f'partNumber="{part_number}"', field=ProjectionFields.NAME ) query_response: List[str] = client.query_product_values(query_request) assert query_response is not None @@ -168,7 +185,11 @@ def test__query_product_values_for_name__name_matches( assert query_response[0] == test_name def test__update_keywords_with_replace__keywords_replaced( - self, client: ProductClient, create_products, unique_identifier + self, + client: ProductClient, + create_products, + unique_identifier, + create_update_product_request, ): original_keyword = "originalKeyword" updated_keyword = "updatedKeyword" @@ -180,7 +201,8 @@ def test__update_keywords_with_replace__keywords_replaced( updated_product = create_response.products[0] updated_product.keywords = [updated_keyword] update_response = client.update_products( - [Product(**updated_product.dict())], replace=True + [create_update_product_request(product_response=updated_product)], + replace=True, ) assert update_response is not None assert len(update_response.products) == 1 @@ -191,7 +213,11 @@ def test__update_keywords_with_replace__keywords_replaced( assert original_keyword not in update_response.products[0].keywords def test__update_keywords_no_replace__keywords_appended( - self, client: ProductClient, create_products, unique_identifier + self, + client: ProductClient, + create_products, + unique_identifier, + create_update_product_request, ): original_keyword = "originalKeyword" additional_keyword = "additionalKeyword" @@ -203,7 +229,8 @@ def test__update_keywords_no_replace__keywords_appended( updated_product = create_response.products[0] updated_product.keywords = [additional_keyword] update_response = client.update_products( - [Product(**updated_product.dict())], replace=False + [create_update_product_request(product_response=updated_product)], + replace=False, ) assert update_response is not None assert len(update_response.products) == 1 @@ -217,7 +244,11 @@ def test__update_keywords_no_replace__keywords_appended( ) def test__update_properties_with_replace__properties_replaced( - self, client: ProductClient, create_products, unique_identifier + self, + client: ProductClient, + create_products, + unique_identifier, + create_update_product_request, ): new_key = "newKey" original_properties = {"originalKey": "originalValue"} @@ -230,7 +261,8 @@ def test__update_properties_with_replace__properties_replaced( updated_product = create_response.products[0] updated_product.properties = new_properties update_response = client.update_products( - [Product(**updated_product.dict())], replace=True + [create_update_product_request(product_response=updated_product)], + replace=True, ) assert update_response is not None assert len(update_response.products) == 1 @@ -244,7 +276,11 @@ def test__update_properties_with_replace__properties_replaced( ) def test__update_properties_append__properties_appended( - self, client: ProductClient, create_products, unique_identifier + self, + client: ProductClient, + create_products, + unique_identifier, + create_update_product_request, ): original_key = "originalKey" new_key = "newKey" @@ -258,7 +294,8 @@ def test__update_properties_append__properties_appended( updated_product = create_response.products[0] updated_product.properties = new_properties update_response = client.update_products( - [Product(**updated_product.dict())], replace=False + [create_update_product_request(product_response=updated_product)], + replace=False, ) assert update_response is not None assert len(update_response.products) == 1 @@ -311,13 +348,12 @@ def test__query_products_with_projection__returns_only_specified_fields( keywords=keywords, properties=properties, ) - response: CreateProductsPartialSuccess = create_products([product]) assert response is not None query_request = QueryProductsRequest( filter=f'partNumber=="{part_number}"', - projection=[ProductField.FAMILY, ProductField.NAME], + projection=[ProjectionFields.FAMILY, ProjectionFields.NAME], ) query_response: PagedProducts = client.query_products_paged(query_request) queried_product = query_response.products[0] @@ -325,7 +361,6 @@ def test__query_products_with_projection__returns_only_specified_fields( # Assert that the projected fields are returned as expected. assert queried_product.family == family assert queried_product.name == name - # Assert that non-projected fields are not returned. assert queried_product.part_number is None assert queried_product.keywords is None From fb9653ad68c717cd2ef622775dbbb6114105a6d9 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Wed, 26 Feb 2025 19:58:26 +0530 Subject: [PATCH 13/24] refactor: update request models and product enums --- examples/product/products.py | 14 ++-- .../clients/product/_product_client.py | 3 +- .../clients/product/models/__init__.py | 8 +-- .../_create_products_partial_success.py | 4 +- .../{_product.py => _product_request.py} | 22 +++++-- .../product/models/_query_products_request.py | 20 ++++-- .../product/models/_update_product_request.py | 12 ---- .../product/test_product_client.py | 66 ++++++++++++------- 8 files changed, 88 insertions(+), 61 deletions(-) rename nisystemlink/clients/product/models/{_product.py => _product_request.py} (81%) delete mode 100644 nisystemlink/clients/product/models/_update_product_request.py diff --git a/examples/product/products.py b/examples/product/products.py index c4f5a28c..46258b03 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -1,9 +1,9 @@ from nisystemlink.clients.core import HttpConfiguration from nisystemlink.clients.product import ProductClient from nisystemlink.clients.product.models import ( - OrderByFields, - Product, - ProjectionFields, + CreateProductRequest, + ProductOrderBy, + ProductProjection, QueryProductsRequest, QueryProductValuesRequest, ) @@ -15,14 +15,14 @@ def create_some_products(): """Create two example products on your server.""" new_products = [ - Product( + CreateProductRequest( part_number="Example 123 AA", name=name, family=family, keywords=["original keyword"], properties={"original property key": "yes"}, ), - Product( + CreateProductRequest( part_number="Example 123 AA1", name=name, family=family, @@ -59,7 +59,7 @@ def create_some_products(): query_request = QueryProductsRequest( filter=f'family="{family}" && name="{name}"', return_count=True, - order_by=OrderByFields.FAMILY, + order_by=ProductOrderBy.FAMILY, ) query_response = client.query_products_paged(query_request) @@ -71,7 +71,7 @@ def create_some_products(): # Query for just the ids of products that match the family values_query = QueryProductValuesRequest( - filter=f'family="{family}"', field=ProjectionFields.ID + filter=f'family="{family}"', field=ProductProjection.ID ) values_response = client.query_product_values(query=values_query) diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index 93466f6e..385e075b 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -5,7 +5,6 @@ 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.product.models import Product from uplink import Field, Query, returns from . import models @@ -30,7 +29,7 @@ def __init__(self, configuration: Optional[core.HttpConfiguration] = None): @post("products", args=[Field("products")]) def create_products( - self, products: List[Product] + self, products: List[models.CreateProductRequest] ) -> models.CreateProductsPartialSuccess: """Creates one or more products and returns errors for failed creations. diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 4c41b995..152daead 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -1,14 +1,14 @@ -from ._product import Product from ._create_products_partial_success import CreateProductsPartialSuccess from ._delete_products_partial_success import DeleteProductsPartialSuccess from ._paged_products import PagedProducts from ._query_products_request import ( - OrderByFields, - ProjectionFields, + ProductField, + ProductOrderBy, + ProductProjection, QueryProductsRequest, QueryProductValuesRequest, ) from ._product_response import ProductResponse -from ._update_product_request import UpdateProductRequest +from ._product_request import CreateProductRequest, UpdateProductRequest # flake8: noqa diff --git a/nisystemlink/clients/product/models/_create_products_partial_success.py b/nisystemlink/clients/product/models/_create_products_partial_success.py index 45d9d0ab..d7f6b32b 100644 --- a/nisystemlink/clients/product/models/_create_products_partial_success.py +++ b/nisystemlink/clients/product/models/_create_products_partial_success.py @@ -2,7 +2,7 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.product.models._product import Product +from nisystemlink.clients.product.models._product_request import CreateProductRequest from nisystemlink.clients.product.models._product_response import ProductResponse @@ -10,7 +10,7 @@ class CreateProductsPartialSuccess(JsonModel): products: List[ProductResponse] """The list of products that were successfully created.""" - failed: Optional[List[Product]] + failed: Optional[List[CreateProductRequest]] """The list of products that were not created. If this is `None`, then all products were successfully created. diff --git a/nisystemlink/clients/product/models/_product.py b/nisystemlink/clients/product/models/_product_request.py similarity index 81% rename from nisystemlink/clients/product/models/_product.py rename to nisystemlink/clients/product/models/_product_request.py index e38d11f5..b2d42255 100644 --- a/nisystemlink/clients/product/models/_product.py +++ b/nisystemlink/clients/product/models/_product_request.py @@ -3,14 +3,9 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class Product(JsonModel): +class BaseProductRequest(JsonModel): """Contains information about a product.""" - part_number: str - """The part number is the unique identifier of a product within a single org. - - Usually the part number refers to a specific revision or version of a given product.""" - name: Optional[str] """The name of the product. @@ -36,3 +31,18 @@ class Product(JsonModel): workspace: Optional[str] """The id of the workspace that this product belongs to.""" + + +class CreateProductRequest(BaseProductRequest): + + part_number: str + """The part number is the unique identifier of a product within a single org. + + Usually the part number refers to a specific revision or version of a given product.""" + + +class UpdateProductRequest(BaseProductRequest): + """This is the request model to update a product.""" + + id: str + """The globally unique id of the product.""" diff --git a/nisystemlink/clients/product/models/_query_products_request.py b/nisystemlink/clients/product/models/_query_products_request.py index 3889e1c7..2c759b1f 100644 --- a/nisystemlink/clients/product/models/_query_products_request.py +++ b/nisystemlink/clients/product/models/_query_products_request.py @@ -5,7 +5,7 @@ from pydantic import Field -class OrderByFields(str, Enum): +class ProductOrderBy(str, Enum): """The valid ways to order a product query.""" ID = "ID" @@ -15,7 +15,17 @@ class OrderByFields(str, Enum): UPDATED_AT = "UPDATED_AT" -class ProjectionFields(str, Enum): +class ProductField(str, Enum): + """An enumeration of product fields for which the values can be queried for.""" + + ID = "ID" + FAMILY = "FAMILY" + PART_NUMBER = "PART_NUMBER" + NAME = "NAME" + UPDATED_AT = "UPDATED_AT" + + +class ProductProjection(str, Enum): """An enumeration of all fields in a Product. These are used to project the required fields from the API response. """ @@ -66,7 +76,7 @@ class QueryProductsBase(JsonModel): class QueryProductsRequest(QueryProductsBase): - order_by: Optional[OrderByFields] = Field(None, alias="orderBy") + order_by: Optional[ProductOrderBy] = Field(None, alias="orderBy") """Specifies the fields to use to sort the products. By default, products are sorted by `id` @@ -78,7 +88,7 @@ class QueryProductsRequest(QueryProductsBase): By default, this value is `false` and products are sorted in ascending order. """ - projection: Optional[List[ProjectionFields]] = None + projection: Optional[List[ProductProjection]] = None """Specifies the product fields to project. When a field value is given here, the corresponding field will be present in all returned products, @@ -110,7 +120,7 @@ class QueryProductsRequest(QueryProductsBase): class QueryProductValuesRequest(QueryProductsBase): - field: Optional[ProjectionFields] = None + field: Optional[ProductField] = None """The product field to return for this query.""" starts_with: Optional[str] = None diff --git a/nisystemlink/clients/product/models/_update_product_request.py b/nisystemlink/clients/product/models/_update_product_request.py deleted file mode 100644 index 2e37390d..00000000 --- a/nisystemlink/clients/product/models/_update_product_request.py +++ /dev/null @@ -1,12 +0,0 @@ -from nisystemlink.clients.product.models._product import Product - - -class UpdateProductRequest(Product): - """This is the request model to update a product. - - It extends the Product model, which is generally used for creating a product. Besides, `id` is added - as a field here. This is to infer which product to be updated. - """ - - id: str - """The globally unique id of the product.""" diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index cc3370af..e152ad9c 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -5,10 +5,11 @@ from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models import ( + CreateProductRequest, CreateProductsPartialSuccess, - Product, + ProductField, + ProductProjection, ProductResponse, - ProjectionFields, UpdateProductRequest, ) from nisystemlink.clients.product.models._paged_products import PagedProducts @@ -42,6 +43,7 @@ def _create_update_product_request( product_data = product_response.dict() product_data.pop("updated_at", None) + product_data.pop("part_number", None) return UpdateProductRequest(**product_data) @@ -53,7 +55,9 @@ def create_products(client: ProductClient): """Fixture to return a factory that creates specs.""" responses: List[CreateProductsPartialSuccess] = [] - def _create_products(products: List[Product]) -> CreateProductsPartialSuccess: + def _create_products( + products: List[CreateProductRequest], + ) -> CreateProductsPartialSuccess: response = client.create_products(products) responses.append(response) return response @@ -79,7 +83,7 @@ def test__create_single_product__one_product_created_with_right_field_values( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = Product( + product = CreateProductRequest( part_number=part_number, name=name, family=family, @@ -101,8 +105,8 @@ def test__create_multiple_products__multiple_creates_succeed( self, client: ProductClient, create_products ): products = [ - Product(part_number=uuid.uuid1().hex), - Product(part_number=uuid.uuid1().hex), + CreateProductRequest(part_number=uuid.uuid1().hex), + CreateProductRequest(part_number=uuid.uuid1().hex), ] response: CreateProductsPartialSuccess = create_products(products) assert response is not None @@ -111,7 +115,7 @@ def test__create_multiple_products__multiple_creates_succeed( def test__create_single_product_and_get_products__at_least_one_product_exists( self, client: ProductClient, create_products, unique_identifier ): - products = [Product(part_number=unique_identifier)] + products = [CreateProductRequest(part_number=unique_identifier)] create_products(products) get_response = client.get_products_paged() assert get_response is not None @@ -121,8 +125,8 @@ def test__create_multiple_products_and_get_products_with_take__only_take_returne self, client: ProductClient, create_products, unique_identifier ): products = [ - Product(part_number=unique_identifier), - Product(part_number=unique_identifier), + CreateProductRequest(part_number=unique_identifier), + CreateProductRequest(part_number=unique_identifier), ] create_products(products) get_response = client.get_products_paged(take=1) @@ -133,8 +137,8 @@ def test__create_multiple_products_and_get_products_with_count_at_least_one_coun self, client: ProductClient, create_products, unique_identifier ): products = [ - Product(part_number=unique_identifier), - Product(part_number=unique_identifier), + CreateProductRequest(part_number=unique_identifier), + CreateProductRequest(part_number=unique_identifier), ] create_products(products) get_response: PagedProducts = client.get_products_paged(return_count=True) @@ -145,7 +149,7 @@ def test__get_product_by_id__product_matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [Product(part_number=part_number)] + products = [CreateProductRequest(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None id = str(create_response.products[0].id) @@ -157,7 +161,7 @@ def test__query_product_by_part_number__matches_expected( self, client: ProductClient, create_products, unique_identifier ): part_number = unique_identifier - products = [Product(part_number=part_number)] + products = [CreateProductRequest(part_number=part_number)] create_response: CreateProductsPartialSuccess = create_products(products) assert create_response is not None query_request = QueryProductsRequest( @@ -173,11 +177,11 @@ def test__query_product_values_for_name__name_matches( part_number = unique_identifier test_name = "query values test" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=part_number, name=test_name)] + [CreateProductRequest(part_number=part_number, name=test_name)] ) assert create_response is not None query_request = QueryProductValuesRequest( - filter=f'partNumber="{part_number}"', field=ProjectionFields.NAME + filter=f'partNumber="{part_number}"', field=ProductField.NAME ) query_response: List[str] = client.query_product_values(query_request) assert query_response is not None @@ -194,7 +198,11 @@ def test__update_keywords_with_replace__keywords_replaced( original_keyword = "originalKeyword" updated_keyword = "updatedKeyword" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, keywords=[original_keyword])] + [ + CreateProductRequest( + part_number=unique_identifier, keywords=[original_keyword] + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 @@ -222,7 +230,11 @@ def test__update_keywords_no_replace__keywords_appended( original_keyword = "originalKeyword" additional_keyword = "additionalKeyword" create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, keywords=[original_keyword])] + [ + CreateProductRequest( + part_number=unique_identifier, keywords=[original_keyword] + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 @@ -254,7 +266,11 @@ def test__update_properties_with_replace__properties_replaced( original_properties = {"originalKey": "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, properties=original_properties)] + [ + CreateProductRequest( + part_number=unique_identifier, properties=original_properties + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 @@ -287,7 +303,11 @@ def test__update_properties_append__properties_appended( original_properties = {original_key: "originalValue"} new_properties = {new_key: "newValue"} create_response: CreateProductsPartialSuccess = create_products( - [Product(part_number=unique_identifier, properties=original_properties)] + [ + CreateProductRequest( + part_number=unique_identifier, properties=original_properties + ) + ] ) assert create_response is not None assert len(create_response.products) == 1 @@ -318,12 +338,12 @@ def test__query_products_linked_to_files_correct_products_returned( file_id = uuid.uuid1().hex product_name_with_file = "Has File" products = [ - Product( + CreateProductRequest( part_number=uuid.uuid1().hex, name=product_name_with_file, file_ids=[file_id], ), - Product(part_number=uuid.uuid1().hex, name="No File Link"), + CreateProductRequest(part_number=uuid.uuid1().hex, name="No File Link"), ] print(products) create_response: CreateProductsPartialSuccess = create_products(products) @@ -341,7 +361,7 @@ def test__query_products_with_projection__returns_only_specified_fields( family = "Example Family" keywords = ["testing"] properties = {"test_property": "yes"} - product = Product( + product = CreateProductRequest( part_number=part_number, name=name, family=family, @@ -353,7 +373,7 @@ def test__query_products_with_projection__returns_only_specified_fields( query_request = QueryProductsRequest( filter=f'partNumber=="{part_number}"', - projection=[ProjectionFields.FAMILY, ProjectionFields.NAME], + projection=[ProductProjection.FAMILY, ProductProjection.NAME], ) query_response: PagedProducts = client.query_products_paged(query_request) queried_product = query_response.products[0] From cfb783594a679e43f9833d0eab78d849af9f3aa7 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Thu, 27 Feb 2025 13:03:04 +0530 Subject: [PATCH 14/24] refactor: update specification enum name --- examples/product/products.py | 4 ++-- nisystemlink/clients/spec/models/__init__.py | 2 +- nisystemlink/clients/spec/models/_query_specs.py | 8 ++++---- tests/integration/spec/test_spec.py | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/product/products.py b/examples/product/products.py index 46258b03..fb77e36b 100644 --- a/examples/product/products.py +++ b/examples/product/products.py @@ -2,8 +2,8 @@ from nisystemlink.clients.product import ProductClient from nisystemlink.clients.product.models import ( CreateProductRequest, + ProductField, ProductOrderBy, - ProductProjection, QueryProductsRequest, QueryProductValuesRequest, ) @@ -71,7 +71,7 @@ def create_some_products(): # Query for just the ids of products that match the family values_query = QueryProductValuesRequest( - filter=f'family="{family}"', field=ProductProjection.ID + filter=f'family="{family}"', field=ProductField.ID ) values_response = client.query_product_values(query=values_query) diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index 6a13942a..97d88558 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -13,9 +13,9 @@ ) from ._delete_specs_request import DeleteSpecificationsPartialSuccess from ._query_specs import ( - Projection, QuerySpecificationsRequest, QuerySpecifications, + SpecificationProjection, SpecificationWithOptionalFields, ) from ._specification import ( diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 7a6b8330..80f382cc 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -12,7 +12,7 @@ ) -class Projection(str, Enum): +class SpecificationProjection(str, Enum): """The allowed projections for query. When using projection, only the fields specified by the projection element will be included in @@ -40,7 +40,7 @@ class Projection(str, Enum): CREATED_BY = "CREATED_BY" -class OrderBy(Enum): +class SpecificationOrderBy(Enum): """The valid ways to order the response to a spec query.""" ID = "ID" @@ -91,13 +91,13 @@ class QuerySpecificationsRequest(JsonModel): documentation for more details. """ - projection: Optional[List[Projection]] = None + projection: Optional[List[SpecificationProjection]] = None """Specifies the fields to include in the returned specifications. Fields you do not specify are excluded. Returns all fields if no value is specified. """ - order_by: Optional[OrderBy] = None + order_by: Optional[SpecificationOrderBy] = None """Specifies the field to use to sort specifications. By default, specifications are sorted by `ID`. diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 1f3a38cf..6a7877a6 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -12,11 +12,11 @@ CreateSpecificationsPartialSuccess, CreateSpecificationsRequest, NumericConditionValue, - Projection, QuerySpecificationsRequest, Specification, SpecificationDefinition, SpecificationLimit, + SpecificationProjection, SpecificationType, UpdateSpecificationsRequest, ) @@ -282,7 +282,8 @@ def test__query_spec_projection_columns__columns_returned( self, client: SpecClient, create_specs, create_specs_for_query, product ): request = QuerySpecificationsRequest( - product_ids=[product], projection=[Projection.SPEC_ID, Projection.NAME] + product_ids=[product], + projection=[SpecificationProjection.SPEC_ID, SpecificationProjection.NAME], ) response = client.query_specs(request) assert response.specs From e35715b38afcc1f2fc4c2a83d810c281f8213524 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Fri, 28 Feb 2025 12:18:51 +0530 Subject: [PATCH 15/24] feat: add retry mechanism for both specs and product clients --- nisystemlink/clients/product/_product_client.py | 5 ++++- nisystemlink/clients/spec/_spec_client.py | 7 +++++-- nisystemlink/clients/spec/models/__init__.py | 2 +- nisystemlink/clients/spec/models/_query_specs.py | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index 385e075b..37048840 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -5,11 +5,14 @@ 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 uplink import Field, Query, returns +from uplink import Field, Query, retry, returns from . import models +@retry( + when=retry.when.status([408, 429, 502, 503, 504]), stop=retry.stop.after_attempt(5) +) class ProductClient(BaseClient): def __init__(self, configuration: Optional[core.HttpConfiguration] = None): """Initialize an instance. diff --git a/nisystemlink/clients/spec/_spec_client.py b/nisystemlink/clients/spec/_spec_client.py index e401932c..ff6de3cd 100644 --- a/nisystemlink/clients/spec/_spec_client.py +++ b/nisystemlink/clients/spec/_spec_client.py @@ -5,11 +5,14 @@ from nisystemlink.clients import core from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post -from uplink import Field +from uplink import Field, retry from . import models +@retry( + when=retry.when.status([408, 429, 502, 503, 504]), stop=retry.stop.after_attempt(5) +) class SpecClient(BaseClient): def __init__(self, configuration: Optional[core.HttpConfiguration] = None): """Initialize an instance. @@ -80,7 +83,7 @@ def delete_specs( @post("query-specs") def query_specs( self, query: models.QuerySpecificationsRequest - ) -> models.QuerySpecifications: + ) -> models.PagedSpecifications: """Queries for specs that match the filters. Args: diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index 97d88558..6bb739fe 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -14,7 +14,7 @@ from ._delete_specs_request import DeleteSpecificationsPartialSuccess from ._query_specs import ( QuerySpecificationsRequest, - QuerySpecifications, + PagedSpecifications, SpecificationProjection, SpecificationWithOptionalFields, ) diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 80f382cc..fe9c95b8 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -173,7 +173,7 @@ class SpecificationWithOptionalFields(SpecificationCreation, SpecificationUpdate """When the spec was created and when.""" -class QuerySpecifications(WithPaging): +class PagedSpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" specs: Optional[List[SpecificationWithOptionalFields]] = None From 1e73458b27c9c05ab3a018069a51d64e2135b369 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Mon, 3 Mar 2025 11:37:23 +0530 Subject: [PATCH 16/24] test: reformat spec column projection test case --- tests/integration/spec/test_spec.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 6a7877a6..0cf1de44 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -285,13 +285,15 @@ def test__query_spec_projection_columns__columns_returned( product_ids=[product], projection=[SpecificationProjection.SPEC_ID, SpecificationProjection.NAME], ) + response = client.query_specs(request) - assert response.specs - assert len(response.specs) == 3 - specs = [vars(spec) for spec in response.specs] + specs = [vars(spec) for spec in response.specs or []] spec_columns = { key for spec in specs for key in spec.keys() if spec[key] is not None } + + assert response.specs + assert len(response.specs) == 3 assert len(spec_columns) == 2 assert "spec_id" in spec_columns assert "name" in spec_columns From 070d68f9d34222ac0ee3037bf6b556e0748870a6 Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Mon, 3 Mar 2025 11:47:55 +0530 Subject: [PATCH 17/24] refactor: change from ProductResponse to Product --- nisystemlink/clients/product/_product_client.py | 2 +- nisystemlink/clients/product/models/__init__.py | 2 +- .../product/models/_create_products_partial_success.py | 4 ++-- nisystemlink/clients/product/models/_paged_products.py | 4 ++-- nisystemlink/clients/product/models/_product_response.py | 2 +- nisystemlink/clients/product/utilities/_file_utilities.py | 6 ++---- tests/integration/product/test_product_client.py | 6 +++--- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/nisystemlink/clients/product/_product_client.py b/nisystemlink/clients/product/_product_client.py index 37048840..9f10de99 100644 --- a/nisystemlink/clients/product/_product_client.py +++ b/nisystemlink/clients/product/_product_client.py @@ -75,7 +75,7 @@ def get_products_paged( ... @get("products/{id}") - def get_product(self, id: str) -> models.ProductResponse: + def get_product(self, id: str) -> models.Product: """Retrieves a single product by id. Args: diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 152daead..32c654dc 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -8,7 +8,7 @@ QueryProductsRequest, QueryProductValuesRequest, ) -from ._product_response import ProductResponse +from ._product_response import Product from ._product_request import CreateProductRequest, UpdateProductRequest # flake8: noqa diff --git a/nisystemlink/clients/product/models/_create_products_partial_success.py b/nisystemlink/clients/product/models/_create_products_partial_success.py index d7f6b32b..a1e05410 100644 --- a/nisystemlink/clients/product/models/_create_products_partial_success.py +++ b/nisystemlink/clients/product/models/_create_products_partial_success.py @@ -3,11 +3,11 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.product.models._product_request import CreateProductRequest -from nisystemlink.clients.product.models._product_response import ProductResponse +from nisystemlink.clients.product.models._product_response import Product class CreateProductsPartialSuccess(JsonModel): - products: List[ProductResponse] + products: List[Product] """The list of products that were successfully created.""" failed: Optional[List[CreateProductRequest]] diff --git a/nisystemlink/clients/product/models/_paged_products.py b/nisystemlink/clients/product/models/_paged_products.py index 9ad92017..6eaa04c7 100644 --- a/nisystemlink/clients/product/models/_paged_products.py +++ b/nisystemlink/clients/product/models/_paged_products.py @@ -1,7 +1,7 @@ from typing import List, Optional from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.product.models._product_response import ProductResponse +from nisystemlink.clients.product.models._product_response import Product class PagedProducts(WithPaging): @@ -9,7 +9,7 @@ class PagedProducts(WithPaging): token if applicable. """ - products: List[ProductResponse] + products: List[Product] """A list of all the products in this page.""" total_count: Optional[int] diff --git a/nisystemlink/clients/product/models/_product_response.py b/nisystemlink/clients/product/models/_product_response.py index 99a989f7..045dba75 100644 --- a/nisystemlink/clients/product/models/_product_response.py +++ b/nisystemlink/clients/product/models/_product_response.py @@ -4,7 +4,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel -class ProductResponse(JsonModel): +class Product(JsonModel): """Contains information about a product, where all the fields are optional. - This is because when using query products' projection, user can request for any of the available diff --git a/nisystemlink/clients/product/utilities/_file_utilities.py b/nisystemlink/clients/product/utilities/_file_utilities.py index 2c6f0a58..d9335fea 100644 --- a/nisystemlink/clients/product/utilities/_file_utilities.py +++ b/nisystemlink/clients/product/utilities/_file_utilities.py @@ -3,16 +3,14 @@ from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models._paged_products import PagedProducts from nisystemlink.clients.product.models._product_response import ( - ProductResponse, + Product, ) from nisystemlink.clients.product.models._query_products_request import ( QueryProductsRequest, ) -def get_products_linked_to_file( - client: ProductClient, file_id: str -) -> List[ProductResponse]: +def get_products_linked_to_file(client: ProductClient, file_id: str) -> List[Product]: """Gets a list of all the products that are linked to the file. Args: diff --git a/tests/integration/product/test_product_client.py b/tests/integration/product/test_product_client.py index e152ad9c..e4ccb163 100644 --- a/tests/integration/product/test_product_client.py +++ b/tests/integration/product/test_product_client.py @@ -7,9 +7,9 @@ from nisystemlink.clients.product.models import ( CreateProductRequest, CreateProductsPartialSuccess, + Product, ProductField, ProductProjection, - ProductResponse, UpdateProductRequest, ) from nisystemlink.clients.product.models._paged_products import PagedProducts @@ -38,7 +38,7 @@ def create_update_product_request(): """Fixture to create a request object for updating products, from a product response""" def _create_update_product_request( - product_response: ProductResponse, + product_response: Product, ) -> UpdateProductRequest: product_data = product_response.dict() @@ -64,7 +64,7 @@ def _create_products( yield _create_products - created_products: List[ProductResponse] = [] + created_products: List[Product] = [] for response in responses: if response.products: created_products = created_products + response.products From 8f8308303a09025fd2ba698594f85cf05fbf6924 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Mon, 3 Mar 2025 14:51:19 +0530 Subject: [PATCH 18/24] fix:PRComments --- nisystemlink/clients/spec/models/__init__.py | 4 +- .../spec/models/_create_specs_request.py | 19 +++++- .../clients/spec/models/_query_specs.py | 67 ++----------------- .../clients/spec/models/_specification.py | 10 +-- .../spec/models/_update_specs_request.py | 29 +++++++- tests/integration/spec/test_spec.py | 22 +++--- 6 files changed, 67 insertions(+), 84 deletions(-) diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index 6bb739fe..01c197fc 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -10,13 +10,14 @@ CreatedSpecification, CreateSpecificationsPartialSuccess, CreateSpecificationsRequest, + CreateSpecificationsRequestObject ) from ._delete_specs_request import DeleteSpecificationsPartialSuccess from ._query_specs import ( QuerySpecificationsRequest, PagedSpecifications, SpecificationProjection, - SpecificationWithOptionalFields, + QuerySpecificationResponse ) from ._specification import ( Specification, @@ -32,6 +33,7 @@ UpdatedSpecification, UpdateSpecificationsPartialSuccess, UpdateSpecificationsRequest, + UpdateSpecificationsRequestObject ) # flake8: noqa diff --git a/nisystemlink/clients/spec/models/_create_specs_request.py b/nisystemlink/clients/spec/models/_create_specs_request.py index 81c5d907..138751f8 100644 --- a/nisystemlink/clients/spec/models/_create_specs_request.py +++ b/nisystemlink/clients/spec/models/_create_specs_request.py @@ -6,14 +6,29 @@ SpecificationCreation, SpecificationDefinition, SpecificationServerManaged, + SpecificationType, SpecificationUserManaged, ) +class CreateSpecificationsRequestObject(SpecificationDefinition): + product_id: str + """Id of the product to which the specification will be associated.""" + + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + + type: SpecificationType + """Type of the specification.""" + + class CreateSpecificationsRequest(JsonModel): """Create multiple specifications.""" - specs: Optional[List[SpecificationDefinition]] = None + specs: Optional[List[CreateSpecificationsRequestObject]] = None """List of specifications to be created.""" @@ -29,7 +44,7 @@ class CreateSpecificationsPartialSuccess(JsonModel): created_specs: Optional[List[CreatedSpecification]] = None """Information about the created specification(s)""" - failed_specs: Optional[List[SpecificationDefinition]] = None + failed_specs: Optional[List[CreateSpecificationsRequestObject]] = None """List of specification requests that failed during creation.""" error: Optional[ApiError] = None diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index fe9c95b8..dae90908 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -5,6 +5,7 @@ from nisystemlink.clients.core._uplink._with_paging import WithPaging from nisystemlink.clients.spec.models._specification import ( Condition, + Specification, SpecificationCreation, SpecificationLimit, SpecificationType, @@ -109,74 +110,14 @@ class QuerySpecificationsRequest(JsonModel): """ -class SpecificationWithOptionalFields(SpecificationCreation, SpecificationUpdated): - """A full specification with update and create history with only optional fields""" - - id: Optional[str] = None - """The global Id of the specification.""" - - version: Optional[int] = None - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - - product_id: Optional[str] = None - """Id of the product to which the specification will be associated.""" - - spec_id: Optional[str] = None - """User provided value using which the specification will be identified. - - This should be unique for a product and workspace combination. - """ - - workspace: Optional[str] = None - """Id of the workspace to which the specification will be associated. - - Default workspace will be taken if the value is not given. - """ - - name: Optional[str] = None - """Name of the specification.""" - - category: Optional[str] = None - """Category of the specification.""" - - type: Optional[SpecificationType] = None - """Type of the specification.""" - - symbol: Optional[str] = None - """Short form identifier of the specification.""" - - block: Optional[str] = None - """Block name of the specification. - - Typically a block is one of the subsystems of the overall product being specified. - """ - - limit: Optional[SpecificationLimit] = None - """The limits for this spec.""" - - unit: Optional[str] = None - """Unit of the specification.""" - - conditions: Optional[List[Condition]] = None - """Conditions associated with the specification.""" - - keywords: Optional[List[str]] = None - """Keywords or phrases associated with the specification.""" - - properties: Optional[Dict[str, str]] = None - """Additional properties associated with the specification.""" - - """When the spec was created and when.""" +class QuerySpecificationResponse(Specification, SpecificationCreation, SpecificationUpdated): + """A full specification with update and create history.""" class PagedSpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" - specs: Optional[List[SpecificationWithOptionalFields]] = None + specs: Optional[List[QuerySpecificationResponse]] = None """List of queried specifications. An empty list indicates that there are no specifications meeting the criteria provided in the diff --git a/nisystemlink/clients/spec/models/_specification.py b/nisystemlink/clients/spec/models/_specification.py index a711a3ec..9eef6e82 100644 --- a/nisystemlink/clients/spec/models/_specification.py +++ b/nisystemlink/clients/spec/models/_specification.py @@ -41,10 +41,10 @@ class SpecificationType(Enum): class SpecificationUserManaged(JsonModel): - product_id: str + product_id: Optional[str] = None """Id of the product to which the specification will be associated.""" - spec_id: str + spec_id: Optional[str] = None """User provided value using which the specification will be identified. This should be unique for a product and workspace combination. @@ -58,10 +58,10 @@ class SpecificationUserManaged(JsonModel): class SpecificationServerManaged(JsonModel): - id: str + id: Optional[str] = None """The global Id of the specification.""" - version: int + version: Optional[int] = None """ Current version of the specification. @@ -77,7 +77,7 @@ class SpecificationDefinition(SpecificationUserManaged): category: Optional[str] = None """Category of the specification.""" - type: SpecificationType + type: Optional[SpecificationType] = None """Type of the specification.""" symbol: Optional[str] = None diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index e2a11b9d..533956ff 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -5,14 +5,39 @@ from nisystemlink.clients.spec.models._specification import ( Specification, SpecificationServerManaged, + SpecificationType, SpecificationUpdated, SpecificationUserManaged, ) +class UpdateSpecificationsRequestObject(Specification): + id: str + """The global Id of the specification.""" + + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + + product_id: str + """Id of the product to which the specification will be associated.""" + + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + + type: SpecificationType + """Type of the specification.""" + + class UpdateSpecificationsRequest(JsonModel): - specs: Optional[List[Specification]] = None + specs: Optional[List[UpdateSpecificationsRequestObject]] = None """List of specifications to be updated.""" @@ -29,7 +54,7 @@ class UpdateSpecificationsPartialSuccess(JsonModel): updated_specs: Optional[List[UpdatedSpecification]] = None """Information about each of the updated specification(s).""" - failed_specs: Optional[List[Specification]] = None + failed_specs: Optional[List[UpdateSpecificationsRequestObject]] = None """Information about each of the specification request(s) that failed during the update.""" error: Optional[ApiError] = None diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 0cf1de44..9781dad9 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -11,14 +11,14 @@ CreatedSpecification, CreateSpecificationsPartialSuccess, CreateSpecificationsRequest, + CreateSpecificationsRequestObject, NumericConditionValue, QuerySpecificationsRequest, - Specification, - SpecificationDefinition, SpecificationLimit, SpecificationProjection, SpecificationType, UpdateSpecificationsRequest, + UpdateSpecificationsRequestObject ) @@ -60,7 +60,7 @@ def _create_specs( def create_specs_for_query(create_specs, product): """Fixture for creating a set of specs that can be used to test query operations.""" spec_requests = [ - SpecificationDefinition( + CreateSpecificationsRequestObject( product_id=product, spec_id=uuid.uuid1().hex, type=SpecificationType.PARAMETRIC, @@ -69,7 +69,7 @@ def create_specs_for_query(create_specs, product): limit=SpecificationLimit(min=1.2, max=1.5), unit="mV", ), - SpecificationDefinition( + CreateSpecificationsRequestObject( product_id=product, spec_id=uuid.uuid1().hex, type=SpecificationType.PARAMETRIC, @@ -96,7 +96,7 @@ def create_specs_for_query(create_specs, product): ), ], ), - SpecificationDefinition( + CreateSpecificationsRequestObject( product_id=product, spec_id=uuid.uuid1().hex, type=SpecificationType.FUNCTIONAL, @@ -119,7 +119,7 @@ def test__create_single_spec__one_created_with_right_field_values( ): specId = uuid.uuid1().hex productId = product - spec = SpecificationDefinition( + spec = CreateSpecificationsRequestObject( product_id=productId, spec_id=specId, type=SpecificationType.FUNCTIONAL, @@ -141,7 +141,7 @@ def test__create_multiple_specs__all_succeed( productId = product specs = [] for id in specIds: - spec = SpecificationDefinition( + spec = CreateSpecificationsRequestObject( product_id=productId, spec_id=id, type=SpecificationType.FUNCTIONAL, @@ -159,7 +159,7 @@ def test__create_duplicate_spec__errors( ): duplicate_id = uuid.uuid1().hex productId = product - spec = SpecificationDefinition( + spec = CreateSpecificationsRequestObject( product_id=productId, spec_id=duplicate_id, type=SpecificationType.FUNCTIONAL, @@ -180,7 +180,7 @@ def test__delete_existing_spec__succeeds(self, client: SpecClient, product): # Not using the fixture here so that we can inspect delete response. specId = uuid.uuid1().hex productId = product - spec = SpecificationDefinition( + spec = CreateSpecificationsRequestObject( product_id=productId, spec_id=specId, type=SpecificationType.FUNCTIONAL, @@ -202,7 +202,7 @@ def test__delete_non_existant_spec__delete_fails(self, client: SpecClient): def test__update_single_same_version__version_updates( self, client: SpecClient, create_specs, product ): - spec = SpecificationDefinition( + spec = CreateSpecificationsRequestObject( product_id=product, spec_id="spec1", type=SpecificationType.FUNCTIONAL, @@ -216,7 +216,7 @@ def test__update_single_same_version__version_updates( created_spec = response.created_specs[0] assert created_spec.version == 0 - update_spec = Specification( + update_spec = UpdateSpecificationsRequestObject( id=created_spec.id, product_id=created_spec.product_id, spec_id=created_spec.spec_id, From 023e3a766ba587f7c082c92c40df624c8d5cc946 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Mon, 3 Mar 2025 15:02:49 +0530 Subject: [PATCH 19/24] fix:PRComments --- nisystemlink/clients/spec/models/__init__.py | 6 +++--- .../spec/models/_create_specs_request.py | 19 +++++++++++++++++++ .../clients/spec/models/_query_specs.py | 11 +++++------ .../spec/models/_update_specs_request.py | 19 +++++++++++++++++++ tests/integration/spec/test_spec.py | 2 +- 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index 01c197fc..a33febc6 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -10,14 +10,14 @@ CreatedSpecification, CreateSpecificationsPartialSuccess, CreateSpecificationsRequest, - CreateSpecificationsRequestObject + CreateSpecificationsRequestObject, ) from ._delete_specs_request import DeleteSpecificationsPartialSuccess from ._query_specs import ( QuerySpecificationsRequest, PagedSpecifications, SpecificationProjection, - QuerySpecificationResponse + QuerySpecificationsResponse, ) from ._specification import ( Specification, @@ -33,7 +33,7 @@ UpdatedSpecification, UpdateSpecificationsPartialSuccess, UpdateSpecificationsRequest, - UpdateSpecificationsRequestObject + UpdateSpecificationsRequestObject, ) # flake8: noqa diff --git a/nisystemlink/clients/spec/models/_create_specs_request.py b/nisystemlink/clients/spec/models/_create_specs_request.py index 138751f8..fd4ce956 100644 --- a/nisystemlink/clients/spec/models/_create_specs_request.py +++ b/nisystemlink/clients/spec/models/_create_specs_request.py @@ -37,6 +37,25 @@ class CreatedSpecification( ): """A specification successfully created on the server.""" + id: str + """The global Id of the specification.""" + + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + + product_id: str + """Id of the product to which the specification will be associated.""" + + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + class CreateSpecificationsPartialSuccess(JsonModel): """When some specs can not be created, this contains the list that was and was not created.""" diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index dae90908..32f637e9 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -1,14 +1,11 @@ from enum import Enum -from typing import Dict, List, Optional +from typing import List, Optional from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging from nisystemlink.clients.spec.models._specification import ( - Condition, Specification, SpecificationCreation, - SpecificationLimit, - SpecificationType, SpecificationUpdated, ) @@ -110,14 +107,16 @@ class QuerySpecificationsRequest(JsonModel): """ -class QuerySpecificationResponse(Specification, SpecificationCreation, SpecificationUpdated): +class QuerySpecificationsResponse( + Specification, SpecificationCreation, SpecificationUpdated +): """A full specification with update and create history.""" class PagedSpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" - specs: Optional[List[QuerySpecificationResponse]] = None + specs: Optional[List[QuerySpecificationsResponse]] = None """List of queried specifications. An empty list indicates that there are no specifications meeting the criteria provided in the diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index 533956ff..241f7cbe 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -48,6 +48,25 @@ class UpdatedSpecification( ): """A specification that was updated on the server.""" + id: str + """The global Id of the specification.""" + + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + + product_id: str + """Id of the product to which the specification will be associated.""" + + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + class UpdateSpecificationsPartialSuccess(JsonModel): diff --git a/tests/integration/spec/test_spec.py b/tests/integration/spec/test_spec.py index 9781dad9..49afc40f 100644 --- a/tests/integration/spec/test_spec.py +++ b/tests/integration/spec/test_spec.py @@ -18,7 +18,7 @@ SpecificationProjection, SpecificationType, UpdateSpecificationsRequest, - UpdateSpecificationsRequestObject + UpdateSpecificationsRequestObject, ) From d7edfdfc242b4ba464281b7868a5f863489605c5 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Mon, 3 Mar 2025 15:59:13 +0530 Subject: [PATCH 20/24] fix:PRComments --- .../clients/spec/models/_create_specs_request.py | 12 +++++++----- .../clients/spec/models/_update_specs_request.py | 14 +++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/nisystemlink/clients/spec/models/_create_specs_request.py b/nisystemlink/clients/spec/models/_create_specs_request.py index fd4ce956..c473113f 100644 --- a/nisystemlink/clients/spec/models/_create_specs_request.py +++ b/nisystemlink/clients/spec/models/_create_specs_request.py @@ -5,9 +5,7 @@ from nisystemlink.clients.spec.models._specification import ( SpecificationCreation, SpecificationDefinition, - SpecificationServerManaged, SpecificationType, - SpecificationUserManaged, ) @@ -32,9 +30,7 @@ class CreateSpecificationsRequest(JsonModel): """List of specifications to be created.""" -class CreatedSpecification( - SpecificationServerManaged, SpecificationUserManaged, SpecificationCreation -): +class CreatedSpecification(SpecificationCreation): """A specification successfully created on the server.""" id: str @@ -56,6 +52,12 @@ class CreatedSpecification( This should be unique for a product and workspace combination. """ + workspace: Optional[str] = None + """Id of the workspace to which the specification will be associated. + + Default workspace will be taken if the value is not given. + """ + class CreateSpecificationsPartialSuccess(JsonModel): """When some specs can not be created, this contains the list that was and was not created.""" diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index 241f7cbe..ed736117 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -4,10 +4,8 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.spec.models._specification import ( Specification, - SpecificationServerManaged, SpecificationType, SpecificationUpdated, - SpecificationUserManaged, ) @@ -41,11 +39,7 @@ class UpdateSpecificationsRequest(JsonModel): """List of specifications to be updated.""" -class UpdatedSpecification( - SpecificationUserManaged, - SpecificationServerManaged, - SpecificationUpdated, -): +class UpdatedSpecification(SpecificationUpdated): """A specification that was updated on the server.""" id: str @@ -67,6 +61,12 @@ class UpdatedSpecification( This should be unique for a product and workspace combination. """ + workspace: str + """Id of the workspace to which the specification will be associated. + + Default workspace will be taken if the value is not given. + """ + class UpdateSpecificationsPartialSuccess(JsonModel): From 1fab252368e287e67b45a4f31081814de22ffa83 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Mon, 3 Mar 2025 18:51:55 +0530 Subject: [PATCH 21/24] fix:PRComments --- .../models/_base_specification_response.py | 24 ++++++++++++++++ .../spec/models/_create_specs_request.py | 24 +++------------- .../clients/spec/models/_specification.py | 6 ++-- .../spec/models/_update_specs_request.py | 28 ++++--------------- 4 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 nisystemlink/clients/spec/models/_base_specification_response.py diff --git a/nisystemlink/clients/spec/models/_base_specification_response.py b/nisystemlink/clients/spec/models/_base_specification_response.py new file mode 100644 index 00000000..485dac53 --- /dev/null +++ b/nisystemlink/clients/spec/models/_base_specification_response.py @@ -0,0 +1,24 @@ +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class BaseSpecificationResponse(JsonModel): + """Base Response Model for create specs response and update specs response.""" + + id: str + """The global Id of the specification.""" + + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + + product_id: str + """Id of the product to which the specification will be associated.""" + + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ diff --git a/nisystemlink/clients/spec/models/_create_specs_request.py b/nisystemlink/clients/spec/models/_create_specs_request.py index c473113f..2aaa099a 100644 --- a/nisystemlink/clients/spec/models/_create_specs_request.py +++ b/nisystemlink/clients/spec/models/_create_specs_request.py @@ -2,6 +2,9 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.spec.models._base_specification_response import ( + BaseSpecificationResponse, +) from nisystemlink.clients.spec.models._specification import ( SpecificationCreation, SpecificationDefinition, @@ -30,28 +33,9 @@ class CreateSpecificationsRequest(JsonModel): """List of specifications to be created.""" -class CreatedSpecification(SpecificationCreation): +class CreatedSpecification(BaseSpecificationResponse, SpecificationCreation): """A specification successfully created on the server.""" - id: str - """The global Id of the specification.""" - - version: int - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - - product_id: str - """Id of the product to which the specification will be associated.""" - - spec_id: str - """User provided value using which the specification will be identified. - - This should be unique for a product and workspace combination. - """ - workspace: Optional[str] = None """Id of the workspace to which the specification will be associated. diff --git a/nisystemlink/clients/spec/models/_specification.py b/nisystemlink/clients/spec/models/_specification.py index 9eef6e82..64e29f82 100644 --- a/nisystemlink/clients/spec/models/_specification.py +++ b/nisystemlink/clients/spec/models/_specification.py @@ -77,9 +77,6 @@ class SpecificationDefinition(SpecificationUserManaged): category: Optional[str] = None """Category of the specification.""" - type: Optional[SpecificationType] = None - """Type of the specification.""" - symbol: Optional[str] = None """Short form identifier of the specification.""" @@ -108,6 +105,9 @@ class SpecificationDefinition(SpecificationUserManaged): class Specification(SpecificationDefinition, SpecificationServerManaged): """The complete definition of a specification.""" + type: Optional[SpecificationType] = None + """Type of the specification.""" + class SpecificationCreation(JsonModel): """When the spec was created and when.""" diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index ed736117..837ff808 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -2,14 +2,17 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.spec.models._base_specification_response import ( + BaseSpecificationResponse, +) from nisystemlink.clients.spec.models._specification import ( - Specification, + SpecificationDefinition, SpecificationType, SpecificationUpdated, ) -class UpdateSpecificationsRequestObject(Specification): +class UpdateSpecificationsRequestObject(SpecificationDefinition): id: str """The global Id of the specification.""" @@ -39,28 +42,9 @@ class UpdateSpecificationsRequest(JsonModel): """List of specifications to be updated.""" -class UpdatedSpecification(SpecificationUpdated): +class UpdatedSpecification(BaseSpecificationResponse, SpecificationUpdated): """A specification that was updated on the server.""" - id: str - """The global Id of the specification.""" - - version: int - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - - product_id: str - """Id of the product to which the specification will be associated.""" - - spec_id: str - """User provided value using which the specification will be identified. - - This should be unique for a product and workspace combination. - """ - workspace: str """Id of the workspace to which the specification will be associated. From 55bed9be1b356909973fb103ac97aac7b03b4d23 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Mon, 3 Mar 2025 19:07:02 +0530 Subject: [PATCH 22/24] fix:PRComments --- nisystemlink/clients/product/models/__init__.py | 2 +- .../product/models/_create_products_partial_success.py | 2 +- nisystemlink/clients/product/models/_paged_products.py | 2 +- .../product/models/{_product_response.py => _product.py} | 0 nisystemlink/clients/product/utilities/_file_utilities.py | 2 +- nisystemlink/clients/spec/models/_update_specs_request.py | 6 ++++++ 6 files changed, 10 insertions(+), 4 deletions(-) rename nisystemlink/clients/product/models/{_product_response.py => _product.py} (100%) diff --git a/nisystemlink/clients/product/models/__init__.py b/nisystemlink/clients/product/models/__init__.py index 32c654dc..4a21c838 100644 --- a/nisystemlink/clients/product/models/__init__.py +++ b/nisystemlink/clients/product/models/__init__.py @@ -8,7 +8,7 @@ QueryProductsRequest, QueryProductValuesRequest, ) -from ._product_response import Product +from ._product import Product from ._product_request import CreateProductRequest, UpdateProductRequest # flake8: noqa diff --git a/nisystemlink/clients/product/models/_create_products_partial_success.py b/nisystemlink/clients/product/models/_create_products_partial_success.py index a1e05410..cd529145 100644 --- a/nisystemlink/clients/product/models/_create_products_partial_success.py +++ b/nisystemlink/clients/product/models/_create_products_partial_success.py @@ -2,8 +2,8 @@ from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel +from nisystemlink.clients.product.models._product import Product from nisystemlink.clients.product.models._product_request import CreateProductRequest -from nisystemlink.clients.product.models._product_response import Product class CreateProductsPartialSuccess(JsonModel): diff --git a/nisystemlink/clients/product/models/_paged_products.py b/nisystemlink/clients/product/models/_paged_products.py index 6eaa04c7..c615a5c4 100644 --- a/nisystemlink/clients/product/models/_paged_products.py +++ b/nisystemlink/clients/product/models/_paged_products.py @@ -1,7 +1,7 @@ from typing import List, Optional from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.product.models._product_response import Product +from nisystemlink.clients.product.models._product import Product class PagedProducts(WithPaging): diff --git a/nisystemlink/clients/product/models/_product_response.py b/nisystemlink/clients/product/models/_product.py similarity index 100% rename from nisystemlink/clients/product/models/_product_response.py rename to nisystemlink/clients/product/models/_product.py diff --git a/nisystemlink/clients/product/utilities/_file_utilities.py b/nisystemlink/clients/product/utilities/_file_utilities.py index d9335fea..e86a6fc7 100644 --- a/nisystemlink/clients/product/utilities/_file_utilities.py +++ b/nisystemlink/clients/product/utilities/_file_utilities.py @@ -2,7 +2,7 @@ from nisystemlink.clients.product._product_client import ProductClient from nisystemlink.clients.product.models._paged_products import PagedProducts -from nisystemlink.clients.product.models._product_response import ( +from nisystemlink.clients.product.models._product import ( Product, ) from nisystemlink.clients.product.models._query_products_request import ( diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index 837ff808..4fc678ab 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -35,6 +35,12 @@ class UpdateSpecificationsRequestObject(SpecificationDefinition): type: SpecificationType """Type of the specification.""" + workspace: str + """Id of the workspace to which the specification will be associated. + + Default workspace will be taken if the value is not given. + """ + class UpdateSpecificationsRequest(JsonModel): From 110c2da820baa8b5e60e738a019613f02a31c178 Mon Sep 17 00:00:00 2001 From: Sam Rishi Date: Tue, 4 Mar 2025 14:12:39 +0530 Subject: [PATCH 23/24] refactor:SpecsModelsAndProductDocstring --- .../clients/product/models/_product.py | 7 ++- nisystemlink/clients/spec/models/__init__.py | 5 -- .../models/_base_specification_response.py | 24 --------- .../spec/models/_create_specs_request.py | 40 +++++++++++--- .../clients/spec/models/_query_specs.py | 14 +---- .../clients/spec/models/_specification.py | 52 +++++++------------ .../spec/models/_update_specs_request.py | 28 +++++----- 7 files changed, 72 insertions(+), 98 deletions(-) delete mode 100644 nisystemlink/clients/spec/models/_base_specification_response.py diff --git a/nisystemlink/clients/product/models/_product.py b/nisystemlink/clients/product/models/_product.py index 045dba75..c8940d44 100644 --- a/nisystemlink/clients/product/models/_product.py +++ b/nisystemlink/clients/product/models/_product.py @@ -7,10 +7,9 @@ class Product(JsonModel): """Contains information about a product, where all the fields are optional. - - This is because when using query products' projection, user can request for any of the available - fields. So, we are making sure that all the available fields are optional. - - - Also while creating a product it is not mandatory that the product should be created with all the fields. + - This is because when using query products' projection, specific product fields alone can be + queried. The value can be None if no projection is applied and the field is unset, or it can be + None when the field is not included in the projection. """ id: Optional[str] diff --git a/nisystemlink/clients/spec/models/__init__.py b/nisystemlink/clients/spec/models/__init__.py index a33febc6..b30499ec 100644 --- a/nisystemlink/clients/spec/models/__init__.py +++ b/nisystemlink/clients/spec/models/__init__.py @@ -17,17 +17,12 @@ QuerySpecificationsRequest, PagedSpecifications, SpecificationProjection, - QuerySpecificationsResponse, ) from ._specification import ( Specification, - SpecificationCreation, SpecificationDefinition, SpecificationLimit, - SpecificationServerManaged, SpecificationType, - SpecificationUpdated, - SpecificationUserManaged, ) from ._update_specs_request import ( UpdatedSpecification, diff --git a/nisystemlink/clients/spec/models/_base_specification_response.py b/nisystemlink/clients/spec/models/_base_specification_response.py deleted file mode 100644 index 485dac53..00000000 --- a/nisystemlink/clients/spec/models/_base_specification_response.py +++ /dev/null @@ -1,24 +0,0 @@ -from nisystemlink.clients.core._uplink._json_model import JsonModel - - -class BaseSpecificationResponse(JsonModel): - """Base Response Model for create specs response and update specs response.""" - - id: str - """The global Id of the specification.""" - - version: int - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - - product_id: str - """Id of the product to which the specification will be associated.""" - - spec_id: str - """User provided value using which the specification will be identified. - - This should be unique for a product and workspace combination. - """ diff --git a/nisystemlink/clients/spec/models/_create_specs_request.py b/nisystemlink/clients/spec/models/_create_specs_request.py index 2aaa099a..28f58b14 100644 --- a/nisystemlink/clients/spec/models/_create_specs_request.py +++ b/nisystemlink/clients/spec/models/_create_specs_request.py @@ -1,12 +1,9 @@ +from datetime import datetime from typing import List, Optional from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.spec.models._base_specification_response import ( - BaseSpecificationResponse, -) from nisystemlink.clients.spec.models._specification import ( - SpecificationCreation, SpecificationDefinition, SpecificationType, ) @@ -33,15 +30,44 @@ class CreateSpecificationsRequest(JsonModel): """List of specifications to be created.""" -class CreatedSpecification(BaseSpecificationResponse, SpecificationCreation): - """A specification successfully created on the server.""" +class BaseSpecificationResponse(JsonModel): + """Base Response Model for create specs response and update specs response.""" + + id: str + """The global Id of the specification.""" + + product_id: str + """Id of the product to which the specification will be associated.""" - workspace: Optional[str] = None + spec_id: str + """User provided value using which the specification will be identified. + + This should be unique for a product and workspace combination. + """ + + workspace: str """Id of the workspace to which the specification will be associated. Default workspace will be taken if the value is not given. """ + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + + +class CreatedSpecification(BaseSpecificationResponse): + """A specification successfully created on the server.""" + + created_at: datetime + """ISO-8601 formatted timestamp indicating when the specification was created.""" + + created_by: str + """Id of the user who created the specification.""" + class CreateSpecificationsPartialSuccess(JsonModel): """When some specs can not be created, this contains the list that was and was not created.""" diff --git a/nisystemlink/clients/spec/models/_query_specs.py b/nisystemlink/clients/spec/models/_query_specs.py index 32f637e9..cd06dcd6 100644 --- a/nisystemlink/clients/spec/models/_query_specs.py +++ b/nisystemlink/clients/spec/models/_query_specs.py @@ -3,11 +3,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging -from nisystemlink.clients.spec.models._specification import ( - Specification, - SpecificationCreation, - SpecificationUpdated, -) +from nisystemlink.clients.spec.models._specification import Specification class SpecificationProjection(str, Enum): @@ -107,16 +103,10 @@ class QuerySpecificationsRequest(JsonModel): """ -class QuerySpecificationsResponse( - Specification, SpecificationCreation, SpecificationUpdated -): - """A full specification with update and create history.""" - - class PagedSpecifications(WithPaging): """The list of matching specifications and a continuation token to get the next items.""" - specs: Optional[List[QuerySpecificationsResponse]] = None + specs: Optional[List[Specification]] = None """List of queried specifications. An empty list indicates that there are no specifications meeting the criteria provided in the diff --git a/nisystemlink/clients/spec/models/_specification.py b/nisystemlink/clients/spec/models/_specification.py index 64e29f82..36e36fc8 100644 --- a/nisystemlink/clients/spec/models/_specification.py +++ b/nisystemlink/clients/spec/models/_specification.py @@ -40,7 +40,8 @@ class SpecificationType(Enum): """Functional specs only have pass/fail status.""" -class SpecificationUserManaged(JsonModel): +class SpecificationDefinition(JsonModel): + product_id: Optional[str] = None """Id of the product to which the specification will be associated.""" @@ -50,33 +51,15 @@ class SpecificationUserManaged(JsonModel): This should be unique for a product and workspace combination. """ - workspace: Optional[str] = None - """Id of the workspace to which the specification will be associated. - - Default workspace will be taken if the value is not given. - """ - - -class SpecificationServerManaged(JsonModel): - id: Optional[str] = None - """The global Id of the specification.""" - - version: Optional[int] = None - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - - -class SpecificationDefinition(SpecificationUserManaged): - name: Optional[str] = None """Name of the specification.""" category: Optional[str] = None """Category of the specification.""" + type: Optional[SpecificationType] = None + """Type of the specification.""" + symbol: Optional[str] = None """Short form identifier of the specification.""" @@ -101,16 +84,18 @@ class SpecificationDefinition(SpecificationUserManaged): properties: Optional[Dict[str, str]] = None """Additional properties associated with the specification.""" + workspace: Optional[str] = None + """Id of the workspace to which the specification will be associated. -class Specification(SpecificationDefinition, SpecificationServerManaged): - """The complete definition of a specification.""" + Default workspace will be taken if the value is not given. + """ - type: Optional[SpecificationType] = None - """Type of the specification.""" +class Specification(SpecificationDefinition): + """The complete definition of a specification.""" -class SpecificationCreation(JsonModel): - """When the spec was created and when.""" + id: Optional[str] = None + """The global Id of the specification.""" created_at: Optional[datetime] = None """ISO-8601 formatted timestamp indicating when the specification was created.""" @@ -118,12 +103,15 @@ class SpecificationCreation(JsonModel): created_by: Optional[str] = None """Id of the user who created the specification.""" - -class SpecificationUpdated(JsonModel): - """When the spec was updated and when.""" - updated_at: Optional[datetime] = None """ISO-8601 formatted timestamp indicating when the specification was last updated.""" updated_by: Optional[str] = None """Id of the user who last updated the specification.""" + + version: Optional[int] = None + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ diff --git a/nisystemlink/clients/spec/models/_update_specs_request.py b/nisystemlink/clients/spec/models/_update_specs_request.py index 4fc678ab..1301015a 100644 --- a/nisystemlink/clients/spec/models/_update_specs_request.py +++ b/nisystemlink/clients/spec/models/_update_specs_request.py @@ -1,14 +1,14 @@ +from datetime import datetime from typing import List, Optional from nisystemlink.clients.core import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -from nisystemlink.clients.spec.models._base_specification_response import ( +from nisystemlink.clients.spec.models._create_specs_request import ( BaseSpecificationResponse, ) from nisystemlink.clients.spec.models._specification import ( SpecificationDefinition, SpecificationType, - SpecificationUpdated, ) @@ -16,13 +16,6 @@ class UpdateSpecificationsRequestObject(SpecificationDefinition): id: str """The global Id of the specification.""" - version: int - """ - Current version of the specification. - - When an update is applied, the version is automatically incremented. - """ - product_id: str """Id of the product to which the specification will be associated.""" @@ -41,6 +34,13 @@ class UpdateSpecificationsRequestObject(SpecificationDefinition): Default workspace will be taken if the value is not given. """ + version: int + """ + Current version of the specification. + + When an update is applied, the version is automatically incremented. + """ + class UpdateSpecificationsRequest(JsonModel): @@ -48,14 +48,14 @@ class UpdateSpecificationsRequest(JsonModel): """List of specifications to be updated.""" -class UpdatedSpecification(BaseSpecificationResponse, SpecificationUpdated): +class UpdatedSpecification(BaseSpecificationResponse): """A specification that was updated on the server.""" - workspace: str - """Id of the workspace to which the specification will be associated. + updated_at: datetime + """ISO-8601 formatted timestamp indicating when the specification was last updated.""" - Default workspace will be taken if the value is not given. - """ + updated_by: str + """Id of the user who last updated the specification.""" class UpdateSpecificationsPartialSuccess(JsonModel): From 1017a4af7fdba876c191ea193704b5bd82c30ecc Mon Sep 17 00:00:00 2001 From: shri2k2 Date: Tue, 4 Mar 2025 17:22:37 +0530 Subject: [PATCH 24/24] docs: update product doc string --- nisystemlink/clients/product/models/_product.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nisystemlink/clients/product/models/_product.py b/nisystemlink/clients/product/models/_product.py index c8940d44..0435bbac 100644 --- a/nisystemlink/clients/product/models/_product.py +++ b/nisystemlink/clients/product/models/_product.py @@ -5,12 +5,7 @@ class Product(JsonModel): - """Contains information about a product, where all the fields are optional. - - - This is because when using query products' projection, specific product fields alone can be - queried. The value can be None if no projection is applied and the field is unset, or it can be - None when the field is not included in the projection. - """ + """Contains information about a product.""" id: Optional[str] """The globally unique id of the product."""