Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/examples/inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Inventory

Albert Inventory serves as a digital manifestation of your physical inventory. It enables you to sort, filter, trace, and manage all types of inventory.

## Inventory function on CAS

Inventory function is a business-controlled, multi-select list on the Inventory ↔ CAS relationship.
Use it by updating an existing inventory item.

!!! example "Add inventory function values to a CAS entry"
```python
from albert import Albert
from albert.resources.lists import ListItem, ListItemCategory

client = Albert.from_client_credentials()

inventory_id = "INV123"
cas_id = "CAS123"

# Optional: create a new inventoryFunction list item first.
list_item = ListItem(
name="Primary Function",
category=ListItemCategory.INVENTORY,
list_type="inventoryFunction",
)
list_item = client.lists.create(list_item=list_item)

inventory_item = client.inventory.get_by_id(id=inventory_id)
if inventory_item.cas:
for cas_amount in inventory_item.cas:
if cas_amount.id == cas_id:
cas_amount.inventory_function = [list_item]
break

updated_item = client.inventory.update(inventory_item=inventory_item)
print(updated_item.id)
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ nav:
- Worksheets: resources/worksheets.md
- Sheets: resources/sheets.md
- Examples:
- Inventory: examples/inventory.md
- Property Data: examples/property_data.md
- Tasks: examples/tasks.md
- Notebooks: examples/notebook.md
Expand Down
2 changes: 1 addition & 1 deletion src/albert/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__all__ = ["Albert", "AlbertClientCredentials", "AlbertSSOClient"]

__version__ = "1.12.0"
__version__ = "1.13.0"
6 changes: 6 additions & 0 deletions src/albert/resources/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
---
Expand All @@ -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")

Expand Down
21 changes: 12 additions & 9 deletions src/albert/resources/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ class ListItem(BaseResource):
id : str | None
The Albert ID of the list item. Set when the list item is retrieved from Albert.
category : ListItemCategory | None
The category of the list item. Allowed values are `businessDefined`, `userDefined`, `projects`, and `extensions`.
The category of the list item. Allowed values are `businessDefined`, `userDefined`, `projects`, `extensions`,
and `inventory`.
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
Expand All @@ -37,14 +39,15 @@ class ListItem(BaseResource):

@model_validator(mode="after")
def validate_list_type(self) -> ListItem:
allowed_by_category = {
ListItemCategory.PROJECTS: {"projectState"},
ListItemCategory.EXTENSIONS: {"extensions"},
ListItemCategory.INVENTORY: {"casCategory", "inventoryFunction"},
}
if (
self.category == ListItemCategory.PROJECTS
and self.list_type is not None
and self.list_type != "projectState"
) or (
self.category == ListItemCategory.EXTENSIONS
and self.list_type is not None
and self.list_type != "extensions"
self.list_type is not None
and self.category in allowed_by_category
and self.list_type not in allowed_by_category[self.category]
):
raise ValueError(
f"List type {self.list_type} is not allowed for category {self.category}"
Expand Down
71 changes: 71 additions & 0 deletions src/albert/utils/inventory.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions tests/resources/test_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def test_cas_amount_attributes():
"target",
"id",
"cas_category",
"inventory_function",
"created",
"updated",
"classification_type",
Expand Down