diff --git a/src/albert/__init__.py b/src/albert/__init__.py index 297b0d64..3396c193 100644 --- a/src/albert/__init__.py +++ b/src/albert/__init__.py @@ -4,4 +4,4 @@ __all__ = ["Albert", "AlbertClientCredentials", "AlbertSSOClient"] -__version__ = "1.11.0" +__version__ = "1.13.0b1" diff --git a/src/albert/resources/inventory.py b/src/albert/resources/inventory.py index 33ea5003..5fa3290d 100644 --- a/src/albert/resources/inventory.py +++ b/src/albert/resources/inventory.py @@ -14,6 +14,7 @@ from albert.resources.acls import ACL from albert.resources.cas import Cas from albert.resources.companies import Company +from albert.resources.lists import ListItem from albert.resources.locations import Location from albert.resources.tagged_base import BaseTaggedResource from albert.resources.tags import Tag @@ -75,6 +76,8 @@ class CasAmount(BaseAlbertModel): The SMILES string of the CAS Number resource. Obtained from the Cas object when provided. number: str | None The CAS number. Obtained from the Cas object when provided. + inventory_function: list[ListItem | EntityLink | str] | None + Business-controlled functions associated with the CAS in this inventory context. !!! tip --- @@ -87,6 +90,9 @@ class CasAmount(BaseAlbertModel): target: float | None = Field(default=None, alias="inventoryValue") id: str | None = Field(default=None) cas_category: str | None = Field(default=None, alias="casCategory") + inventory_function: list[SerializeAsEntityLink[ListItem] | str] | None = Field( + default=None, alias="inventoryFunction" + ) type: str | None = Field(default=None) classification_type: str | None = Field(default=None, alias="classificationType") diff --git a/src/albert/resources/lists.py b/src/albert/resources/lists.py index d3ff6cf7..7d16dfd8 100644 --- a/src/albert/resources/lists.py +++ b/src/albert/resources/lists.py @@ -27,7 +27,8 @@ class ListItem(BaseResource): category : ListItemCategory | None The category of the list item. Allowed values are `businessDefined`, `userDefined`, `projects`, and `extensions`. list_type : str | None - The type of the list item. Allowed values are `projectState` for `projects` and `extensions` for `extensions`. + The type of the list item. Allowed values are `projectState` for `projects`, `extensions` for `extensions`, + and `casCategory` or `inventoryFunction` for `inventory`. """ name: str diff --git a/src/albert/utils/inventory.py b/src/albert/utils/inventory.py index 651c7d1e..af6e14ef 100644 --- a/src/albert/utils/inventory.py +++ b/src/albert/utils/inventory.py @@ -1,6 +1,7 @@ from collections.abc import Iterable from typing import Any +from albert.core.shared.models.base import BaseResource, EntityLink from albert.resources.inventory import CasAmount @@ -56,6 +57,61 @@ def _build_cas_delete_operation(identifier: str) -> dict[str, Any]: } +def _normalize_inventory_function_ids( + value: list[BaseResource | EntityLink | str] | None, +) -> list[str]: + if not value: + return [] + ids: list[str] = [] + for item in value: + if isinstance(item, str): + if item: + ids.append(item) + continue + if isinstance(item, BaseResource): + if item.id: + ids.append(item.id) + continue + if isinstance(item, EntityLink): + if item.id: + ids.append(item.id) + continue + return ids + + +def _build_inventory_function_operations( + *, + entity_id: str, + existing: list[BaseResource | EntityLink | str] | None, + updated: list[BaseResource | EntityLink | str] | None, +) -> list[dict[str, Any]]: + existing_ids = set(_normalize_inventory_function_ids(existing)) + updated_ids = set(_normalize_inventory_function_ids(updated)) + to_add = sorted(updated_ids - existing_ids) + to_delete = sorted(existing_ids - updated_ids) + + operations: list[dict[str, Any]] = [] + if to_add: + operations.append( + { + "attribute": "inventoryFunction", + "entityId": entity_id, + "operation": "add", + "newValue": to_add, + } + ) + if to_delete: + operations.append( + { + "attribute": "inventoryFunction", + "entityId": entity_id, + "operation": "delete", + "oldValue": to_delete, + } + ) + return operations + + def _build_cas_scalar_operation( *, attribute: str, @@ -117,6 +173,14 @@ def _build_cas_update_operations(existing: CasAmount, updated: CasAmount) -> lis if operation is not None: operations.append(operation) + operations.extend( + _build_inventory_function_operations( + entity_id=identifier, + existing=existing.inventory_function, + updated=updated.inventory_function, + ) + ) + return operations @@ -159,6 +223,13 @@ def _build_cas_patch_operations( ) if target_operation is not None: operations.append(target_operation) + operations.extend( + _build_inventory_function_operations( + entity_id=identifier, + existing=None, + updated=cas_amount.inventory_function, + ) + ) removals = [existing_lookup[key] for key in existing_lookup.keys() - updated_lookup.keys()] for cas_amount in removals: diff --git a/tests/resources/test_inventory.py b/tests/resources/test_inventory.py index a43b237b..03a4be09 100644 --- a/tests/resources/test_inventory.py +++ b/tests/resources/test_inventory.py @@ -29,6 +29,7 @@ def test_cas_amount_attributes(): "target", "id", "cas_category", + "inventory_function", "created", "updated", "classification_type",