Skip to content

Inventory

albert.collections.inventory.InventoryCollection

InventoryCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage Inventory Items in the Albert platform.

An Inventory Item is a catalog entry for a physical or formulated material tracked in Albert. Every item belongs to one of four categories:

  • RawMaterials: purchased substances used as ingredients (e.g. a solvent or pigment), typically linked to a manufacturing Company and one or more CAS numbers.
  • Consumables: supplies consumed during lab work (e.g. gloves, vials).
  • Equipment: instruments and apparatus.
  • Formulas: mixtures designed in Albert. A Formula item is created under its parent Project by passing project_id to create.

Inventory Items are referenced throughout the platform by their Inventory ID (format INV..., e.g. "INVA9999999"). They are the building blocks that Worksheets, Tasks, and Property Data all point back to.

This collection is accessed as client.inventory.

Example

from albert import Albert
from albert.resources.inventory import InventoryCategory
client = Albert()
# Find raw materials mentioning "titanium dioxide"
items = client.inventory.get_all(
    text="titanium dioxide",
    category=InventoryCategory.RAW_MATERIALS,
    max_items=25,
)
for item in items:
    print(item.id, item.name)

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

required

Attributes:

Name Type Description
base_path str

The base API route for inventory requests.

Methods:

Name Description
create

Create a new inventory item (raw material, consumable, equipment, or formula).

get_by_id

Get a single fully populated item by its ID.

get_by_ids

Get many items by their IDs in batches.

search

Fast, lightweight search returning partial items (best for lookups/counts).

get_all

Same filters as search, but returns fully populated items (slower).

update

Update an existing item.

update_many

Update multiple items, fetching current state in one batched call.

delete

Delete an item by its ID.

reactivate

Reactivate a deleted item by its ID.

merge

Merge duplicate item(s) into a single parent item.

exists

Check whether an item with the same name and company already exists.

get_match_or_none

Return the existing item matching name + company, or None.

add_specs

Attach inventory reference specs to an item (deprecated; prefer client.attributes).

get_specs

Get inventory reference specs for items (deprecated; prefer client.attributes).

get_all_facets

Get facet groups (aggregated filter counts) for a query.

get_facet_by_name

Get a single named facet group for a query.

get_instructions

Get a formula's batch instruction steps, filtered to one formula or one author.

get_instruction_by_id

Get a single instruction by its ID.

get_instructions_by_parent_ids

Get the instructions of several formulas in one call.

create_instruction

Add a batch instruction step to a formula.

update_instruction

Update an existing instruction's text.

delete_instruction

Delete an instruction from a formula.

update_instruction_row_sequence

Reorder the instructions within one row of a formula.

copy_instructions

Copy a formula's instructions and their order to other formulas.

get_instruction_layout

Get a formula's batch instruction procedure: ingredients, stages, and readings.

get_instruction_sequence

Get the row order of a formula's procedure.

update_instruction_sequence

Move a parameter group row to a new position in the row order.

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

required
Source code in src/albert/collections/inventory.py
def __init__(self, *, session: AlbertSession):
    """Initialize an InventoryCollection.

    Parameters
    ----------
    session : AlbertSession
        The authenticated Albert session used for API calls.
    """
    super().__init__(session=session)
    self.base_path = f"/api/{InventoryCollection._api_version}/inventories"
    self._instructions_base_path = f"/api/{InventoryCollection._api_version}/instructions"

base_path

base_path = (
    f"/api/{InventoryCollection._api_version}/inventories"
)

merge

merge(
    *,
    parent_id: InventoryId,
    child_id: InventoryId | list[InventoryId],
    modules: list[InventoryMergeModule] | None = None,
) -> None

Merge one or more duplicate inventory items into a single parent item.

Use this to consolidate duplicates: the child item(s) are folded into the parent, and their data (as selected by modules) is carried over. The child items are removed as standalone entries.

Example

client.inventory.merge(parent_id="INVA9999999", child_id=["INVA9999998", "INVA9999997"])

Parameters:

Name Type Description Default
parent_id InventoryId

The item to keep. All merged data ends up here.

required
child_id InventoryId or list[InventoryId]

The duplicate item(s) to merge into the parent. At least one is required.

required
modules list[InventoryMergeModule]

Which categories of data to carry over from the children (e.g. pricing, notes). Defaults to all modules.

None

Returns:

Type Description
None
Source code in src/albert/collections/inventory.py
@validate_call
def merge(
    self,
    *,
    parent_id: InventoryId,
    child_id: InventoryId | list[InventoryId],
    modules: list[InventoryMergeModule] | None = None,
) -> None:
    """Merge one or more duplicate inventory items into a single parent item.

    Use this to consolidate duplicates: the child item(s) are folded into the
    parent, and their data (as selected by ``modules``) is carried over. The
    child items are removed as standalone entries.

    !!! example
        ```python
        client.inventory.merge(parent_id="INVA9999999", child_id=["INVA9999998", "INVA9999997"])
        ```

    Parameters
    ----------
    parent_id : InventoryId
        The item to keep. All merged data ends up here.
    child_id : InventoryId or list[InventoryId]
        The duplicate item(s) to merge into the parent. At least one is required.
    modules : list[InventoryMergeModule], optional
        Which categories of data to carry over from the children (e.g. pricing,
        notes). Defaults to all modules.

    Returns
    -------
    None
    """

    # assume "all" modules if not specified explicitly
    modules = modules if modules is not None else ALL_MERGE_MODULES

    # define merge endpoint
    url = f"{self.base_path}/merge"

    child_ids = ensure_list(child_id) or []
    if not child_ids:
        raise ValueError("At least one child inventory id is required for merge operations.")
    child_inventories = [{"id": i} for i in child_ids]

    # define payload using the class
    payload = MergeInventory(
        parent_id=parent_id,
        child_inventories=child_inventories,
        modules=modules,
    )

    # post request
    self.session.post(url, json=payload.model_dump(mode="json", by_alias=True))

exists

exists(*, inventory_item: InventoryItem) -> bool

Check whether a matching inventory item already exists.

A match is determined by name and company, the same way create detects duplicates. Useful before creating an item to avoid duplicates.

Example

from albert.resources.inventory import InventoryItem, InventoryCategory
from albert.resources.companies import Company
candidate = InventoryItem(
    name="Acetone",
    category=InventoryCategory.RAW_MATERIALS,
    company=Company(name="Acme Chemicals"),
)
client.inventory.exists(inventory_item=candidate)
# True

Parameters:

Name Type Description Default
inventory_item InventoryItem

The item to look for. Its name and company are used to match.

required

Returns:

Type Description
bool

True if a matching item exists, False otherwise.

Source code in src/albert/collections/inventory.py
def exists(self, *, inventory_item: InventoryItem) -> bool:
    """Check whether a matching inventory item already exists.

    A match is determined by name and company, the same way [`create`][albert.collections.inventory.InventoryCollection.create]
    detects duplicates. Useful before creating an item to avoid duplicates.

    !!! example
        ```python
        from albert.resources.inventory import InventoryItem, InventoryCategory
        from albert.resources.companies import Company
        candidate = InventoryItem(
            name="Acetone",
            category=InventoryCategory.RAW_MATERIALS,
            company=Company(name="Acme Chemicals"),
        )
        client.inventory.exists(inventory_item=candidate)
        # True
        ```

    Parameters
    ----------
    inventory_item : InventoryItem
        The item to look for. Its ``name`` and ``company`` are used to match.

    Returns
    -------
    bool
        True if a matching item exists, False otherwise.
    """
    hit = self.get_match_or_none(inventory_item=inventory_item)
    return bool(hit)

get_match_or_none

get_match_or_none(
    *, inventory_item: InventoryItem
) -> InventoryItem | None

Return the existing item matching name and company, or None.

Like exists, but returns the matched item itself so you can reuse its ID instead of creating a duplicate.

Example

existing = client.inventory.get_match_or_none(inventory_item=candidate)
existing.id if existing else "no match"
# 'INVA9999999'

Parameters:

Name Type Description Default
inventory_item InventoryItem

The item to match. Its name and company are used to match.

required

Returns:

Type Description
InventoryItem or None

The matching item, or None if no match is found.

Source code in src/albert/collections/inventory.py
def get_match_or_none(self, *, inventory_item: InventoryItem) -> InventoryItem | None:
    """Return the existing item matching name and company, or None.

    Like [`exists`][albert.collections.inventory.InventoryCollection.exists], but returns the matched item itself so you can reuse
    its ID instead of creating a duplicate.

    !!! example
        ```python
        existing = client.inventory.get_match_or_none(inventory_item=candidate)
        existing.id if existing else "no match"
        # 'INVA9999999'
        ```

    Parameters
    ----------
    inventory_item : InventoryItem
        The item to match. Its ``name`` and ``company`` are used to match.

    Returns
    -------
    InventoryItem or None
        The matching item, or None if no match is found.
    """
    company = inventory_item.company
    company_id = company.id if company is not None else None
    company_name = company.name if company is not None else None

    hits = self.get_all(
        text=inventory_item.name,
        company=[company] if isinstance(company, Company) else None,
        max_items=100,
    )

    for inv in hits:
        if inv.name != inventory_item.name:
            continue
        inv_company = inv.company
        # Prefer matching on company id; fall back to name when the id is
        # unavailable (e.g. an unsaved Company passed without an id).
        if company_id is not None:
            matched = inv_company is not None and inv_company.id == company_id
        else:
            matched = (inv_company.name if inv_company else None) == company_name
        if matched:
            return inv
    return None

create

create(
    *,
    inventory_item: InventoryItem,
    avoid_duplicates: bool = True,
) -> InventoryItem

Create a new inventory item.

Use this to add a raw material, consumable, equipment, or formula item to the catalog. A Formula item (category="Formulas") represents a mixture designed in a Project and requires project_id set to that Project's ID.

Any tags or company on the item that do not yet exist in Albert are created automatically before the item is registered (see CompanyCollection and TagCollection).

Example

from albert.resources.inventory import InventoryItem, InventoryCategory
from albert.resources.companies import Company
item = InventoryItem(
    name="Titanium Dioxide",
    category=InventoryCategory.RAW_MATERIALS,
    company=Company(name="Acme Chemicals"),
)
created = client.inventory.create(inventory_item=item)
created.id
# 'INVA9999999'

Parameters:

Name Type Description Default
inventory_item InventoryItem

The item to create. name and category are required. For raw materials, set company to the manufacturing Company and cas to the relevant CAS numbers. Volume items (unit_category="volume") require a positive density at creation. Formula items require project_id.

required
avoid_duplicates bool

When True (default), if an item with the same name and company already exists, that existing item is returned instead of creating a duplicate. Set to False to force creation. Not applied to Formula items, which are always created as a new item under their Project.

True

Returns:

Type Description
InventoryItem

The newly created item, populated with its assigned Inventory ID.

Source code in src/albert/collections/inventory.py
def create(
    self,
    *,
    inventory_item: InventoryItem,
    avoid_duplicates: bool = True,
) -> InventoryItem:
    """Create a new inventory item.

    Use this to add a raw material, consumable, equipment, or formula item to
    the catalog. A Formula item (``category="Formulas"``) represents a mixture
    designed in a Project and requires ``project_id`` set to that Project's ID.

    Any tags or company on the item that do not yet exist in Albert are
    created automatically before the item is registered (see
    [`CompanyCollection`][albert.collections.companies.CompanyCollection] and
    [`TagCollection`][albert.collections.tags.TagCollection]).

    !!! example
        ```python
        from albert.resources.inventory import InventoryItem, InventoryCategory
        from albert.resources.companies import Company
        item = InventoryItem(
            name="Titanium Dioxide",
            category=InventoryCategory.RAW_MATERIALS,
            company=Company(name="Acme Chemicals"),
        )
        created = client.inventory.create(inventory_item=item)
        created.id
        # 'INVA9999999'
        ```

    Parameters
    ----------
    inventory_item : InventoryItem
        The item to create. ``name`` and ``category`` are required. For raw
        materials, set ``company`` to the manufacturing Company and ``cas`` to
        the relevant CAS numbers. Volume items (``unit_category="volume"``)
        require a positive ``density`` at creation. Formula items require
        ``project_id``.
    avoid_duplicates : bool, optional
        When True (default), if an item with the same name and company already
        exists, that existing item is returned instead of creating a duplicate.
        Set to False to force creation. Not applied to Formula items, which are
        always created as a new item under their Project.

    Returns
    -------
    InventoryItem
        The newly created item, populated with its assigned Inventory ID.
    """
    if inventory_item.tags:
        inventory_item.tags = resolve_tags(session=self.session, tags=inventory_item.tags)
    if inventory_item.company and inventory_item.company.id is None:
        company_collection = CompanyCollection(session=self.session)
        inventory_item.company = company_collection.get_or_create(
            company=inventory_item.company
        )
    # Check to see if there is a match on name + Company already
    if avoid_duplicates and self._dedup_applies_to(category=inventory_item.category):
        existing = self.get_match_or_none(inventory_item=inventory_item)
        if isinstance(existing, InventoryItem):
            logging.warning(
                f"Inventory item already exists with name {existing.name} and company {existing.company.name}, returning existing item."
            )
            return existing
    response = self.session.post(
        self.base_path,
        json=inventory_item.model_dump(by_alias=True, exclude_none=True, mode="json"),
    )

    # ACL is populated after the create response is sent by the API.
    return self.get_by_id(id=response.json()["albertId"])

get_by_id

get_by_id(*, id: InventoryId) -> InventoryItem

Get a single, fully populated inventory item by its ID.

A formula's batch instructions are not included; read them with get_instruction_layout (procedure table) and get_instructions (instruction steps).

For retrieving many items at once, use get_by_ids. To find items without knowing their IDs, use search or get_all.

Example

item = client.inventory.get_by_id(id="INVA9999999")
item.name
# 'Titanium Dioxide'

Parameters:

Name Type Description Default
id InventoryId

The Inventory ID (format INV..., e.g. "INVA9999999").

required

Returns:

Type Description
InventoryItem

The fully populated item.

Source code in src/albert/collections/inventory.py
@validate_call
def get_by_id(self, *, id: InventoryId) -> InventoryItem:
    """Get a single, fully populated inventory item by its ID.

    A formula's batch instructions are not included; read them with
    [`get_instruction_layout`][albert.collections.inventory.InventoryCollection.get_instruction_layout]
    (procedure table) and
    [`get_instructions`][albert.collections.inventory.InventoryCollection.get_instructions]
    (instruction steps).

    For retrieving many items at once, use [`get_by_ids`][albert.collections.inventory.InventoryCollection.get_by_ids]. To find items
    without knowing their IDs, use [`search`][albert.collections.inventory.InventoryCollection.search] or [`get_all`][albert.collections.inventory.InventoryCollection.get_all].

    !!! example
        ```python
        item = client.inventory.get_by_id(id="INVA9999999")
        item.name
        # 'Titanium Dioxide'
        ```

    Parameters
    ----------
    id : InventoryId
        The Inventory ID (format ``INV...``, e.g. ``"INVA9999999"``).

    Returns
    -------
    InventoryItem
        The fully populated item.
    """
    url = f"{self.base_path}/{id}"
    response = self.session.get(url)
    return InventoryItem(**response.json())

get_by_ids

get_by_ids(
    *, ids: list[InventoryId]
) -> list[InventoryItem]

Get multiple fully populated inventory items by their IDs.

Arbitrarily long ID lists are supported. Items not found are omitted from the result.

Example

items = client.inventory.get_by_ids(ids=["INVA9999999", "INVA9999998"])
[i.name for i in items]
# ['Titanium Dioxide', 'Acetone']

Parameters:

Name Type Description Default
ids list[InventoryId]

The Inventory IDs to retrieve (format INV...).

required

Returns:

Type Description
list[InventoryItem]

The matching items. Order is not guaranteed to match the input.

Source code in src/albert/collections/inventory.py
@validate_call
def get_by_ids(self, *, ids: list[InventoryId]) -> list[InventoryItem]:
    """Get multiple fully populated inventory items by their IDs.

    Arbitrarily long ID lists are supported. Items not found are omitted
    from the result.

    !!! example
        ```python
        items = client.inventory.get_by_ids(ids=["INVA9999999", "INVA9999998"])
        [i.name for i in items]
        # ['Titanium Dioxide', 'Acetone']
        ```

    Parameters
    ----------
    ids : list[InventoryId]
        The Inventory IDs to retrieve (format ``INV...``).

    Returns
    -------
    list[InventoryItem]
        The matching items. Order is not guaranteed to match the input.
    """
    batch_size = 300
    batches = [ids[i : i + batch_size] for i in range(0, len(ids), batch_size)]
    inventory = []
    for batch in batches:
        response = self.session.get(f"{self.base_path}/ids", params={"id": batch})
        inventory.extend([InventoryItem(**item) for item in response.json()["Items"]])
    return inventory

get_specs

get_specs(
    *, ids: list[InventoryId]
) -> list[InventorySpecList]

Get the legacy inventory reference specs attached to inventory items.

Each InventorySpecList holds that item's declared reference properties (definition and value together). This is not Property Data: for measured task results or custom property-data values, use PropertyDataCollection. Requests are automatically batched.

Deprecated

Prefer get_by_parent_ids (client.attributes). Specs are removed in SDK 2.0. See the Specs → Attributes migration guide.

Example

spec_lists = client.inventory.get_specs(ids=["INVA9999999"])
for spec in spec_lists[0].specs:
    print(spec.name, spec.value.reference if spec.value else None)

Parameters:

Name Type Description Default
ids list[InventoryId]

The Inventory IDs to fetch specs for (format INV...).

required

Returns:

Type Description
list[InventorySpecList]

One entry per item, each holding that item's specs.

Source code in src/albert/collections/inventory.py
@deprecated(
    "get_specs() is deprecated and will be removed in 2.0. "
    "Use client.attributes.get_by_parent_ids() instead."
)
@validate_call
def get_specs(self, *, ids: list[InventoryId]) -> list[InventorySpecList]:
    """Get the legacy inventory reference specs attached to inventory items.

    Each [`InventorySpecList`][albert.resources.inventory.InventorySpecList]
    holds that item's declared reference properties (definition and value
    together). This is **not** Property Data: for measured task results or
    custom property-data values, use
    [`PropertyDataCollection`][albert.collections.property_data.PropertyDataCollection].
    Requests are automatically batched.

    !!! warning "Deprecated"
        Prefer
        [`get_by_parent_ids`][albert.collections.attributes.AttributeCollection.get_by_parent_ids]
        (``client.attributes``). Specs are removed in SDK 2.0. See the Specs →
        Attributes migration guide.

    !!! example
        ```python
        spec_lists = client.inventory.get_specs(ids=["INVA9999999"])
        for spec in spec_lists[0].specs:
            print(spec.name, spec.value.reference if spec.value else None)
        ```

    Parameters
    ----------
    ids : list[InventoryId]
        The Inventory IDs to fetch specs for (format ``INV...``).

    Returns
    -------
    list[InventorySpecList]
        One entry per item, each holding that item's specs.
    """
    url = f"{self.base_path}/specs"
    batches = [ids[i : i + 250] for i in range(0, len(ids), 250)]
    ta = TypeAdapter(InventorySpecList)
    return [
        ta.validate_python(item)
        for batch in batches
        for item in self.session.get(url, params={"id": batch}).json()
    ]

add_specs

add_specs(
    *,
    inventory_id: InventoryId,
    specs: InventorySpec | list[InventorySpec],
) -> InventorySpecList

Attach legacy inventory reference specs to an inventory item.

Each InventorySpec both defines a property (name, data_column_id) and assigns its expected InventorySpecValue on this item. Use Specs for inventory reference properties (e.g. a supplier-stated density that worksheets look up). For experimentally measured results, use Tasks and PropertyDataCollection instead. A spec may optionally name conditions via a workflow.

Deprecated

Prefer add_values (client.attributes) after creating shared attribute definitions. Specs are removed in SDK 2.0. See the Specs → Attributes migration guide.

Warning

This call replaces the item's complete spec set; it is not an append. Always pass every spec the item should carry in one call: a follow-up call with a subset can drop previously attached specs. A 500 Duplicate reference name error means spec rows with those names already exist on the item (even when get_specs shows none); do not blindly retry, call get_specs first and reconcile. There is no Specs API to remove individual values.

Example

from albert.resources.inventory import InventorySpec, InventorySpecValue
spec = InventorySpec(
    name="Density",
    data_column_id="DAC9999999",
    value=InventorySpecValue(min="1.1", max="1.3"),
)
client.inventory.add_specs(inventory_id="INVA9999999", specs=spec)

Parameters:

Name Type Description Default
inventory_id InventoryId

The item to attach the specs to (format INV...).

required
specs InventorySpec or list[InventorySpec]

The full set of reference specs the item should carry. Each embeds the property definition and value (and optionally workflow conditions).

required

Returns:

Type Description
InventorySpecList

The full set of specs now attached to the item.

Source code in src/albert/collections/inventory.py
@deprecated(
    "add_specs() is deprecated and will be removed in 2.0. "
    "Use client.attributes.add_values() instead."
)
@validate_call
def add_specs(
    self,
    *,
    inventory_id: InventoryId,
    specs: InventorySpec | list[InventorySpec],
) -> InventorySpecList:
    """Attach legacy inventory reference specs to an inventory item.

    Each [`InventorySpec`][albert.resources.inventory.InventorySpec] both
    defines a property (``name``, ``data_column_id``) and assigns its expected
    [`InventorySpecValue`][albert.resources.inventory.InventorySpecValue] on
    this item. Use Specs for inventory **reference** properties (e.g. a
    supplier-stated density that worksheets look up). For experimentally
    measured results, use Tasks and
    [`PropertyDataCollection`][albert.collections.property_data.PropertyDataCollection]
    instead. A spec may optionally name conditions via a workflow.

    !!! warning "Deprecated"
        Prefer
        [`add_values`][albert.collections.attributes.AttributeCollection.add_values]
        (``client.attributes``) after creating shared attribute definitions.
        Specs are removed in SDK 2.0. See the Specs → Attributes migration guide.

    !!! warning
        This call replaces the item's complete spec set; it is not an
        append. Always pass every spec the item should carry in one call: a
        follow-up call with a subset can drop previously attached specs. A
        ``500 Duplicate reference name`` error means spec rows with those
        names already exist on the item (even when ``get_specs`` shows
        none); do not blindly retry, call ``get_specs`` first and
        reconcile. There is no Specs API to remove individual values.

    !!! example
        ```python
        from albert.resources.inventory import InventorySpec, InventorySpecValue
        spec = InventorySpec(
            name="Density",
            data_column_id="DAC9999999",
            value=InventorySpecValue(min="1.1", max="1.3"),
        )
        client.inventory.add_specs(inventory_id="INVA9999999", specs=spec)
        ```

    Parameters
    ----------
    inventory_id : InventoryId
        The item to attach the specs to (format ``INV...``).
    specs : InventorySpec or list[InventorySpec]
        The full set of reference specs the item should carry. Each embeds
        the property definition and value (and optionally workflow conditions).

    Returns
    -------
    InventorySpecList
        The full set of specs now attached to the item.
    """
    if isinstance(specs, InventorySpec):
        specs = [specs]
    response = self.session.put(
        url=f"{self.base_path}/{inventory_id}/specs",
        json=[x.model_dump(exclude_unset=True, by_alias=True, mode="json") for x in specs],
    )
    return InventorySpecList(**response.json())

delete

delete(*, id: InventoryId) -> None

Delete an inventory item by its ID.

This permanently removes the item. To consolidate duplicates while preserving data, use merge instead.

Example

client.inventory.delete(id="INVA9999999")

Parameters:

Name Type Description Default
id InventoryId

The Inventory ID to delete (format INV...).

required

Returns:

Type Description
None
Source code in src/albert/collections/inventory.py
@validate_call
def delete(self, *, id: InventoryId) -> None:
    """Delete an inventory item by its ID.

    This permanently removes the item. To consolidate duplicates while
    preserving data, use [`merge`][albert.collections.inventory.InventoryCollection.merge] instead.

    !!! example
        ```python
        client.inventory.delete(id="INVA9999999")
        ```

    Parameters
    ----------
    id : InventoryId
        The Inventory ID to delete (format ``INV...``).

    Returns
    -------
    None
    """

    url = f"{self.base_path}/{id}"
    self.session.delete(url)

reactivate

reactivate(*, id: InventoryId) -> InventoryItem

Reactivate a deleted inventory item by its ID.

Restores an item previously deleted with delete, including a duplicate removed by merge. The item's lots are restored with it. Data already carried over to the merge parent stays on the parent.

Example

item = client.inventory.reactivate(id="INVA9999999")
item.status
# 'active'

Parameters:

Name Type Description Default
id InventoryId

The Inventory ID to reactivate (format INV...).

required

Returns:

Type Description
InventoryItem

The fully populated reactivated InventoryItem.

Source code in src/albert/collections/inventory.py
@validate_call
def reactivate(self, *, id: InventoryId) -> InventoryItem:
    """Reactivate a deleted inventory item by its ID.

    Restores an item previously deleted with
    [`delete`][albert.collections.inventory.InventoryCollection.delete], including
    a duplicate removed by
    [`merge`][albert.collections.inventory.InventoryCollection.merge]. The item's
    lots are restored with it. Data already carried over to the merge parent
    stays on the parent.

    !!! example
        ```python
        item = client.inventory.reactivate(id="INVA9999999")
        item.status
        # 'active'
        ```

    Parameters
    ----------
    id : InventoryId
        The Inventory ID to reactivate (format ``INV...``).

    Returns
    -------
    InventoryItem
        The fully populated reactivated InventoryItem.
    """
    url = f"{self.base_path}/{id}/reactivate"
    self.session.patch(url)
    return self.get_by_id(id=id)

get_all_facets

get_all_facets(
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory]
    | InventoryCategory
    | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[
        StorageLocation | StorageLocationFilter
    ]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
) -> list[FacetItem]

Get the facets available for an inventory search.

Facets are the grouped, counted filter options for a query, like the refinement sidebar of a search UI (e.g. how many matching items fall under each category, company, or tag). Use them to build progressive filtering or to summarize a result set without fetching every item. To pull a single named facet, use get_facet_by_name.

Example

facets = client.inventory.get_all_facets(text="titanium dioxide")
[f.name for f in facets]
# ['Category', 'Company', 'Tags', ...]

Parameters:

Name Type Description Default
text str

Free-text query matched against item name and related fields.

None
cas Cas or list[Cas]

Filter by CAS number(s).

None
category InventoryCategory or list[InventoryCategory]

Filter by category: RawMaterials, Consumables, Equipment, or Formulas.

None
company Company or list[Company]

Filter by manufacturing Company.

None
location Location or list[Location]

Filter by location.

None
storage_location StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter]

Filter by storage location.

None
project_id ProjectId

Filter by project.

None
sheet_id WorksheetId

Filter by worksheet.

None
created_by User, list[User], str, or list[str]

Filter by creator. Accepts user display name(s) or UserId(s) (e.g. "USR4227" or "Jane Doe"), or User object(s).

None
lot_owner User or list[User]

Filter by lot owner.

None
tags list[str]

Filter by tag name(s).

None
match_all_conditions bool

If True, only count items that satisfy every applied filter (AND logic). Default False.

False

Returns:

Type Description
list[FacetItem]

The facet groups available for the query.

Source code in src/albert/collections/inventory.py
@validate_call
def get_all_facets(
    self,
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory] | InventoryCategory | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[StorageLocation | StorageLocationFilter]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User] | User | str | list[str] | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
) -> list[FacetItem]:
    """Get the facets available for an inventory search.

    Facets are the grouped, counted filter options for a query, like the
    refinement sidebar of a search UI (e.g. how many matching items fall under
    each category, company, or tag). Use them to build progressive filtering
    or to summarize a result set without fetching every item. To pull a single
    named facet, use [`get_facet_by_name`][albert.collections.inventory.InventoryCollection.get_facet_by_name].

    !!! example
        ```python
        facets = client.inventory.get_all_facets(text="titanium dioxide")
        [f.name for f in facets]
        # ['Category', 'Company', 'Tags', ...]
        ```

    Parameters
    ----------
    text : str, optional
        Free-text query matched against item name and related fields.
    cas : Cas or list[Cas], optional
        Filter by CAS number(s).
    category : InventoryCategory or list[InventoryCategory], optional
        Filter by category: ``RawMaterials``, ``Consumables``, ``Equipment``,
        or ``Formulas``.
    company : Company or list[Company], optional
        Filter by manufacturing Company.
    location : Location or list[Location], optional
        Filter by location.
    storage_location : StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter], optional
        Filter by storage location.
    project_id : ProjectId, optional
        Filter by project.
    sheet_id : WorksheetId, optional
        Filter by worksheet.
    created_by : User, list[User], str, or list[str], optional
        Filter by creator. Accepts user display name(s) or UserId(s) (e.g.
        ``"USR4227"`` or ``"Jane Doe"``), or [`User`][albert.resources.users.User]
        object(s).
    lot_owner : User or list[User], optional
        Filter by lot owner.
    tags : list[str], optional
        Filter by tag name(s).
    match_all_conditions : bool, optional
        If True, only count items that satisfy every applied filter (AND logic).
        Default False.

    Returns
    -------
    list[FacetItem]
        The facet groups available for the query.
    """

    params = self._prepare_parameters(
        text=text,
        cas=cas,
        category=category,
        company=company,
        location=location,
        storage_location=storage_location,
        project_id=project_id,
        sheet_id=sheet_id,
        created_by=created_by,
        lot_owner=lot_owner,
        tags=tags,
    )
    params["limit"] = 1
    params = {k: v for k, v in params.items() if v is not None}
    response = self.session.get(
        url=f"{self.base_path}/llmsearch"
        if match_all_conditions
        else f"{self.base_path}/search",
        params=params,
    )
    return [FacetItem.model_validate(x) for x in response.json()["Facets"]]

get_facet_by_name

get_facet_by_name(
    name: str | list[str],
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory]
    | InventoryCategory
    | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[
        StorageLocation | StorageLocationFilter
    ]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
) -> list[FacetItem]

Return one or more named facets for an inventory search.

A convenience wrapper over get_all_facets that keeps only the facet group(s) you name. Useful for iterative search refinement, e.g. fetching the remaining Tags facet after other filters are applied.

Example

tags = client.inventory.get_facet_by_name("Tags", text="acetone")
tags[0].name
# 'Tags'

Parameters:

Name Type Description Default
name str or list[str]

The facet group name(s) to return (e.g. "Tags", "Company"). Matching is case-insensitive.

required
text str

Search text for full-text matching.

None
cas list[Cas] | Cas | None

Filter by CAS values.

None
category list[InventoryCategory] | InventoryCategory | None

Filter by inventory category.

None
company list[Company] | Company | None

Filter by company.

None
location list[Location] | Location | None

Filter by location.

None
storage_location list[StorageLocation | StorageLocationFilter] | StorageLocation | StorageLocationFilter | None

Filter by storage location.

None
project_id ProjectId | None

Filter by project.

None
sheet_id WorksheetId | None

Filter by worksheet.

None
created_by User, list[User], str, or list[str]

Filter by creator. Accepts user display name(s) or UserId(s) (e.g. "USR4227" or "Jane Doe"), or User object(s).

None
lot_owner list[User] | User | None

Filter by lot owner.

None
tags list[str] | None

Filter by tags.

None
match_all_conditions bool

If True, only count items that satisfy every applied filter (AND logic). Default False.

False

Returns:

Type Description
list[FacetItem]

The facet group(s) matching name.

Source code in src/albert/collections/inventory.py
@validate_call
def get_facet_by_name(
    self,
    name: str | list[str],
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory] | InventoryCategory | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[StorageLocation | StorageLocationFilter]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User] | User | str | list[str] | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
) -> list[FacetItem]:
    """Return one or more named facets for an inventory search.

    A convenience wrapper over [`get_all_facets`][albert.collections.inventory.InventoryCollection.get_all_facets] that keeps only the
    facet group(s) you name. Useful for iterative search refinement, e.g.
    fetching the remaining ``Tags`` facet after other filters are applied.

    !!! example
        ```python
        tags = client.inventory.get_facet_by_name("Tags", text="acetone")
        tags[0].name
        # 'Tags'
        ```

    Parameters
    ----------
    name : str or list[str]
        The facet group name(s) to return (e.g. ``"Tags"``, ``"Company"``).
        Matching is case-insensitive.
    text : str, optional
        Search text for full-text matching.
    cas : list[Cas] | Cas | None, optional
        Filter by CAS values.
    category : list[InventoryCategory] | InventoryCategory | None, optional
        Filter by inventory category.
    company : list[Company] | Company | None, optional
        Filter by company.
    location : list[Location] | Location | None, optional
        Filter by location.
    storage_location : list[StorageLocation | StorageLocationFilter] | StorageLocation | StorageLocationFilter | None, optional
        Filter by storage location.
    project_id : ProjectId | None, optional
        Filter by project.
    sheet_id : WorksheetId | None, optional
        Filter by worksheet.
    created_by : User, list[User], str, or list[str], optional
        Filter by creator. Accepts user display name(s) or UserId(s) (e.g.
        ``"USR4227"`` or ``"Jane Doe"``), or [`User`][albert.resources.users.User]
        object(s).
    lot_owner : list[User] | User | None, optional
        Filter by lot owner.
    tags : list[str] | None, optional
        Filter by tags.
    match_all_conditions : bool, optional
        If True, only count items that satisfy every applied filter (AND logic).
        Default False.

    Returns
    -------
    list[FacetItem]
        The facet group(s) matching ``name``.
    """
    name = ensure_list(name) or []

    facets = self.get_all_facets(
        text=text,
        cas=cas,
        category=category,
        company=company,
        location=location,
        storage_location=storage_location,
        project_id=project_id,
        sheet_id=sheet_id,
        created_by=created_by,
        lot_owner=lot_owner,
        tags=tags,
        match_all_conditions=match_all_conditions,
    )
    filtered_facets = []
    for facet in facets:
        if facet.name in name or facet.name.lower() in name:
            filtered_facets.append(facet)

    return filtered_facets

search

search(
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory]
    | InventoryCategory
    | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[
        StorageLocation | StorageLocationFilter
    ]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
    order: OrderBy = DESCENDING,
    sort_by: str | None = None,
    max_items: int | None = None,
    offset: int | None = 0,
    from_created_at: str | None = None,
    to_created_at: str | None = None,
    updated_by: str | list[str] | None = None,
    from_updated_at: str | None = None,
    to_updated_at: str | None = None,
    albert_id: str | list[str] | None = None,
    attribute_id: str | list[str] | None = None,
    cas_smile: str | list[str] | None = None,
    collaborator_pop_up: bool | None = None,
    contains_field: str | list[str] | None = None,
    contains_text: str | list[str] | None = None,
    created_by_id: str | list[str] | None = None,
    details: bool | None = None,
    drop_down_text: str | None = None,
    drop_down_text_prop: str | None = None,
    dup_detection: bool | None = None,
    facet_field: str | None = None,
    facet_text: str | None = None,
    from_expiration_date: str | None = None,
    from_lot_created_at: str | None = None,
    from_on_hand: str | None = None,
    gslo_group: str | list[str] | None = None,
    idh: str | list[str] | None = None,
    is_pop_up: bool | None = None,
    lot_created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    material_category: str | list[str] | None = None,
    pack_size: str | list[str] | None = None,
    pictogram_name: str | list[str] | None = None,
    result: str | list[str] | None = None,
    rsn: str | list[str] | None = None,
    source_field: str | list[str] | None = None,
    status: str | list[str] | None = None,
    sub_category: str | list[str] | None = None,
    synthesis_product_created: str
    | list[str]
    | None = None,
    to_expiration_date: str | None = None,
    to_lot_created_at: str | None = None,
    to_on_hand: str | None = None,
    metadata_filters: dict[str, Any] | None = None,
    custom_fields: dict[str, Any] | None = None,
    additional_field: str | list[str] | None = None,
    project_facets: dict[str, Any] | None = None,
    composite_search: dict[str, Any] | None = None,
) -> Iterator[InventorySearchItem]

Search for inventory items matching the given filters.

Returns lightweight, partially populated results and is the fastest way to look items up (best for name lookups, counts, or feeding IDs into another call). Fields such as full CAS breakdowns and metadata are omitted; when you need complete items, use get_all with the same filters, or pass the resulting IDs to get_by_ids.

Filters are combined with OR logic by default (an item matches if it satisfies any filter); set match_all_conditions=True to require every filter to match. Results are returned as a lazily paginated iterator, so iterating fetches additional pages on demand.

Example

from albert.resources.inventory import InventoryCategory
hits = client.inventory.search(
    text="acetone",
    category=InventoryCategory.RAW_MATERIALS,
    max_items=10,
)
first = next(iter(hits))
first.name
# 'Acetone'

Parameters:

Name Type Description Default
text str

Free-text query matched against item name, alias, and related fields. Only the first 50 characters are used.

None
cas Cas or list[Cas]

Filter by CAS number(s).

None
category InventoryCategory or list[InventoryCategory]

Filter by category: RawMaterials, Consumables, Equipment, or Formulas.

None
company Company or list[Company]

Filter by manufacturing Company.

None
location Location or list[Location]

Filter by location.

None
storage_location StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter]

Filter by storage location.

None
project_id str

Filter by the project a formula belongs to (Formula items only).

None
sheet_id str

Filter by worksheet ID.

None
created_by User, list[User], str, or list[str]

Filter by creator. Accepts user display name(s) or UserId(s) (e.g. "USR4227" or "Jane Doe"), or User object(s).

None
lot_owner User or list[User]

Filter by lot owner(s).

None
tags list[str]

Filter by tag name(s).

None
match_all_conditions bool

Require every filter to match (AND logic). Default False (OR logic).

False
order OrderBy

Sort direction. Default OrderBy.DESCENDING.

DESCENDING
sort_by str

Field to sort by. Default None (server default order).

None
max_items int

Maximum number of items to return in total. If None, iterates over all matches.

None
from_created_at str

Only include items created on or after this date, formatted as YYYY-MM-DD.

None
to_created_at str

Only include items created on or before this date, formatted as YYYY-MM-DD.

None
updated_by str or list[str]

Filter by user(s) who last updated the item. Accepts UserId(s) only (e.g. "USR4227"), not display names.

None
from_updated_at str

Only include items updated on or after this date (ISO 8601).

None
to_updated_at str

Only include items updated on or before this date (ISO 8601).

None
metadata_filters dict[str, Any]

Filter by custom field (metadata) values.

None
albert_id str or list[str]

Filter by Albert ID(s).

None
attribute_id str or list[str]

Filter by attribute ID(s). Cannot be combined with metadata_filters, custom_fields, additional_field, project_facets, or composite_search.

None
cas_smile str or list[str]

Filter by CAS SMILES string(s).

None
collaborator_pop_up bool

Apply collaborator popup search behavior.

None
contains_field str or list[str]

Field(s) for contains-style filtering.

None
contains_text str or list[str]

Text value(s) paired with contains_field.

None
created_by_id str or list[str]

Filter by creator UserId(s).

None
details bool

Invoke custom logic for the worksheet details view.

None
drop_down_text str

Dropdown search text.

None
drop_down_text_prop str

Dropdown search property name.

None
dup_detection bool

Enable duplicate-detection text sanitization.

None
facet_field str

Facet field to filter on.

None
facet_text str

Facet text to match.

None
from_expiration_date str

Only include lots expiring on or after this date (YYYY-MM-DD).

None
from_lot_created_at str

Only include lots created on or after this date (YYYY-MM-DD).

None
from_on_hand str

Minimum on-hand quantity filter.

None
gslo_group str or list[str]

Filter by GSLO group(s).

None
idh str or list[str]

Filter by IDH value(s).

None
is_pop_up bool

Apply popup search behavior.

None
lot_created_by User, list[User], str, or list[str]

Filter by lot creator. Accepts display name(s), UserId(s), or User object(s).

None
material_category str or list[str]

Filter by material category.

None
pack_size str or list[str]

Filter by pack size.

None
pictogram_name str or list[str]

Filter by pictogram name(s).

None
result str or list[str]

Filter by result value(s).

None
rsn str or list[str]

Filter by RSN value(s).

None
source_field str or list[str]

Restrict which fields are returned in search results.

None
status str or list[str]

Filter by status value(s).

None
sub_category str or list[str]

Filter by sub-category.

None
synthesis_product_created str or list[str]

Filter by synthesis product creation value(s).

None
to_expiration_date str

Only include lots expiring on or before this date (YYYY-MM-DD).

None
to_lot_created_at str

Only include lots created on or before this date (YYYY-MM-DD).

None
to_on_hand str

Maximum on-hand quantity filter.

None
custom_fields dict[str, Any]

Filter by custom field values.

None
additional_field str or list[str]

Request additional columns from the search index.

None
project_facets dict[str, Any]

Project facet filters.

None
composite_search dict[str, Any]

Composite search specification.

None

Returns:

Type Description
Iterator[InventorySearchItem]

A lazily paginated iterator of partially populated search results.

Source code in src/albert/collections/inventory.py
@validate_call
def search(
    self,
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory] | InventoryCategory | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[StorageLocation | StorageLocationFilter]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User] | User | str | list[str] | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
    order: OrderBy = OrderBy.DESCENDING,
    sort_by: str | None = None,
    max_items: int | None = None,
    offset: int | None = 0,
    from_created_at: str | None = None,
    to_created_at: str | None = None,
    updated_by: str | list[str] | None = None,
    from_updated_at: str | None = None,
    to_updated_at: str | None = None,
    albert_id: str | list[str] | None = None,
    attribute_id: str | list[str] | None = None,
    cas_smile: str | list[str] | None = None,
    collaborator_pop_up: bool | None = None,
    contains_field: str | list[str] | None = None,
    contains_text: str | list[str] | None = None,
    created_by_id: str | list[str] | None = None,
    details: bool | None = None,
    drop_down_text: str | None = None,
    drop_down_text_prop: str | None = None,
    dup_detection: bool | None = None,
    facet_field: str | None = None,
    facet_text: str | None = None,
    from_expiration_date: str | None = None,
    from_lot_created_at: str | None = None,
    from_on_hand: str | None = None,
    gslo_group: str | list[str] | None = None,
    idh: str | list[str] | None = None,
    is_pop_up: bool | None = None,
    lot_created_by: list[User] | User | str | list[str] | None = None,
    material_category: str | list[str] | None = None,
    pack_size: str | list[str] | None = None,
    pictogram_name: str | list[str] | None = None,
    result: str | list[str] | None = None,
    rsn: str | list[str] | None = None,
    source_field: str | list[str] | None = None,
    status: str | list[str] | None = None,
    sub_category: str | list[str] | None = None,
    synthesis_product_created: str | list[str] | None = None,
    to_expiration_date: str | None = None,
    to_lot_created_at: str | None = None,
    to_on_hand: str | None = None,
    metadata_filters: dict[str, Any] | None = None,
    custom_fields: dict[str, Any] | None = None,
    additional_field: str | list[str] | None = None,
    project_facets: dict[str, Any] | None = None,
    composite_search: dict[str, Any] | None = None,
) -> Iterator[InventorySearchItem]:
    """Search for inventory items matching the given filters.

    Returns lightweight, partially populated results and is the fastest way to
    look items up (best for name lookups, counts, or feeding IDs into another
    call). Fields such as full CAS breakdowns and metadata are omitted; when
    you need complete items, use [`get_all`][albert.collections.inventory.InventoryCollection.get_all] with the same filters, or pass
    the resulting IDs to [`get_by_ids`][albert.collections.inventory.InventoryCollection.get_by_ids].

    Filters are combined with OR logic by default (an item matches if it
    satisfies any filter); set ``match_all_conditions=True`` to require every
    filter to match. Results are returned as a lazily paginated iterator, so
    iterating fetches additional pages on demand.

    !!! example
        ```python
        from albert.resources.inventory import InventoryCategory
        hits = client.inventory.search(
            text="acetone",
            category=InventoryCategory.RAW_MATERIALS,
            max_items=10,
        )
        first = next(iter(hits))
        first.name
        # 'Acetone'
        ```

    Parameters
    ----------
    text : str, optional
        Free-text query matched against item name, alias, and related fields.
        Only the first 50 characters are used.
    cas : Cas or list[Cas], optional
        Filter by CAS number(s).
    category : InventoryCategory or list[InventoryCategory], optional
        Filter by category: ``RawMaterials``, ``Consumables``, ``Equipment``,
        or ``Formulas``.
    company : Company or list[Company], optional
        Filter by manufacturing Company.
    location : Location or list[Location], optional
        Filter by location.
    storage_location : StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter], optional
        Filter by storage location.
    project_id : str, optional
        Filter by the project a formula belongs to (Formula items only).
    sheet_id : str, optional
        Filter by worksheet ID.
    created_by : User, list[User], str, or list[str], optional
        Filter by creator. Accepts user display name(s) or UserId(s) (e.g.
        ``"USR4227"`` or ``"Jane Doe"``), or [`User`][albert.resources.users.User]
        object(s).
    lot_owner : User or list[User], optional
        Filter by lot owner(s).
    tags : list[str], optional
        Filter by tag name(s).
    match_all_conditions : bool, optional
        Require every filter to match (AND logic). Default False (OR logic).
    order : OrderBy, optional
        Sort direction. Default ``OrderBy.DESCENDING``.
    sort_by : str, optional
        Field to sort by. Default None (server default order).
    max_items : int, optional
        Maximum number of items to return in total. If None, iterates over all
        matches.
    from_created_at : str, optional
        Only include items created on or after this date, formatted as
        ``YYYY-MM-DD``.
    to_created_at : str, optional
        Only include items created on or before this date, formatted as
        ``YYYY-MM-DD``.
    updated_by : str or list[str], optional
        Filter by user(s) who last updated the item. Accepts UserId(s) only
        (e.g. ``"USR4227"``), not display names.
    from_updated_at : str, optional
        Only include items updated on or after this date (ISO 8601).
    to_updated_at : str, optional
        Only include items updated on or before this date (ISO 8601).
    metadata_filters : dict[str, Any], optional
        Filter by custom field (metadata) values.
    albert_id : str or list[str], optional
        Filter by Albert ID(s).
    attribute_id : str or list[str], optional
        Filter by attribute ID(s). Cannot be combined with ``metadata_filters``, ``custom_fields``, ``additional_field``, ``project_facets``, or ``composite_search``.
    cas_smile : str or list[str], optional
        Filter by CAS SMILES string(s).
    collaborator_pop_up : bool, optional
        Apply collaborator popup search behavior.
    contains_field : str or list[str], optional
        Field(s) for contains-style filtering.
    contains_text : str or list[str], optional
        Text value(s) paired with ``contains_field``.
    created_by_id : str or list[str], optional
        Filter by creator UserId(s).
    details : bool, optional
        Invoke custom logic for the worksheet details view.
    drop_down_text : str, optional
        Dropdown search text.
    drop_down_text_prop : str, optional
        Dropdown search property name.
    dup_detection : bool, optional
        Enable duplicate-detection text sanitization.
    facet_field : str, optional
        Facet field to filter on.
    facet_text : str, optional
        Facet text to match.
    from_expiration_date : str, optional
        Only include lots expiring on or after this date (``YYYY-MM-DD``).
    from_lot_created_at : str, optional
        Only include lots created on or after this date (``YYYY-MM-DD``).
    from_on_hand : str, optional
        Minimum on-hand quantity filter.
    gslo_group : str or list[str], optional
        Filter by GSLO group(s).
    idh : str or list[str], optional
        Filter by IDH value(s).
    is_pop_up : bool, optional
        Apply popup search behavior.
    lot_created_by : User, list[User], str, or list[str], optional
        Filter by lot creator. Accepts display name(s), UserId(s), or
        [`User`][albert.resources.users.User] object(s).
    material_category : str or list[str], optional
        Filter by material category.
    pack_size : str or list[str], optional
        Filter by pack size.
    pictogram_name : str or list[str], optional
        Filter by pictogram name(s).
    result : str or list[str], optional
        Filter by result value(s).
    rsn : str or list[str], optional
        Filter by RSN value(s).
    source_field : str or list[str], optional
        Restrict which fields are returned in search results.
    status : str or list[str], optional
        Filter by status value(s).
    sub_category : str or list[str], optional
        Filter by sub-category.
    synthesis_product_created : str or list[str], optional
        Filter by synthesis product creation value(s).
    to_expiration_date : str, optional
        Only include lots expiring on or before this date (``YYYY-MM-DD``).
    to_lot_created_at : str, optional
        Only include lots created on or before this date (``YYYY-MM-DD``).
    to_on_hand : str, optional
        Maximum on-hand quantity filter.
    custom_fields : dict[str, Any], optional
        Filter by custom field values.
    additional_field : str or list[str], optional
        Request additional columns from the search index.
    project_facets : dict[str, Any], optional
        Project facet filters.
    composite_search : dict[str, Any], optional
        Composite search specification.

    Returns
    -------
    Iterator[InventorySearchItem]
        A lazily paginated iterator of partially populated search results.
    """

    def deserialize(items: list[dict]):
        return [InventorySearchItem.model_validate(x)._bind_collection(self) for x in items]

    search_text = text if (text is None or len(text) < 50) else text[:50]

    query_params = self._prepare_parameters(
        text=search_text,
        cas=cas,
        category=category,
        company=company,
        order=order,
        sort_by=sort_by,
        location=location,
        storage_location=storage_location,
        project_id=project_id,
        sheet_id=sheet_id,
        created_by=created_by,
        lot_owner=lot_owner,
        tags=tags,
        offset=offset,
        from_created_at=from_created_at,
        to_created_at=to_created_at,
        updated_by=updated_by,
        from_updated_at=from_updated_at,
        to_updated_at=to_updated_at,
        albert_id=albert_id,
        attribute_id=attribute_id,
        cas_smile=cas_smile,
        collaborator_pop_up=collaborator_pop_up,
        contains_field=contains_field,
        contains_text=contains_text,
        created_by_id=created_by_id,
        details=details,
        drop_down_text=drop_down_text,
        drop_down_text_prop=drop_down_text_prop,
        dup_detection=dup_detection,
        facet_field=facet_field,
        facet_text=facet_text,
        from_expiration_date=from_expiration_date,
        from_lot_created_at=from_lot_created_at,
        from_on_hand=from_on_hand,
        gslo_group=gslo_group,
        idh=idh,
        is_pop_up=is_pop_up,
        lot_created_by=lot_created_by,
        material_category=material_category,
        pack_size=pack_size,
        pictogram_name=pictogram_name,
        result=result,
        rsn=rsn,
        source_field=source_field,
        status=status,
        sub_category=sub_category,
        synthesis_product_created=synthesis_product_created,
        to_expiration_date=to_expiration_date,
        to_lot_created_at=to_lot_created_at,
        to_on_hand=to_on_hand,
    )

    return self._paginate_inventory_search(
        deserialize=deserialize,
        query_params=query_params,
        match_all_conditions=match_all_conditions,
        max_items=max_items,
        metadata_filters=metadata_filters,
        custom_fields=custom_fields,
        additional_field=additional_field,
        project_facets=project_facets,
        composite_search=composite_search,
    )

get_all

get_all(
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory]
    | InventoryCategory
    | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[
        StorageLocation | StorageLocationFilter
    ]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
    order: OrderBy = DESCENDING,
    sort_by: str | None = None,
    max_items: int | None = None,
    offset: int | None = 0,
    from_created_at: str | None = None,
    to_created_at: str | None = None,
    updated_by: str | list[str] | None = None,
    from_updated_at: str | None = None,
    to_updated_at: str | None = None,
    albert_id: str | list[str] | None = None,
    attribute_id: str | list[str] | None = None,
    cas_smile: str | list[str] | None = None,
    collaborator_pop_up: bool | None = None,
    contains_field: str | list[str] | None = None,
    contains_text: str | list[str] | None = None,
    created_by_id: str | list[str] | None = None,
    details: bool | None = None,
    drop_down_text: str | None = None,
    drop_down_text_prop: str | None = None,
    dup_detection: bool | None = None,
    facet_field: str | None = None,
    facet_text: str | None = None,
    from_expiration_date: str | None = None,
    from_lot_created_at: str | None = None,
    from_on_hand: str | None = None,
    gslo_group: str | list[str] | None = None,
    idh: str | list[str] | None = None,
    is_pop_up: bool | None = None,
    lot_created_by: list[User]
    | User
    | str
    | list[str]
    | None = None,
    material_category: str | list[str] | None = None,
    pack_size: str | list[str] | None = None,
    pictogram_name: str | list[str] | None = None,
    result: str | list[str] | None = None,
    rsn: str | list[str] | None = None,
    source_field: str | list[str] | None = None,
    status: str | list[str] | None = None,
    sub_category: str | list[str] | None = None,
    synthesis_product_created: str
    | list[str]
    | None = None,
    to_expiration_date: str | None = None,
    to_lot_created_at: str | None = None,
    to_on_hand: str | None = None,
    metadata_filters: dict[str, Any] | None = None,
    custom_fields: dict[str, Any] | None = None,
    additional_field: str | list[str] | None = None,
    project_facets: dict[str, Any] | None = None,
    composite_search: dict[str, Any] | None = None,
) -> Iterator[InventoryItem]

Get fully populated inventory items matching the given filters.

Accepts the same filters as search but returns complete InventoryItem entities rather than lightweight search results. This is slower because it fetches full detail for every match, so prefer search when you only need names, IDs, or counts.

Filters are combined with OR logic by default; set match_all_conditions=True to require every filter to match. Results are returned as a lazily paginated iterator.

Example

from albert.resources.inventory import InventoryCategory
for item in client.inventory.get_all(
    category=InventoryCategory.RAW_MATERIALS,
    max_items=50,
):
    print(item.id, item.name)

Parameters:

Name Type Description Default
text str

Free-text query matched against item name, alias, and related fields. Only the first 50 characters are used.

None
cas Cas or list[Cas]

Filter by CAS number(s).

None
category InventoryCategory or list[InventoryCategory]

Filter by category: RawMaterials, Consumables, Equipment, or Formulas.

None
company Company or list[Company]

Filter by manufacturing Company.

None
location Location or list[Location]

Filter by location.

None
storage_location StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter]

Filter by storage location.

None
project_id str

Filter by the project a formula belongs to (Formula items only).

None
sheet_id str

Filter by worksheet ID.

None
created_by User, list[User], str, or list[str]

Filter by creator. Accepts user display name(s) or UserId(s) (e.g. "USR4227" or "Jane Doe"), or User object(s).

None
lot_owner User or list[User]

Filter by lot owner(s).

None
tags list[str]

Filter by tag name(s).

None
match_all_conditions bool

Require every filter to match (AND logic). Default False (OR logic).

False
order OrderBy

Sort direction. Default OrderBy.DESCENDING.

DESCENDING
sort_by str

Field to sort by. Default None (server default order).

None
max_items int

Maximum number of items to return in total. If None, iterates over all matches.

None
from_created_at str

Only include items created on or after this date, formatted as YYYY-MM-DD.

None
to_created_at str

Only include items created on or before this date, formatted as YYYY-MM-DD.

None
updated_by str or list[str]

Filter by user(s) who last updated the item. Accepts UserId(s) only (e.g. "USR4227"), not display names.

None
from_updated_at str

Only include items updated on or after this date (ISO 8601).

None
to_updated_at str

Only include items updated on or before this date (ISO 8601).

None
metadata_filters dict[str, Any]

Filter by custom field (metadata) values.

None
albert_id str or list[str]

Filter by Albert ID(s).

None
attribute_id str or list[str]

Filter by attribute ID(s). Cannot be combined with metadata_filters, custom_fields, additional_field, project_facets, or composite_search.

None
cas_smile str or list[str]

Filter by CAS SMILES string(s).

None
collaborator_pop_up bool

Apply collaborator popup search behavior.

None
contains_field str or list[str]

Field(s) for contains-style filtering.

None
contains_text str or list[str]

Text value(s) paired with contains_field.

None
created_by_id str or list[str]

Filter by creator UserId(s).

None
details bool

Invoke custom logic for the worksheet details view.

None
drop_down_text str

Dropdown search text.

None
drop_down_text_prop str

Dropdown search property name.

None
dup_detection bool

Enable duplicate-detection text sanitization.

None
facet_field str

Facet field to filter on.

None
facet_text str

Facet text to match.

None
from_expiration_date str

Only include lots expiring on or after this date (YYYY-MM-DD).

None
from_lot_created_at str

Only include lots created on or after this date (YYYY-MM-DD).

None
from_on_hand str

Minimum on-hand quantity filter.

None
gslo_group str or list[str]

Filter by GSLO group(s).

None
idh str or list[str]

Filter by IDH value(s).

None
is_pop_up bool

Apply popup search behavior.

None
lot_created_by User, list[User], str, or list[str]

Filter by lot creator. Accepts display name(s), UserId(s), or User object(s).

None
material_category str or list[str]

Filter by material category.

None
pack_size str or list[str]

Filter by pack size.

None
pictogram_name str or list[str]

Filter by pictogram name(s).

None
result str or list[str]

Filter by result value(s).

None
rsn str or list[str]

Filter by RSN value(s).

None
source_field str or list[str]

Restrict which fields are returned in search results.

None
status str or list[str]

Filter by status value(s).

None
sub_category str or list[str]

Filter by sub-category.

None
synthesis_product_created str or list[str]

Filter by synthesis product creation value(s).

None
to_expiration_date str

Only include lots expiring on or before this date (YYYY-MM-DD).

None
to_lot_created_at str

Only include lots created on or before this date (YYYY-MM-DD).

None
to_on_hand str

Maximum on-hand quantity filter.

None
custom_fields dict[str, Any]

Filter by custom field values.

None
additional_field str or list[str]

Request additional columns from the search index.

None
project_facets dict[str, Any]

Project facet filters.

None
composite_search dict[str, Any]

Composite search specification.

None

Returns:

Type Description
Iterator[InventoryItem]

A lazily paginated iterator of fully populated items.

Source code in src/albert/collections/inventory.py
@validate_call
def get_all(
    self,
    *,
    text: str | None = None,
    cas: list[Cas] | Cas | None = None,
    category: list[InventoryCategory] | InventoryCategory | None = None,
    company: list[Company] | Company | None = None,
    location: list[Location] | Location | None = None,
    storage_location: list[StorageLocation | StorageLocationFilter]
    | StorageLocation
    | StorageLocationFilter
    | None = None,
    project_id: ProjectId | None = None,
    sheet_id: WorksheetId | None = None,
    created_by: list[User] | User | str | list[str] | None = None,
    lot_owner: list[User] | User | None = None,
    tags: list[str] | None = None,
    match_all_conditions: bool = False,
    order: OrderBy = OrderBy.DESCENDING,
    sort_by: str | None = None,
    max_items: int | None = None,
    offset: int | None = 0,
    from_created_at: str | None = None,
    to_created_at: str | None = None,
    updated_by: str | list[str] | None = None,
    from_updated_at: str | None = None,
    to_updated_at: str | None = None,
    albert_id: str | list[str] | None = None,
    attribute_id: str | list[str] | None = None,
    cas_smile: str | list[str] | None = None,
    collaborator_pop_up: bool | None = None,
    contains_field: str | list[str] | None = None,
    contains_text: str | list[str] | None = None,
    created_by_id: str | list[str] | None = None,
    details: bool | None = None,
    drop_down_text: str | None = None,
    drop_down_text_prop: str | None = None,
    dup_detection: bool | None = None,
    facet_field: str | None = None,
    facet_text: str | None = None,
    from_expiration_date: str | None = None,
    from_lot_created_at: str | None = None,
    from_on_hand: str | None = None,
    gslo_group: str | list[str] | None = None,
    idh: str | list[str] | None = None,
    is_pop_up: bool | None = None,
    lot_created_by: list[User] | User | str | list[str] | None = None,
    material_category: str | list[str] | None = None,
    pack_size: str | list[str] | None = None,
    pictogram_name: str | list[str] | None = None,
    result: str | list[str] | None = None,
    rsn: str | list[str] | None = None,
    source_field: str | list[str] | None = None,
    status: str | list[str] | None = None,
    sub_category: str | list[str] | None = None,
    synthesis_product_created: str | list[str] | None = None,
    to_expiration_date: str | None = None,
    to_lot_created_at: str | None = None,
    to_on_hand: str | None = None,
    metadata_filters: dict[str, Any] | None = None,
    custom_fields: dict[str, Any] | None = None,
    additional_field: str | list[str] | None = None,
    project_facets: dict[str, Any] | None = None,
    composite_search: dict[str, Any] | None = None,
) -> Iterator[InventoryItem]:
    """Get fully populated inventory items matching the given filters.

    Accepts the same filters as [`search`][albert.collections.inventory.InventoryCollection.search] but returns complete
    ``InventoryItem`` entities rather than lightweight search results. This is
    slower because it fetches full detail for every match, so prefer
    [`search`][albert.collections.inventory.InventoryCollection.search] when you only need names, IDs, or counts.

    Filters are combined with OR logic by default; set
    ``match_all_conditions=True`` to require every filter to match. Results are
    returned as a lazily paginated iterator.

    !!! example
        ```python
        from albert.resources.inventory import InventoryCategory
        for item in client.inventory.get_all(
            category=InventoryCategory.RAW_MATERIALS,
            max_items=50,
        ):
            print(item.id, item.name)
        ```

    Parameters
    ----------
    text : str, optional
        Free-text query matched against item name, alias, and related fields.
        Only the first 50 characters are used.
    cas : Cas or list[Cas], optional
        Filter by CAS number(s).
    category : InventoryCategory or list[InventoryCategory], optional
        Filter by category: ``RawMaterials``, ``Consumables``, ``Equipment``,
        or ``Formulas``.
    company : Company or list[Company], optional
        Filter by manufacturing Company.
    location : Location or list[Location], optional
        Filter by location.
    storage_location : StorageLocation or StorageLocationFilter or list[StorageLocation | StorageLocationFilter], optional
        Filter by storage location.
    project_id : str, optional
        Filter by the project a formula belongs to (Formula items only).
    sheet_id : str, optional
        Filter by worksheet ID.
    created_by : User, list[User], str, or list[str], optional
        Filter by creator. Accepts user display name(s) or UserId(s) (e.g.
        ``"USR4227"`` or ``"Jane Doe"``), or [`User`][albert.resources.users.User]
        object(s).
    lot_owner : User or list[User], optional
        Filter by lot owner(s).
    tags : list[str], optional
        Filter by tag name(s).
    match_all_conditions : bool, optional
        Require every filter to match (AND logic). Default False (OR logic).
    order : OrderBy, optional
        Sort direction. Default ``OrderBy.DESCENDING``.
    sort_by : str, optional
        Field to sort by. Default None (server default order).
    max_items : int, optional
        Maximum number of items to return in total. If None, iterates over all
        matches.
    from_created_at : str, optional
        Only include items created on or after this date, formatted as
        ``YYYY-MM-DD``.
    to_created_at : str, optional
        Only include items created on or before this date, formatted as
        ``YYYY-MM-DD``.
    updated_by : str or list[str], optional
        Filter by user(s) who last updated the item. Accepts UserId(s) only
        (e.g. ``"USR4227"``), not display names.
    from_updated_at : str, optional
        Only include items updated on or after this date (ISO 8601).
    to_updated_at : str, optional
        Only include items updated on or before this date (ISO 8601).
    metadata_filters : dict[str, Any], optional
        Filter by custom field (metadata) values.
    albert_id : str or list[str], optional
        Filter by Albert ID(s).
    attribute_id : str or list[str], optional
        Filter by attribute ID(s). Cannot be combined with ``metadata_filters``, ``custom_fields``, ``additional_field``, ``project_facets``, or ``composite_search``.
    cas_smile : str or list[str], optional
        Filter by CAS SMILES string(s).
    collaborator_pop_up : bool, optional
        Apply collaborator popup search behavior.
    contains_field : str or list[str], optional
        Field(s) for contains-style filtering.
    contains_text : str or list[str], optional
        Text value(s) paired with ``contains_field``.
    created_by_id : str or list[str], optional
        Filter by creator UserId(s).
    details : bool, optional
        Invoke custom logic for the worksheet details view.
    drop_down_text : str, optional
        Dropdown search text.
    drop_down_text_prop : str, optional
        Dropdown search property name.
    dup_detection : bool, optional
        Enable duplicate-detection text sanitization.
    facet_field : str, optional
        Facet field to filter on.
    facet_text : str, optional
        Facet text to match.
    from_expiration_date : str, optional
        Only include lots expiring on or after this date (``YYYY-MM-DD``).
    from_lot_created_at : str, optional
        Only include lots created on or after this date (``YYYY-MM-DD``).
    from_on_hand : str, optional
        Minimum on-hand quantity filter.
    gslo_group : str or list[str], optional
        Filter by GSLO group(s).
    idh : str or list[str], optional
        Filter by IDH value(s).
    is_pop_up : bool, optional
        Apply popup search behavior.
    lot_created_by : User, list[User], str, or list[str], optional
        Filter by lot creator. Accepts display name(s), UserId(s), or
        [`User`][albert.resources.users.User] object(s).
    material_category : str or list[str], optional
        Filter by material category.
    pack_size : str or list[str], optional
        Filter by pack size.
    pictogram_name : str or list[str], optional
        Filter by pictogram name(s).
    result : str or list[str], optional
        Filter by result value(s).
    rsn : str or list[str], optional
        Filter by RSN value(s).
    source_field : str or list[str], optional
        Restrict which fields are returned in search results.
    status : str or list[str], optional
        Filter by status value(s).
    sub_category : str or list[str], optional
        Filter by sub-category.
    synthesis_product_created : str or list[str], optional
        Filter by synthesis product creation value(s).
    to_expiration_date : str, optional
        Only include lots expiring on or before this date (``YYYY-MM-DD``).
    to_lot_created_at : str, optional
        Only include lots created on or before this date (``YYYY-MM-DD``).
    to_on_hand : str, optional
        Maximum on-hand quantity filter.
    custom_fields : dict[str, Any], optional
        Filter by custom field values.
    additional_field : str or list[str], optional
        Request additional columns from the search index.
    project_facets : dict[str, Any], optional
        Project facet filters.
    composite_search : dict[str, Any], optional
        Composite search specification.

    Returns
    -------
    Iterator[InventoryItem]
        A lazily paginated iterator of fully populated items.
    """

    def deserialize(items: list[dict]) -> list[InventoryItem]:
        return self.get_by_ids(ids=[x["albertId"] for x in items])

    search_text = text if (text is None or len(text) < 50) else text[:50]

    query_params = self._prepare_parameters(
        text=search_text,
        cas=cas,
        category=category,
        company=company,
        order=order,
        sort_by=sort_by,
        location=location,
        storage_location=storage_location,
        project_id=project_id,
        sheet_id=sheet_id,
        created_by=created_by,
        lot_owner=lot_owner,
        tags=tags,
        offset=offset,
        from_created_at=from_created_at,
        to_created_at=to_created_at,
        updated_by=updated_by,
        from_updated_at=from_updated_at,
        to_updated_at=to_updated_at,
        albert_id=albert_id,
        attribute_id=attribute_id,
        cas_smile=cas_smile,
        collaborator_pop_up=collaborator_pop_up,
        contains_field=contains_field,
        contains_text=contains_text,
        created_by_id=created_by_id,
        details=details,
        drop_down_text=drop_down_text,
        drop_down_text_prop=drop_down_text_prop,
        dup_detection=dup_detection,
        facet_field=facet_field,
        facet_text=facet_text,
        from_expiration_date=from_expiration_date,
        from_lot_created_at=from_lot_created_at,
        from_on_hand=from_on_hand,
        gslo_group=gslo_group,
        idh=idh,
        is_pop_up=is_pop_up,
        lot_created_by=lot_created_by,
        material_category=material_category,
        pack_size=pack_size,
        pictogram_name=pictogram_name,
        result=result,
        rsn=rsn,
        source_field=source_field,
        status=status,
        sub_category=sub_category,
        synthesis_product_created=synthesis_product_created,
        to_expiration_date=to_expiration_date,
        to_lot_created_at=to_lot_created_at,
        to_on_hand=to_on_hand,
    )

    return self._paginate_inventory_search(
        deserialize=deserialize,
        query_params=query_params,
        match_all_conditions=match_all_conditions,
        max_items=max_items,
        metadata_filters=metadata_filters,
        custom_fields=custom_fields,
        additional_field=additional_field,
        project_facets=project_facets,
        composite_search=composite_search,
    )

update

update(*, inventory_item: InventoryItem) -> InventoryItem

Update an existing inventory item.

Fetch the item (e.g. with get_by_id), modify the updatable fields on the returned object, then pass it here. Only the fields listed in Notes are applied; changes to other fields are ignored.

Example

item = client.inventory.get_by_id(id="INVA9999999")
item.description = "Updated description"
updated = client.inventory.update(inventory_item=item)
updated.description
# 'Updated description'

Parameters:

Name Type Description Default
inventory_item InventoryItem

The item to update. Must have a valid id.

required

Returns:

Type Description
InventoryItem

The updated item.

Raises:

Type Description
AlbertPartialError

If a tag or company change only partially succeeds. The error carries the succeeded (created_items) and failed (failed_items) items.

Notes

The following fields can be updated: acls, alias, company, description, is_formula_override, metadata, name, security_class, tags, unit_category. Note that unit_category cannot be changed to or from volume, and density is fixed at creation. On individual CAS entries (via cas): min, max, target, cas_category, inventory_function. substance_id can be set when adding a new CAS entry; it is not patchable on existing entries.

Any tags that do not yet exist in Albert are created automatically.

Source code in src/albert/collections/inventory.py
def update(self, *, inventory_item: InventoryItem) -> InventoryItem:
    """Update an existing inventory item.

    Fetch the item (e.g. with [`get_by_id`][albert.collections.inventory.InventoryCollection.get_by_id]), modify the updatable fields
    on the returned object, then pass it here. Only the fields listed in Notes
    are applied; changes to other fields are ignored.

    !!! example
        ```python
        item = client.inventory.get_by_id(id="INVA9999999")
        item.description = "Updated description"
        updated = client.inventory.update(inventory_item=item)
        updated.description
        # 'Updated description'
        ```

    Parameters
    ----------
    inventory_item : InventoryItem
        The item to update. Must have a valid ``id``.

    Returns
    -------
    InventoryItem
        The updated item.

    Raises
    ------
    AlbertPartialError
        If a tag or company change only partially succeeds. The error carries
        the succeeded (``created_items``) and failed (``failed_items``) items.

    Notes
    -----
    The following fields can be updated: ``acls``, ``alias``, ``company``,
    ``description``, ``is_formula_override``, ``metadata``, ``name``,
    ``security_class``, ``tags``, ``unit_category``.
    Note that ``unit_category`` cannot be changed to or from ``volume``,
    and ``density`` is fixed at creation.
    On individual CAS entries (via ``cas``): ``min``, ``max``, ``target``,
    ``cas_category``, ``inventory_function``.
    ``substance_id`` can be set when adding a new CAS entry; it is not
    patchable on existing entries.

    Any tags that do not yet exist in Albert are created automatically.
    """
    if "tags" in inventory_item.model_fields_set and inventory_item.tags:
        inventory_item.tags = resolve_tags(session=self.session, tags=inventory_item.tags)
    # Fetch the current object state from the server or database
    current_object = self.get_by_id(id=inventory_item.id)
    # Generate the PATCH payload
    patch_payload = self._generate_inventory_patch_payload(
        existing=current_object, updated=inventory_item
    )

    self._apply_inventory_patch_payloads(patch_payloads=[(inventory_item.id, patch_payload)])

    updated_inv = self.get_by_id(id=inventory_item.id)
    return updated_inv

update_many

update_many(
    *, inventory_items: list[InventoryItem]
) -> list[InventoryItem]

Update multiple inventory items.

Same per-item semantics as update, but the current state of all items is fetched up front in batched calls and the updated items are re-fetched the same way, so updating N items avoids N individual read round-trips.

Example

items = client.inventory.get_by_ids(ids=["INVA9999999", "INVA9999998"])
for item in items:
    item.description = "Updated description"
updated = client.inventory.update_many(inventory_items=items)

Parameters:

Name Type Description Default
inventory_items list[InventoryItem]

The items to update. Each must have a valid id.

required

Returns:

Type Description
list[InventoryItem]

The updated items. Order is not guaranteed to match the input list.

Raises:

Type Description
AlbertPartialError

If a batched tag or company change only partially succeeds. The error carries the succeeded (created_items) and failed (failed_items) items.

Notes

The same fields can be updated as with update. Tag and company changes are applied together in batches of up to 300 items; all other changes are applied per item. If an error occurs mid-batch, earlier updates are not rolled back.

Source code in src/albert/collections/inventory.py
@validate_call
def update_many(self, *, inventory_items: list[InventoryItem]) -> list[InventoryItem]:
    """Update multiple inventory items.

    Same per-item semantics as [`update`][albert.collections.inventory.InventoryCollection.update],
    but the current state of all items is fetched up front in batched calls
    and the updated items are re-fetched the same way, so updating N items
    avoids N individual read round-trips.

    !!! example
        ```python
        items = client.inventory.get_by_ids(ids=["INVA9999999", "INVA9999998"])
        for item in items:
            item.description = "Updated description"
        updated = client.inventory.update_many(inventory_items=items)
        ```

    Parameters
    ----------
    inventory_items : list[InventoryItem]
        The items to update. Each must have a valid ``id``.

    Returns
    -------
    list[InventoryItem]
        The updated items. Order is not guaranteed to match the input list.

    Raises
    ------
    AlbertPartialError
        If a batched tag or company change only partially succeeds. The error
        carries the succeeded (``created_items``) and failed (``failed_items``)
        items.

    Notes
    -----
    The same fields can be updated as with
    [`update`][albert.collections.inventory.InventoryCollection.update].
    Tag and company changes are applied together in batches of up to 300
    items; all other changes are applied per item. If an error occurs
    mid-batch, earlier updates are not rolled back.
    """
    if not inventory_items:
        return []
    ids = [item.id for item in inventory_items]
    existing_by_id = {item.id: item for item in self.get_by_ids(ids=ids)}
    patch_payloads = []
    for inventory_item in inventory_items:
        if "tags" in inventory_item.model_fields_set and inventory_item.tags:
            inventory_item.tags = resolve_tags(session=self.session, tags=inventory_item.tags)
        current_object = existing_by_id.get(inventory_item.id)
        if current_object is None:
            # Raises NotFoundError, matching update() on an unknown ID.
            current_object = self.get_by_id(id=inventory_item.id)
        patch_payloads.append(
            (
                inventory_item.id,
                self._generate_inventory_patch_payload(
                    existing=current_object, updated=inventory_item
                ),
            )
        )
    self._apply_inventory_patch_payloads(patch_payloads=patch_payloads)
    return self.get_by_ids(ids=ids)

get_instructions

get_instructions(
    *,
    parent_id: InventoryId | None = None,
    created_by: str | None = None,
    updated_by: str | None = None,
    max_items: int | None = None,
) -> Iterator[Instruction]

Get a formula's batch instruction steps, filtered to one formula or one author.

Exactly one filter must be provided: a formula (parent_id), a creator (created_by), or a last editor (updated_by). Results are returned as a lazily paginated iterator; when filtering by formula, instructions come back in display order (formula-level instructions first, then each ingredient row's).

Example

from albert import Albert
client = Albert()
for instruction in client.inventory.get_instructions(parent_id="INV123"):
    print(instruction.id, instruction.name)

Parameters:

Name Type Description Default
parent_id InventoryId

Only instructions belonging to this formula (format INV...).

None
created_by str

Only instructions created by this user ID.

None
updated_by str

Only instructions last edited by this user ID.

None
max_items int

Maximum number of items to return in total. If None, iterates over all matches.

None

Returns:

Type Description
Iterator[Instruction]

A lazily paginated iterator of matching instructions.

Raises:

Type Description
ValueError

If zero or more than one of the filters is provided.

Source code in src/albert/collections/inventory.py
@validate_call
def get_instructions(
    self,
    *,
    parent_id: InventoryId | None = None,
    created_by: str | None = None,
    updated_by: str | None = None,
    max_items: int | None = None,
) -> Iterator[Instruction]:
    """Get a formula's batch instruction steps, filtered to one formula or one author.

    Exactly one filter must be provided: a formula (``parent_id``), a creator
    (``created_by``), or a last editor (``updated_by``). Results are returned
    as a lazily paginated iterator; when filtering by formula, instructions
    come back in display order (formula-level instructions first, then each
    ingredient row's).

    !!! example
        ```python
        from albert import Albert
        client = Albert()
        for instruction in client.inventory.get_instructions(parent_id="INV123"):
            print(instruction.id, instruction.name)
        ```

    Parameters
    ----------
    parent_id : InventoryId, optional
        Only instructions belonging to this formula (format ``INV...``).
    created_by : str, optional
        Only instructions created by this user ID.
    updated_by : str, optional
        Only instructions last edited by this user ID.
    max_items : int, optional
        Maximum number of items to return in total. If None, iterates over all
        matches.

    Returns
    -------
    Iterator[Instruction]
        A lazily paginated iterator of matching instructions.

    Raises
    ------
    ValueError
        If zero or more than one of the filters is provided.
    """
    params = _resolve_instruction_list_params(
        parent_id=parent_id, created_by=created_by, updated_by=updated_by
    )
    params["limit"] = _INSTRUCTIONS_PAGE_SIZE
    return AlbertPaginator(
        mode=PaginationMode.KEY,
        path=self._instructions_base_path,
        session=self.session,
        params=params,
        max_items=max_items,
        deserialize=lambda items: [Instruction.model_validate(x) for x in items],
    )

get_instruction_by_id

get_instruction_by_id(
    *, parent_id: InventoryId, id: str
) -> Instruction

Get a single instruction by its ID.

Example

instruction = client.inventory.get_instruction_by_id(
    parent_id="INV123", id="ABI100"
)
instruction.name
# 'Take the pH of the batch'

Parameters:

Name Type Description Default
parent_id InventoryId

The ID of the formula the instruction belongs to (format INV...).

required
id str

The ID of the instruction to retrieve (format ABI...).

required

Returns:

Type Description
Instruction

The fully populated instruction.

Source code in src/albert/collections/inventory.py
@validate_call
def get_instruction_by_id(self, *, parent_id: InventoryId, id: str) -> Instruction:
    """Get a single instruction by its ID.

    !!! example
        ```python
        instruction = client.inventory.get_instruction_by_id(
            parent_id="INV123", id="ABI100"
        )
        instruction.name
        # 'Take the pH of the batch'
        ```

    Parameters
    ----------
    parent_id : InventoryId
        The ID of the formula the instruction belongs to (format ``INV...``).
    id : str
        The ID of the instruction to retrieve (format ``ABI...``).

    Returns
    -------
    Instruction
        The fully populated instruction.
    """
    response = self.session.get(
        f"{self._instructions_base_path}/{parent_id}", params={"id": id}
    )
    return Instruction(**response.json())

get_instructions_by_parent_ids

get_instructions_by_parent_ids(
    *, parent_ids: list[InventoryId]
) -> list[InstructionSet]

Get the instructions of several formulas in one call.

Each formula comes back with its instructions in display order and the per-row ordering of those instructions. Formulas without instructions yet are returned empty. At most 15 formulas can be fetched per call.

Example

sets = client.inventory.get_instructions_by_parent_ids(
    parent_ids=["INV123", "INV456"]
)
for s in sets:
    print(s.id, len(s.instructions))

Parameters:

Name Type Description Default
parent_ids list[InventoryId]

The IDs of the formulas to fetch instructions for (format INV...). Maximum 15 per call.

required

Returns:

Type Description
list[InstructionSet]

One entry per requested formula, with its instructions and ordering.

Raises:

Type Description
ValueError

If more than 15 formula IDs are provided.

Source code in src/albert/collections/inventory.py
@validate_call
def get_instructions_by_parent_ids(
    self, *, parent_ids: list[InventoryId]
) -> list[InstructionSet]:
    """Get the instructions of several formulas in one call.

    Each formula comes back with its instructions in display order and the
    per-row ordering of those instructions. Formulas without instructions
    yet are returned empty. At most 15 formulas can be fetched per call.

    !!! example
        ```python
        sets = client.inventory.get_instructions_by_parent_ids(
            parent_ids=["INV123", "INV456"]
        )
        for s in sets:
            print(s.id, len(s.instructions))
        ```

    Parameters
    ----------
    parent_ids : list[InventoryId]
        The IDs of the formulas to fetch instructions for (format ``INV...``).
        Maximum 15 per call.

    Returns
    -------
    list[InstructionSet]
        One entry per requested formula, with its instructions and ordering.

    Raises
    ------
    ValueError
        If more than 15 formula IDs are provided.
    """
    if not parent_ids or len(parent_ids) > _INSTRUCTIONS_BULK_MAX_IDS:
        raise ValueError("Provide between 1 and 15 formula IDs.")
    response = self.session.get(
        f"{self._instructions_base_path}/ids", params={"id": parent_ids}
    )
    return [InstructionSet(**x) for x in response.json()["Items"]]

create_instruction

create_instruction(
    *, instruction: Instruction
) -> Instruction

Add a batch instruction step (e.g. "Take the pH of the batch") to a formula.

The instruction's parent_id selects the formula and is required. A new instruction is appended at the end of its order by default (its row's, or the formula-level one); reorder with update_instruction_row_sequence.

Example

from albert import Albert
from albert.resources.instructions import Instruction

client = Albert()
instruction = client.inventory.create_instruction(
    instruction=Instruction(
        name="Take the pH of the batch", parent_id="INV123"
    )
)
print(instruction.id)

Parameters:

Name Type Description Default
instruction Instruction

The instruction to create. Requires name and parent_id. To pin the instruction to a specific ingredient row, set design.design_row_id to that row's unique ID (format DES...#ROW...; match rows by name in get_instruction_layout results and read row_unique_id). Without a row link, the instruction is formula-level.

required

Returns:

Type Description
Instruction

The created instruction, populated with its assigned ID.

Raises:

Type Description
ValueError

If the instruction has no name or parent_id, or its design.product_id does not match parent_id.

Source code in src/albert/collections/inventory.py
@validate_call
def create_instruction(self, *, instruction: Instruction) -> Instruction:
    """Add a batch instruction step (e.g. "Take the pH of the batch") to a formula.

    The instruction's ``parent_id`` selects the formula and is required.
    A new instruction is appended at the end of its order by default (its
    row's, or the formula-level one); reorder with
    [`update_instruction_row_sequence`][albert.collections.inventory.InventoryCollection.update_instruction_row_sequence].

    !!! example
        ```python
        from albert import Albert
        from albert.resources.instructions import Instruction

        client = Albert()
        instruction = client.inventory.create_instruction(
            instruction=Instruction(
                name="Take the pH of the batch", parent_id="INV123"
            )
        )
        print(instruction.id)
        ```

    Parameters
    ----------
    instruction : Instruction
        The instruction to create. Requires ``name`` and ``parent_id``.
        To pin the instruction to a specific ingredient row, set
        ``design.design_row_id`` to that row's unique ID (format
        ``DES...#ROW...``; match rows by name in
        [`get_instruction_layout`][albert.collections.inventory.InventoryCollection.get_instruction_layout]
        results and read ``row_unique_id``). Without a row link, the
        instruction is formula-level.

    Returns
    -------
    Instruction
        The created instruction, populated with its assigned ID.

    Raises
    ------
    ValueError
        If the instruction has no ``name`` or ``parent_id``, or its
        ``design.product_id`` does not match ``parent_id``.
    """
    _validate_instruction_for_create(instruction)
    response = self.session.post(
        f"{self._instructions_base_path}/{instruction.parent_id}",
        json=_build_instruction_create_payload(instruction=instruction),
    )
    return Instruction(**response.json())

update_instruction

update_instruction(
    *, instruction: Instruction
) -> Instruction

Update an instruction's text.

Fetch an instruction (e.g. via get_instruction_by_id), modify its name, then pass it here. The instruction is matched by its id and parent_id. If nothing changed, the existing instruction is returned unmodified.

Example

instruction = client.inventory.get_instruction_by_id(
    parent_id="INV123", id="ABI100"
)
instruction.name = "Take the pH of the batch twice"
updated = client.inventory.update_instruction(instruction=instruction)

Parameters:

Name Type Description Default
instruction Instruction

The instruction with updated fields. Must include id and parent_id.

required

Returns:

Type Description
Instruction

The updated instruction.

Notes

The following fields can be updated: name. Renames that change only letter casing are rejected. An unset name (never assigned on the model) is left unchanged rather than cleared.

Source code in src/albert/collections/inventory.py
@validate_call
def update_instruction(self, *, instruction: Instruction) -> Instruction:
    """Update an instruction's text.

    Fetch an instruction (e.g. via [`get_instruction_by_id`][albert.collections.inventory.InventoryCollection.get_instruction_by_id]),
    modify its ``name``, then pass it here. The instruction is matched by its
    ``id`` and ``parent_id``. If nothing changed, the existing instruction is
    returned unmodified.

    !!! example
        ```python
        instruction = client.inventory.get_instruction_by_id(
            parent_id="INV123", id="ABI100"
        )
        instruction.name = "Take the pH of the batch twice"
        updated = client.inventory.update_instruction(instruction=instruction)
        ```

    Parameters
    ----------
    instruction : Instruction
        The instruction with updated fields. Must include ``id`` and
        ``parent_id``.

    Returns
    -------
    Instruction
        The updated instruction.

    Notes
    -----
    The following fields can be updated: ``name``. Renames that change only
    letter casing are rejected. An unset ``name`` (never assigned on the
    model) is left unchanged rather than cleared.
    """
    existing = self.get_instruction_by_id(parent_id=instruction.parent_id, id=instruction.id)
    payload = _build_instruction_name_patch(existing=existing, updated=instruction)
    if payload is None:
        return existing
    self.session.patch(f"{self._instructions_base_path}/{instruction.parent_id}", json=payload)
    return self.get_instruction_by_id(parent_id=instruction.parent_id, id=instruction.id)

delete_instruction

delete_instruction(
    *, parent_id: InventoryId, id: str
) -> None

Delete an instruction from a formula.

Example

client.inventory.delete_instruction(parent_id="INV123", id="ABI100")

Parameters:

Name Type Description Default
parent_id InventoryId

The ID of the formula the instruction belongs to (format INV...).

required
id str

The ID of the instruction to delete (format ABI...).

required

Returns:

Type Description
None
Source code in src/albert/collections/inventory.py
@validate_call
def delete_instruction(self, *, parent_id: InventoryId, id: str) -> None:
    """Delete an instruction from a formula.

    !!! example
        ```python
        client.inventory.delete_instruction(parent_id="INV123", id="ABI100")
        ```

    Parameters
    ----------
    parent_id : InventoryId
        The ID of the formula the instruction belongs to (format ``INV...``).
    id : str
        The ID of the instruction to delete (format ``ABI...``).

    Returns
    -------
    None
    """
    self.session.delete(f"{self._instructions_base_path}/{parent_id}", json={"id": id})

update_instruction_row_sequence

update_instruction_row_sequence(
    *,
    parent_id: InventoryId,
    instruction_ids: list[str],
    design_row_id: str | None = None,
) -> InstructionSet

Reorder the instructions within one row of a formula.

Instructions are ordered independently inside each row they are pinned to, with one extra order for the formula-level (unpinned) instructions. Pass the row's full set of instruction IDs in the desired order; omit design_row_id to reorder the formula-level instructions. This does not change the order of the rows themselves (see update_instruction_sequence).

Example

from albert import Albert
client = Albert()
current = client.inventory.get_instructions_by_parent_ids(parent_ids=["INV123"])[0]
ids = [i.id for i in current.instructions]
updated = client.inventory.update_instruction_row_sequence(
    parent_id="INV123", instruction_ids=list(reversed(ids))
)

Parameters:

Name Type Description Default
parent_id InventoryId

The ID of the formula whose instructions are reordered (format INV...).

required
instruction_ids list[str]

The IDs of the row's instructions in the desired order (format ABI...). Must contain exactly the instructions currently in the row.

required
design_row_id str

The unique ID of the ingredient row whose instructions are reordered (format DES...#ROW...). When None, the formula-level instructions are reordered.

None

Returns:

Type Description
InstructionSet

The formula's instructions with their updated ordering.

Raises:

Type Description
ValueError

If instruction_ids is not exactly the row's current set of instruction IDs, or the row has no instructions.

Source code in src/albert/collections/inventory.py
@validate_call
def update_instruction_row_sequence(
    self,
    *,
    parent_id: InventoryId,
    instruction_ids: list[str],
    design_row_id: str | None = None,
) -> InstructionSet:
    """Reorder the instructions within one row of a formula.

    Instructions are ordered independently inside each row they are pinned
    to, with one extra order for the formula-level (unpinned) instructions.
    Pass the row's full set of instruction IDs in the desired order; omit
    ``design_row_id`` to reorder the formula-level instructions. This does
    not change the order of the rows themselves (see
    [`update_instruction_sequence`][albert.collections.inventory.InventoryCollection.update_instruction_sequence]).

    !!! example
        ```python
        from albert import Albert
        client = Albert()
        current = client.inventory.get_instructions_by_parent_ids(parent_ids=["INV123"])[0]
        ids = [i.id for i in current.instructions]
        updated = client.inventory.update_instruction_row_sequence(
            parent_id="INV123", instruction_ids=list(reversed(ids))
        )
        ```

    Parameters
    ----------
    parent_id : InventoryId
        The ID of the formula whose instructions are reordered (format
        ``INV...``).
    instruction_ids : list[str]
        The IDs of the row's instructions in the desired order (format
        ``ABI...``). Must contain exactly the instructions currently in the
        row.
    design_row_id : str, optional
        The unique ID of the ingredient row whose instructions are reordered
        (format ``DES...#ROW...``). When None, the formula-level instructions
        are reordered.

    Returns
    -------
    InstructionSet
        The formula's instructions with their updated ordering.

    Raises
    ------
    ValueError
        If ``instruction_ids`` is not exactly the row's current set of
        instruction IDs, or the row has no instructions.
    """
    current = self.get_instructions_by_parent_ids(parent_ids=[parent_id])[0]
    payload = _build_instruction_row_sequence_payload(
        sequence=current.sequence,
        design_row_id=design_row_id,
        instruction_ids=instruction_ids,
    )
    self.session.patch(f"{self._instructions_base_path}/{parent_id}/sequence", json=payload)
    return self.get_instructions_by_parent_ids(parent_ids=[parent_id])[0]

copy_instructions

copy_instructions(
    *, source_id: InventoryId, target_ids: list[InventoryId]
) -> InstructionCopyResult

Copy a formula's instructions and their order to other formulas.

Every instruction of the source formula is recreated on each target formula with a fresh ID, keeping the same row pins and order. Formulas that already have instructions are skipped, not overwritten.

Example

result = client.inventory.copy_instructions(
    source_id="INV123", target_ids=["INV456", "INV789"]
)
print(result.copied, result.skipped)

Parameters:

Name Type Description Default
source_id InventoryId

The ID of the formula to copy instructions from (format INV...).

required
target_ids list[InventoryId]

The IDs of the formulas to copy instructions to (format INV...).

required

Returns:

Type Description
InstructionCopyResult

How many target formulas received the instructions and how many were skipped.

Source code in src/albert/collections/inventory.py
@validate_call
def copy_instructions(
    self, *, source_id: InventoryId, target_ids: list[InventoryId]
) -> InstructionCopyResult:
    """Copy a formula's instructions and their order to other formulas.

    Every instruction of the source formula is recreated on each target
    formula with a fresh ID, keeping the same row pins and order. Formulas
    that already have instructions are skipped, not overwritten.

    !!! example
        ```python
        result = client.inventory.copy_instructions(
            source_id="INV123", target_ids=["INV456", "INV789"]
        )
        print(result.copied, result.skipped)
        ```

    Parameters
    ----------
    source_id : InventoryId
        The ID of the formula to copy instructions from (format ``INV...``).
    target_ids : list[InventoryId]
        The IDs of the formulas to copy instructions to (format ``INV...``).

    Returns
    -------
    InstructionCopyResult
        How many target formulas received the instructions and how many were
        skipped.
    """
    payload = {"sourceId": source_id, "targets": target_ids}
    response = self.session.post(f"{self._instructions_base_path}/copy", json=payload)
    return InstructionCopyResult(**response.json())

get_instruction_layout

get_instruction_layout(
    *, inventory_id: InventoryId
) -> InstructionLayout

Get a formula's batch instruction procedure: ingredients, stages, and readings.

Returns every row of the formula's procedure in display order: ingredient rows, parameter groups (procedure stages), and parameter rows, each with its values for the relevant Worksheet columns. To get only the row order without the values, use get_instruction_sequence. See the albert.resources.instructions models for how the overall order is determined.

Example

from albert import Albert
client = Albert()
layout = client.inventory.get_instruction_layout(inventory_id="INV123")
for row in layout.rows:
    print(row.type, row.name)

Parameters:

Name Type Description Default
inventory_id InventoryId

The ID of the formula inventory item (format INV...).

required

Returns:

Type Description
InstructionLayout

The formula's procedure table, including the version of the row order.

Source code in src/albert/collections/inventory.py
@validate_call
def get_instruction_layout(self, *, inventory_id: InventoryId) -> InstructionLayout:
    """Get a formula's batch instruction procedure: ingredients, stages, and readings.

    Returns every row of the formula's procedure in display order:
    ingredient rows, parameter groups (procedure stages), and parameter
    rows, each with its values for the relevant Worksheet columns. To get
    only the row order without the values, use
    [`get_instruction_sequence`][albert.collections.inventory.InventoryCollection.get_instruction_sequence].
    See the [`albert.resources.instructions`][albert.resources.instructions]
    models for how the overall order is determined.

    !!! example
        ```python
        from albert import Albert
        client = Albert()
        layout = client.inventory.get_instruction_layout(inventory_id="INV123")
        for row in layout.rows:
            print(row.type, row.name)
        ```

    Parameters
    ----------
    inventory_id : InventoryId
        The ID of the formula inventory item (format ``INV...``).

    Returns
    -------
    InstructionLayout
        The formula's procedure table, including the version of the row
        order.
    """
    path = f"{self.base_path}/{inventory_id}/instructions"
    response = self.session.get(path)
    return InstructionLayout(**response.json())

get_instruction_sequence

get_instruction_sequence(
    *,
    inventory_id: InventoryId,
    exclude_hidden: bool = False,
) -> InstructionSequence

Get the row order of a formula's procedure.

Lists every row of the formula's procedure in display order, including rows that have no values for the formula yet. If the order has never been customized, it matches the Sheet the formula lives on; see the albert.resources.instructions models for how the order is determined. The returned version is required when reordering rows with update_instruction_sequence.

Example

from albert import Albert
client = Albert()
sequence = client.inventory.get_instruction_sequence(inventory_id="INV123")
for row in sequence.rows:
    print(row.row_id, row.is_hidden)

Parameters:

Name Type Description Default
inventory_id InventoryId

The ID of the formula inventory item (format INV...).

required
exclude_hidden bool

When True, omit rows that are currently hidden on the Worksheet. Default is False.

False

Returns:

Type Description
InstructionSequence

The formula's row order, including its version.

Source code in src/albert/collections/inventory.py
@validate_call
def get_instruction_sequence(
    self, *, inventory_id: InventoryId, exclude_hidden: bool = False
) -> InstructionSequence:
    """Get the row order of a formula's procedure.

    Lists every row of the formula's procedure in display order, including
    rows that have no values for the formula yet. If the order has never
    been customized, it matches the Sheet the formula lives on; see the
    [`albert.resources.instructions`][albert.resources.instructions] models
    for how the order is determined. The returned ``version`` is required
    when reordering rows with
    [`update_instruction_sequence`][albert.collections.inventory.InventoryCollection.update_instruction_sequence].

    !!! example
        ```python
        from albert import Albert
        client = Albert()
        sequence = client.inventory.get_instruction_sequence(inventory_id="INV123")
        for row in sequence.rows:
            print(row.row_id, row.is_hidden)
        ```

    Parameters
    ----------
    inventory_id : InventoryId
        The ID of the formula inventory item (format ``INV...``).
    exclude_hidden : bool, optional
        When True, omit rows that are currently hidden on the Worksheet.
        Default is False.

    Returns
    -------
    InstructionSequence
        The formula's row order, including its ``version``.
    """
    path = f"{self.base_path}/{inventory_id}/instructions/sequence"
    response = self.session.get(path, params={"excludeHiddenItems": exclude_hidden})
    return InstructionSequence(**response.json())

update_instruction_sequence

update_instruction_sequence(
    *,
    inventory_id: InventoryId,
    source_id: str,
    reference_id: str,
    position: SequencePosition,
    version: int,
) -> InstructionSequence

Move a parameter group row to a new position in the row order.

Only parameter group rows (the stages of the procedure) can be reordered; ingredient rows always follow the Sheet's Product Design. The row identified by source_id is placed directly above or below the row identified by reference_id, which can be an ingredient row or another parameter group row.

Both IDs use the unique row format DES...#ROW..., which users rarely know directly. Find them by matching display names in the formula's procedure table, as in the example below; each row's row_unique_id is the value to pass here.

The version guards against conflicting edits: it must match the row order's current version, or the move is rejected and the row order should be re-fetched before retrying. The updated row order (with its new version) is returned, so consecutive moves can chain off each result.

Example

from albert import Albert
from albert.resources.instructions import SequencePosition

client = Albert()
layout = client.inventory.get_instruction_layout(inventory_id="INV123")
ids_by_name = {row.name: row.row_unique_id for row in layout.rows}

sequence = client.inventory.get_instruction_sequence(inventory_id="INV123")
updated = client.inventory.update_instruction_sequence(
    inventory_id="INV123",
    source_id=ids_by_name["Heating"],
    reference_id=ids_by_name["Premix"],
    position=SequencePosition.BELOW,
    version=sequence.version,
)
print(updated.version)

Parameters:

Name Type Description Default
inventory_id InventoryId

The ID of the formula inventory item (format INV...).

required
source_id str

The unique ID of the parameter group row to move (format DES...#ROW...).

required
reference_id str

The unique ID of the row to place the moved row next to (format DES...#ROW...). Can be an ingredient row or another parameter group row.

required
position SequencePosition

Whether to place the moved row above or below the reference row.

required
version int

The row order's current version, as returned by get_instruction_sequence.

required

Returns:

Type Description
InstructionSequence

The updated row order, including its new version.

Notes

Reordering is available only when it is enabled for the tenant; otherwise the call is rejected as not applicable.

Source code in src/albert/collections/inventory.py
@validate_call
def update_instruction_sequence(
    self,
    *,
    inventory_id: InventoryId,
    source_id: str,
    reference_id: str,
    position: SequencePosition,
    version: int,
) -> InstructionSequence:
    """Move a parameter group row to a new position in the row order.

    Only parameter group rows (the stages of the procedure) can be
    reordered; ingredient rows always follow the Sheet's Product Design. The
    row identified by ``source_id`` is placed directly above or below the row
    identified by ``reference_id``, which can be an ingredient row or another
    parameter group row.

    Both IDs use the unique row format ``DES...#ROW...``, which users rarely
    know directly. Find them by matching display names in the formula's
    procedure table, as in the example below; each row's ``row_unique_id``
    is the value to pass here.

    The ``version`` guards against conflicting edits: it must match the row
    order's current version, or the move is rejected and the row order
    should be re-fetched before retrying. The updated row order (with its
    new version) is returned, so consecutive moves can chain off each
    result.

    !!! example
        ```python
        from albert import Albert
        from albert.resources.instructions import SequencePosition

        client = Albert()
        layout = client.inventory.get_instruction_layout(inventory_id="INV123")
        ids_by_name = {row.name: row.row_unique_id for row in layout.rows}

        sequence = client.inventory.get_instruction_sequence(inventory_id="INV123")
        updated = client.inventory.update_instruction_sequence(
            inventory_id="INV123",
            source_id=ids_by_name["Heating"],
            reference_id=ids_by_name["Premix"],
            position=SequencePosition.BELOW,
            version=sequence.version,
        )
        print(updated.version)
        ```

    Parameters
    ----------
    inventory_id : InventoryId
        The ID of the formula inventory item (format ``INV...``).
    source_id : str
        The unique ID of the parameter group row to move (format ``DES...#ROW...``).
    reference_id : str
        The unique ID of the row to place the moved row next to (format
        ``DES...#ROW...``). Can be an ingredient row or another parameter
        group row.
    position : SequencePosition
        Whether to place the moved row ``above`` or ``below`` the reference row.
    version : int
        The row order's current version, as returned by
        [`get_instruction_sequence`][albert.collections.inventory.InventoryCollection.get_instruction_sequence].

    Returns
    -------
    InstructionSequence
        The updated row order, including its new ``version``.

    Notes
    -----
    Reordering is available only when it is enabled for the tenant; otherwise
    the call is rejected as not applicable.
    """
    path = f"{self.base_path}/{inventory_id}/instructions/sequence"
    payload = _build_instruction_sequence_move_payload(
        source_id=source_id, reference_id=reference_id, position=position, version=version
    )
    self.session.patch(path, json=payload)
    return self.get_instruction_sequence(inventory_id=inventory_id)