From 998db974fc336546a2b023e5c89ac5ae4e28abc3 Mon Sep 17 00:00:00 2001 From: Fred Visser <1458528+fredvisser@users.noreply.github.com> Date: Tue, 26 May 2026 17:01:03 -0500 Subject: [PATCH] Make alias handling consistent and clean up lint findings --- .../clients/assetmanagement/models/_asset.py | 20 +++- .../assetmanagement/models/_asset_location.py | 22 ++-- .../models/_create_asset_request.py | 12 +- .../clients/core/_uplink/_json_model.py | 15 ++- .../clients/file/models/_file_metadata.py | 9 +- .../file/models/_file_query_response.py | 9 +- .../models/_upload_session_start_response.py | 7 +- .../clients/notebook/models/_execution.py | 34 ++++-- .../product/models/_query_products_request.py | 27 +++-- .../models/_query_results_request.py | 43 +++++-- tests/core/test_json_model.py | 107 ++++++++++++++++++ .../assetmanagement/test_asset_management.py | 17 +-- 12 files changed, 261 insertions(+), 61 deletions(-) create mode 100644 tests/core/test_json_model.py diff --git a/nisystemlink/clients/assetmanagement/models/_asset.py b/nisystemlink/clients/assetmanagement/models/_asset.py index 690b3fcc..9275bb1e 100644 --- a/nisystemlink/clients/assetmanagement/models/_asset.py +++ b/nisystemlink/clients/assetmanagement/models/_asset.py @@ -2,7 +2,7 @@ from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field from ._asset_calibration import ( CalibrationStatus, @@ -68,17 +68,25 @@ class Asset(JsonModel): self_calibration: SelfCalibration | None = None """Gets or sets the last self-calibration of the asset.""" - is_NI_asset: bool | None = Field(alias="isNIAsset", default=None) + is_ni_asset: bool | None = Field( + default=None, + validation_alias=AliasChoices("is_ni_asset", "is_NI_asset", "isNIAsset"), + serialization_alias="isNIAsset", + ) """Gets or sets whether this asset is an NI asset (true) or a third-party asset (false).""" id: str | None = None """Gets or sets unique identifier of the asset.""" location: AssetLocation | None = None - """Model for information about the asset location, presence and the connection status of the system""" + """Model for information about the asset location, presence, + and the connection status of the system. + """ calibration_status: CalibrationStatus | None = None - """Gets or sets the calibration category the asset belongs to based on the next due calibration date.""" + """Gets or sets the calibration category the asset belongs to + based on the next due calibration date. + """ is_system_controller: bool | None = None """Gets or sets whether this asset represents a System Controller.""" @@ -96,7 +104,9 @@ class Asset(JsonModel): """Gets or sets words or phrases associated with an asset.""" last_updated_timestamp: datetime | None = None - """Gets or sets ISO-8601 formatted timestamp specifying the last date that the asset has had a property update.""" + """Gets or sets an ISO-8601 timestamp for the last date + that the asset had a property update. + """ file_ids: List[str] | None = None """Gets or sets all files linked to the asset.""" diff --git a/nisystemlink/clients/assetmanagement/models/_asset_location.py b/nisystemlink/clients/assetmanagement/models/_asset_location.py index bb35ccb9..67f7c22b 100644 --- a/nisystemlink/clients/assetmanagement/models/_asset_location.py +++ b/nisystemlink/clients/assetmanagement/models/_asset_location.py @@ -13,10 +13,12 @@ class AssetPresenceStatus(Enum): class SystemConnection(Enum): - """Whether or not the minion is connected to the server and has updated the server with its data. - To maintain compatibility with previous versions of SystemLink, the values - [APPROVED, UNSUPPORTED, ACTIVATED] are considered equivalent to DISCONNECTED and - [CONNECTED_UPDATE_PENDING, CONNECTED_UPDATE_SUCCESSFUL, CONNECTED_UPDATE_FAILED] are equivalent to CONNECTED. + """Whether the minion is connected to the server. + + For backward compatibility, APPROVED, UNSUPPORTED, and ACTIVATED are + treated as DISCONNECTED. CONNECTED_UPDATE_PENDING, + CONNECTED_UPDATE_SUCCESSFUL, and CONNECTED_UPDATE_FAILED are treated + as CONNECTED. """ APPROVED = "APPROVED" @@ -30,13 +32,15 @@ class SystemConnection(Enum): class AssetPresenceWithSystemConnection(JsonModel): - """Model for the presence of an asset and the connection of the system in which it resides.""" + """Asset presence and system connection information.""" asset_presence: AssetPresenceStatus """Gets or sets the status of an asset's presence in a system.""" system_connection: SystemConnection | None = None - """Gets or sets whether or not the minion is connected to the server and has updated the server with its data.""" + """Gets or sets whether the minion is connected to the server + and has updated it with its data. + """ class AssetPresence(JsonModel): @@ -47,7 +51,7 @@ class AssetPresence(JsonModel): class _AssetLocation(JsonModel): - """local model for information about the asset location, presence and the connection status of the system.""" + """Local model for asset location and presence information.""" minion_id: str | None = None """Gets or sets identifier of the minion where the asset is located.""" @@ -66,14 +70,14 @@ class _AssetLocation(JsonModel): class AssetLocation(_AssetLocation): - """Model for information about the asset location, presence and the connection status of the system.""" + """Asset location and system connection information.""" state: AssetPresenceWithSystemConnection """Presence of an asset and the connection of the system in which it resides.""" class AssetLocationForCreate(_AssetLocation): - """Model for information about the asset presence status of the system, used while create""" + """Asset presence information used during create.""" state: AssetPresence """Model for the presence of an asset.""" diff --git a/nisystemlink/clients/assetmanagement/models/_create_asset_request.py b/nisystemlink/clients/assetmanagement/models/_create_asset_request.py index b8a29357..325d67ab 100644 --- a/nisystemlink/clients/assetmanagement/models/_create_asset_request.py +++ b/nisystemlink/clients/assetmanagement/models/_create_asset_request.py @@ -1,7 +1,7 @@ from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field from ._asset import ( AssetBusType, @@ -65,14 +65,20 @@ class CreateAssetRequest(JsonModel): self_calibration: SelfCalibration | None = None """Gets or sets the last self-calibration of the asset.""" - is_NI_asset: bool | None = Field(alias="isNIAsset", default=None) + is_ni_asset: bool | None = Field( + default=None, + validation_alias=AliasChoices("is_ni_asset", "is_NI_asset", "isNIAsset"), + serialization_alias="isNIAsset", + ) """Gets or sets whether this asset is an NI asset (true) or a third-party asset (false).""" workspace: str | None = None """Gets or sets the ID of the workspace.""" location: AssetLocationForCreate - """Model for information about the asset location, presence and the connection status of the system""" + """Model for information about the asset location, presence, + and the connection status of the system. + """ external_calibration: ExternalCalibration | None = None """Gets or sets the last external calibration of the asset.""" diff --git a/nisystemlink/clients/core/_uplink/_json_model.py b/nisystemlink/clients/core/_uplink/_json_model.py index e1643c43..80ed7f7d 100644 --- a/nisystemlink/clients/core/_uplink/_json_model.py +++ b/nisystemlink/clients/core/_uplink/_json_model.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict +from pydantic import AliasChoices, AliasGenerator, BaseModel, ConfigDict def _camelcase(s: str) -> str: @@ -7,11 +7,22 @@ def _camelcase(s: str) -> str: return next(parts) + "".join(i.title() for i in parts) +def _validation_aliases(s: str) -> str | AliasChoices: + """Accept both Python snake_case and wire-format camelCase inputs.""" + camelcase = _camelcase(s) + if camelcase == s: + return s + return AliasChoices(s, camelcase) + + class JsonModel(BaseModel): """Base class for models that are serialized to and from JSON.""" model_config = ConfigDict( - alias_generator=_camelcase, + alias_generator=AliasGenerator( + validation_alias=_validation_aliases, + serialization_alias=_camelcase, + ), validate_by_name=True, validate_by_alias=True, extra="ignore", diff --git a/nisystemlink/clients/file/models/_file_metadata.py b/nisystemlink/clients/file/models/_file_metadata.py index 680572a6..04583286 100644 --- a/nisystemlink/clients/file/models/_file_metadata.py +++ b/nisystemlink/clients/file/models/_file_metadata.py @@ -2,7 +2,7 @@ from typing import Dict from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field from ._link import Link @@ -53,8 +53,13 @@ class BaseFileMetadata(JsonModel): class FileMetadata(BaseFileMetadata): + """Metadata for a file.""" - field_links: Dict[str, Link] | None = Field(None, alias="_links") + field_links: Dict[str, Link] | None = Field( + None, + validation_alias=AliasChoices("field_links", "_links"), + serialization_alias="_links", + ) """ The links to access and manipulate the file: - data: Link to download the file using a GET request diff --git a/nisystemlink/clients/file/models/_file_query_response.py b/nisystemlink/clients/file/models/_file_query_response.py index f52e520c..f2bf79c3 100644 --- a/nisystemlink/clients/file/models/_file_query_response.py +++ b/nisystemlink/clients/file/models/_file_query_response.py @@ -3,16 +3,19 @@ from typing import Dict, List from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field from ._file_metadata import FileMetadata from ._link import Link class FileQueryResponse(JsonModel): - """The result of a file query""" + """The result of a file query.""" - field_links: Dict[str, Link] = Field(alias="_links") + field_links: Dict[str, Link] = Field( + validation_alias=AliasChoices("field_links", "_links"), + serialization_alias="_links", + ) """The links that apply to the collection of files for a service group: - deleteFiles: Link to delete multiple files from the service group using a POST - query: Link to query for available files in the service group using a POST diff --git a/nisystemlink/clients/file/models/_upload_session_start_response.py b/nisystemlink/clients/file/models/_upload_session_start_response.py index c87357c7..0f87e47d 100644 --- a/nisystemlink/clients/file/models/_upload_session_start_response.py +++ b/nisystemlink/clients/file/models/_upload_session_start_response.py @@ -1,13 +1,16 @@ from datetime import datetime from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field class UploadSessionStartResponse(JsonModel): """Response model for starting an upload session.""" - session_id: str = Field(alias="id") + session_id: str = Field( + validation_alias=AliasChoices("session_id", "id"), + serialization_alias="id", + ) """ The id created for the upload session. """ diff --git a/nisystemlink/clients/notebook/models/_execution.py b/nisystemlink/clients/notebook/models/_execution.py index 6ee90677..c49d967a 100644 --- a/nisystemlink/clients/notebook/models/_execution.py +++ b/nisystemlink/clients/notebook/models/_execution.py @@ -3,11 +3,11 @@ from typing import Any, Dict from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field class SourceType(str, Enum): - """Source type of an execution""" + """Source type of an execution.""" MANUAL = "MANUAL" @@ -15,7 +15,7 @@ class SourceType(str, Enum): class Source(JsonModel): - """An object that defines properties set by routine service""" + """An object that defines properties set by routine service.""" type: SourceType """Source type of an execution""" @@ -38,13 +38,13 @@ class ReportType(str, Enum): class ReportSettings(JsonModel): - """A class that defines settings of the Report""" + """A class that defines settings of the Report.""" format: ReportType """Type for the report that is going to be generated.""" exclude_code: bool - """Boolean parameter that will define if the source code should be included in the report or not.""" + """Whether the source code should be included in the report.""" class ExecutionPriority(str, Enum): @@ -58,6 +58,7 @@ class ExecutionPriority(str, Enum): class ExecutionResourceProfile(str, Enum): + """Resource profile of the execution. Can be one of Low, Medium, High or Default.""" DEFAULT = "DEFAULT" @@ -109,7 +110,10 @@ class ExecutionErrorCode(str, Enum): class Execution(JsonModel): - """Information about an execution of a Jupyter notebook that has the cachedResult field added.""" + """Jupyter notebook execution information. + + Includes the cachedResult field. + """ id: str | None = None """The ID of the execution.""" @@ -117,22 +121,30 @@ class Execution(JsonModel): notebook_id: str | None = None """The ID of the executed notebook.""" - organization_id: str | None = Field(None, alias="orgId") + organization_id: str | None = Field( + None, + validation_alias=AliasChoices("organization_id", "orgId"), + serialization_alias="orgId", + ) """The org ID of the user creating the request.""" user_id: str | None = None """The user ID of the user creating the request.""" parameters: Dict[str, Any] | None = None - """The input parameters for this execution of the notebook. The keys are strings and the values can be of any - valid JSON type.""" + """The input parameters for this execution. + + The keys are strings and the values can be any valid JSON type. + """ workspace_id: str | None = None """The ID of the workspace this execution belongs to.""" timeout: int | None = None - """The number of seconds the execution runs before it aborts if uncompleted. The timer starts once status is - IN_PROGRESS. 0 means infinite.""" + """The number of seconds the execution runs before it aborts. + + The timer starts once status is IN_PROGRESS. 0 means infinite. + """ status: ExecutionStatus | None = None """Status of an execution.""" diff --git a/nisystemlink/clients/product/models/_query_products_request.py b/nisystemlink/clients/product/models/_query_products_request.py index 9f119ec8..d1ca795d 100644 --- a/nisystemlink/clients/product/models/_query_products_request.py +++ b/nisystemlink/clients/product/models/_query_products_request.py @@ -2,7 +2,7 @@ from typing import List from nisystemlink.clients.core._uplink._json_model import JsonModel -from pydantic import Field +from pydantic import AliasChoices, Field class ProductOrderBy(str, Enum): @@ -26,8 +26,9 @@ class ProductField(str, Enum): class ProductProjection(str, Enum): - """An enumeration of all fields in a Product. These are used to project the required fields - from the API response. + """An enumeration of all fields in a Product. + + These are used to project the required fields from the API response. """ ID = "ID" @@ -42,6 +43,8 @@ class ProductProjection(str, Enum): class QueryProductsBase(JsonModel): + """Base class for product query requests.""" + filter: str | None = None """ The product query filter in Dynamic Linq format. @@ -56,8 +59,8 @@ class QueryProductsBase(JsonModel): - `properties`: A dictionary of additional string to string properties - `fileIds`: A list of string ids for files stored in the file service (`/nifile`) - See [Dynamic Linq](https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language) - documentation for more details. + See the Dynamic Linq query language documentation: + https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language `"@0"`, `"@1"` etc. can be used in conjunction with the `substitutions` parameter to keep this query string more simple and reusable. @@ -75,8 +78,13 @@ class QueryProductsBase(JsonModel): class QueryProductsRequest(QueryProductsBase): + """Request model for querying products.""" - order_by: ProductOrderBy | None = Field(None, alias="orderBy") + order_by: ProductOrderBy | None = Field( + None, + validation_alias=AliasChoices("order_by", "orderBy"), + serialization_alias="orderBy", + ) """Specifies the fields to use to sort the products. By default, products are sorted by `id` @@ -91,8 +99,9 @@ class QueryProductsRequest(QueryProductsBase): projection: List[ProductProjection] | None = 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 + 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. """ @@ -120,6 +129,8 @@ class QueryProductsRequest(QueryProductsBase): class QueryProductValuesRequest(QueryProductsBase): + """Request model for querying product values.""" + field: ProductField | None = None """The product field to return for this query.""" diff --git a/nisystemlink/clients/testmonitor/models/_query_results_request.py b/nisystemlink/clients/testmonitor/models/_query_results_request.py index eb4c4b8e..ed14177a 100644 --- a/nisystemlink/clients/testmonitor/models/_query_results_request.py +++ b/nisystemlink/clients/testmonitor/models/_query_results_request.py @@ -3,7 +3,7 @@ from nisystemlink.clients.core._uplink._json_model import JsonModel from nisystemlink.clients.core._uplink._with_paging import WithPaging -from pydantic import Field +from pydantic import AliasChoices, Field class ResultField(str, Enum): @@ -74,6 +74,8 @@ class ResultProjection(str, Enum): class QueryResultsBase(JsonModel): + """Base class for result query requests.""" + filter: str | None = None """ The result query filter in Dynamic Linq format. @@ -94,9 +96,12 @@ class QueryResultsBase(JsonModel): - `keywords`: A list of keyword strings - `properties`: A dictionary of additional string to string properties - `fileIds`: A list of string ids for files stored in the file service (`/nifile`) - - `dataTableIds`: A list of string ids for data tables stored in the data frame service (`/nidataframe`) + - `dataTableIds`: A list of string ids for data tables stored in the + data frame service (`/nidataframe`) - `workspaceId`: String for the workspace identifier of the result - See [Dynamic Linq](https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language) + + See the Dynamic Linq query language documentation: + https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language documentation for more details. `"@0"`, `"@1"` etc. can be used in conjunction with the `substitutions` parameter to keep this query string more simple and reusable. @@ -113,6 +118,8 @@ class QueryResultsBase(JsonModel): class QueryProductsBase(JsonModel): + """Base class for product query requests.""" + product_filter: str | None = None """ The product query filter in Dynamic Linq format. @@ -125,7 +132,9 @@ class QueryProductsBase(JsonModel): - `keywords`: A list of keyword strings - `properties`: A dictionary of additional string to string properties - `fileIds`: A list of string ids for files stored in the file service (`/nifile`) - See [Dynamic Linq](https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language) + + See the Dynamic Linq query language documentation: + https://github.com/ni/systemlink-OpenAPI-documents/wiki/Dynamic-Linq-Query-Language documentation for more details. `"@0"`, `"@1"` etc. can be used in conjunction with the `substitutions` parameter to keep this query string more simple and reusable. @@ -142,20 +151,36 @@ class QueryProductsBase(JsonModel): class QueryResultsRequest(QueryResultsBase, QueryProductsBase, WithPaging): + """Request model for querying results.""" - order_by: ResultOrderByField | None = Field(None, alias="orderBy") + order_by: ResultOrderByField | None = Field( + None, + validation_alias=AliasChoices("order_by", "orderBy"), + serialization_alias="orderBy", + ) """Specifies the fields to use to sort the results. By default, results are sorted by `id` """ - order_by_key: str | None = Field(None, alias="orderByKey") + order_by_key: str | None = Field( + None, + validation_alias=AliasChoices("order_by_key", "orderByKey"), + serialization_alias="orderByKey", + ) """Specifies the property to use to sort the results when ordering by PROPERTIES. Results that do not contain the orderByKey will be considered the smallest value. """ order_by_comparison_type: ComparisonType | None = Field( - None, alias="orderByComparisonType" + None, + validation_alias=AliasChoices( + "order_by_comparison_type", + "orderByComparisonType", + ), + serialization_alias="orderByComparisonType", ) """An enumeration of comparison types that can be used for ordered queries. - For non-DEFAULT comparisons, values that cannot be converted will be considered the smallest value. + + For non-DEFAULT comparisons, values that cannot be converted + will be considered the smallest value. """ descending: bool | None = None """Specifies whether to return the results in descending order. @@ -177,6 +202,8 @@ class QueryResultsRequest(QueryResultsBase, QueryProductsBase, WithPaging): class QueryResultValuesRequest(QueryResultsBase): + """Request model for querying result values.""" + field: ResultField | None = None """The result field to return for this query.""" diff --git a/tests/core/test_json_model.py b/tests/core/test_json_model.py new file mode 100644 index 00000000..7e380401 --- /dev/null +++ b/tests/core/test_json_model.py @@ -0,0 +1,107 @@ +"""Tests for JsonModel alias handling.""" + +from inspect import signature +from typing import Annotated, cast, Literal + +from nisystemlink.clients.core._uplink._json_model import JsonModel +from pydantic import AliasChoices, Field, TypeAdapter + + +class ExampleJsonModel(JsonModel): + """Simple model used to verify JsonModel alias behavior.""" + + program_name: str + + +class ExampleExplicitAliasJsonModel(JsonModel): + """Simple model used to verify explicit alias behavior.""" + + program_name: str + session_id: str = Field( + validation_alias=AliasChoices("session_id", "id"), + serialization_alias="id", + ) + + +class TestJsonModel: + """Test cases for JsonModel alias behavior.""" + + def test__signature__uses_snake_case_field_names(self): + """Test that constructor signatures expose Pythonic snake_case names.""" + assert str(signature(ExampleJsonModel)) == "(*, program_name: str) -> None" + + def test__snake_case_input__deserializes_and_serializes_by_alias(self): + """Test that snake_case input remains valid and serializes to camelCase.""" + model = ExampleJsonModel(program_name="My Program") + + assert model.program_name == "My Program" + assert model.model_dump() == {"program_name": "My Program"} + assert model.model_dump(by_alias=True) == {"programName": "My Program"} + + def test__camel_case_input__remains_backward_compatible(self): + """Test that legacy camelCase input remains valid.""" + model = ExampleJsonModel.model_validate({"programName": "My Program"}) + + assert model.program_name == "My Program" + + def test__snake_case_input__wins_when_both_names_are_provided(self): + """Test that Python field names remain the primary input form.""" + model = ExampleJsonModel.model_validate( + { + "program_name": "Preferred Program", + "programName": "Legacy Program", + } + ) + + assert model.program_name == "Preferred Program" + + def test__explicit_alias_signature__uses_snake_case_field_names(self): + """Test that explicit aliases preserve snake_case constructor signatures.""" + assert str(signature(ExampleExplicitAliasJsonModel)) == ( + "(*, program_name: str, session_id: str) -> None" + ) + assert ExampleExplicitAliasJsonModel.model_fields["session_id"].alias is None + + def test__explicit_alias__accepts_both_input_names_and_serializes_wire_name(self): + """Test that explicit aliases accept Python and wire names and serialize wire names.""" + snake_case_model = ExampleExplicitAliasJsonModel( + program_name="My Program", + session_id="session-1", + ) + camel_case_model = ExampleExplicitAliasJsonModel.model_validate( + {"program_name": "My Program", "id": "session-2"} + ) + + assert snake_case_model.session_id == "session-1" + assert snake_case_model.model_dump(by_alias=True) == { + "programName": "My Program", + "id": "session-1", + } + assert camel_case_model.session_id == "session-2" + + def test__identical_wire_and_python_names__do_not_create_duplicate_aliases(self): + """Test that identical Python and wire names remain valid for discriminated unions.""" + + class NotebookExecution(JsonModel): + type: Literal["NOTEBOOK"] = Field(default="NOTEBOOK") + + class JobExecution(JsonModel): + type: Literal["JOB"] = Field(default="JOB") + + execution_type = Annotated[ + NotebookExecution | JobExecution, + Field(discriminator="type"), + ] + adapter = TypeAdapter(list[execution_type]) + + parsed = cast( + list[NotebookExecution | JobExecution], + adapter.validate_python( + [ + {"type": "NOTEBOOK"}, + {"type": "JOB"}, + ] + ), + ) + + assert [item.type for item in parsed] == ["NOTEBOOK", "JOB"] diff --git a/tests/integration/assetmanagement/test_asset_management.py b/tests/integration/assetmanagement/test_asset_management.py index db8fc0f8..99645fc6 100644 --- a/tests/integration/assetmanagement/test_asset_management.py +++ b/tests/integration/assetmanagement/test_asset_management.py @@ -62,13 +62,13 @@ def _create_assets( @pytest.fixture(scope="class") def client(enterprise_config: HttpConfiguration) -> AssetManagementClient: - """Fixture to create a AssetManagementClient instance""" + """Fixture to create an AssetManagementClient instance.""" return AssetManagementClient(enterprise_config) @pytest.fixture def unique_identifier() -> str: - """Fixture to generate a unique identifier using UUID""" + """Fixture to generate a unique identifier using UUID.""" return str(uuid4()) @@ -123,9 +123,10 @@ def _start_utilization( @pytest.mark.integration @pytest.mark.enterprise class TestAssetManagement: + """Integration tests for the asset management client.""" + _workspace = "2300760d-38c4-48a1-9acb-800260812337" - """Used the main-test default workspace since the client - for creating a workspace has not been added yet""" + """Use the main-test default workspace until workspace creation is supported.""" _create_assets_request = [ CreateAssetRequest( @@ -148,7 +149,7 @@ class TestAssetManagement: is_limited=False, date=datetime(2022, 6, 7, 18, 58, 5, tzinfo=timezone.utc), ), - is_NI_asset=True, + is_ni_asset=True, workspace=_workspace, location=AssetLocationForCreate( state=AssetPresence(asset_presence=AssetPresenceStatus.PRESENT) @@ -312,7 +313,7 @@ def test_query_assets_with_projections__returns_the_assets_with_projected_proper and asset.file_ids is None and asset.firmware_version is None and asset.hardware_version is None - and asset.is_NI_asset is None + and asset.is_ni_asset is None and asset.is_system_controller is None and asset.keywords is None and asset.last_updated_timestamp is None @@ -531,7 +532,7 @@ def test__query_asset_utilization_history__returns_response( assert isinstance(utilization.end_timestamp, datetime) assert isinstance(utilization.heartbeat_timestamp, datetime) - def test__start_utilization_with_nonexistent_asset__raises_ApiException( + def test__start_utilization_with_nonexistent_asset__raises_api_exception( self, client: AssetManagementClient, unique_identifier: str ): start_request = StartUtilizationRequest( @@ -576,7 +577,7 @@ def test__end_utilization_with_nonexistent_utilization_id__returns_empty_list( assert response.updated_utilization_ids is not None assert len(response.updated_utilization_ids) == 0 - def test__query_utilization_history_with_invalid_filter__raises_ApiException( + def test__query_utilization_history_with_invalid_filter__raises_api_exception( self, client: AssetManagementClient ): query_request = QueryAssetUtilizationHistoryRequest(