From 31011183e523003e8267ffd72e21993382cc2e04 Mon Sep 17 00:00:00 2001 From: Prasad Date: Tue, 13 Jan 2026 15:06:39 +0530 Subject: [PATCH 1/2] feat: inventory function on cas --- src/albert/__init__.py | 2 +- src/albert/collections/inventory.py | 1 - src/albert/resources/inventory.py | 6 +++ src/albert/resources/lists.py | 3 +- src/albert/utils/inventory.py | 71 +++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 3 deletions(-) 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/collections/inventory.py b/src/albert/collections/inventory.py index b4a8bf1a..07c47324 100644 --- a/src/albert/collections/inventory.py +++ b/src/albert/collections/inventory.py @@ -841,7 +841,6 @@ def update(self, *, inventory_item: InventoryItem) -> InventoryItem: patch_payload = self._generate_inventory_patch_payload( existing=current_object, updated=inventory_item ) - # Complex patching does not work for some fields, so I'm going to do this in a loop :( # https://teams.microsoft.com/l/message/19:de4a48c366664ce1bafcdbea02298810@thread.tacv2/1724856117312?tenantId=98aab90e-764b-48f1-afaa-02e3c7300653&groupId=35a36a3d-fc25-4899-a1dd-ad9c7d77b5b3&parentMessageId=1724856117312&teamName=Product%20%2B%20Engineering&channelName=General%20-%20API&createdTime=1724856117312 url = f"{self.base_path}/{inventory_item.id}" 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: From d27ec65020acf30cecfe9a459b8c7d877f59250b Mon Sep 17 00:00:00 2001 From: Prasad Date: Tue, 13 Jan 2026 18:05:22 +0530 Subject: [PATCH 2/2] fix: tests --- src/albert/collections/inventory.py | 1 + tests/resources/test_inventory.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/albert/collections/inventory.py b/src/albert/collections/inventory.py index 07c47324..b4a8bf1a 100644 --- a/src/albert/collections/inventory.py +++ b/src/albert/collections/inventory.py @@ -841,6 +841,7 @@ def update(self, *, inventory_item: InventoryItem) -> InventoryItem: patch_payload = self._generate_inventory_patch_payload( existing=current_object, updated=inventory_item ) + # Complex patching does not work for some fields, so I'm going to do this in a loop :( # https://teams.microsoft.com/l/message/19:de4a48c366664ce1bafcdbea02298810@thread.tacv2/1724856117312?tenantId=98aab90e-764b-48f1-afaa-02e3c7300653&groupId=35a36a3d-fc25-4899-a1dd-ad9c7d77b5b3&parentMessageId=1724856117312&teamName=Product%20%2B%20Engineering&channelName=General%20-%20API&createdTime=1724856117312 url = f"{self.base_path}/{inventory_item.id}" 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",