Skip to content

Attributes

albert.collections.attributes.AttributeCollection

AttributeCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage inventory reference Attributes in the Albert platform.

An Attribute is a centralized, reusable inventory reference property template (e.g. "Viscosity @ 25°C"): it names what to track, which DataColumn it maps to, optional parameter setpoints (e.g. Temperature = 25°C), an optional unit, and typed validation. Reference values are the actual measured or assigned values for that attribute on a specific inventory item or lot.

The API separates definition (create Attribute definitions) from assignment (store values on a parent with add_values). Inventory-level values are the source of truth for worksheet lookup columns; worksheet cells may override locally for what-if analysis without writing back to inventory.

This replaces the deprecated per-item Inventory Specs API (get_specs, add_specs).

Attributes are identified by Attribute ID (format ATR..., e.g. "ATR469"). Reference values are keyed by parent_id: inventory items (INV...), lots (LOT...), or other parent types as the platform expands.

This collection is accessed as client.attributes.

Example

from albert import Albert
from albert.resources.attributes import (
    Attribute,
    AttributeCategory,
    AttributeValue,
    ValidationItem,
)
from albert.resources.parameter_groups import DataType, Operator

client = Albert()
viscosity = client.attributes.create(
    attribute=Attribute(
        datacolumn_id="DAC123",
        category=AttributeCategory.PROPERTY,
        reference_name="Viscosity @ 25°C",
        validation=[
            ValidationItem(
                datatype=DataType.NUMBER,
                min=0.0,
                max=500.0,
                operator=Operator.BETWEEN,
            )
        ],
    )
)
client.attributes.add_values(
    parent_id="INVA123",
    values=[AttributeValue(attributeId=viscosity.id, referenceValue=45.2)],
)

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 attribute requests.

Methods:

Name Description
get_all

Get all attributes, with optional filters.

get_by_id

Get a single attribute by its ID.

get_by_ids

Get multiple attributes by their IDs.

create

Create a new attribute.

update

Update an existing attribute.

delete

Delete an attribute by its ID.

search

Search for attributes matching the given filters.

add_values

Add or update reference values for a parent entity.

get_values

Get reference values for a parent entity.

get_by_parent_ids

Get reference values for multiple parent entities.

delete_values

Delete specific reference values from a parent entity.

clear_values

Remove all reference values from a parent entity.

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

required
Source code in src/albert/collections/attributes.py
def __init__(self, *, session: AlbertSession):
    """Initialize the AttributeCollection.

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

base_path

base_path = f'/api/{self._api_version}/attributes'

get_all

get_all(
    *,
    category: AttributeCategory | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[Attribute]

Get all attribute definitions, with optional filters.

Lists reusable inventory reference property templates (e.g. "Viscosity @ 25°C") that can be assigned to inventory items and lots. Use search for full-text and field filters across the catalogue.

Parameters:

Name Type Description Default
category AttributeCategory

Filter attributes by category (currently Property).

None
start_key str

Pagination start key from a previous request.

None
max_items int

Maximum number of items to return.

None

Returns:

Type Description
Iterator[Attribute]

An iterator over Attribute entities.

Source code in src/albert/collections/attributes.py
@validate_call
def get_all(
    self,
    *,
    category: AttributeCategory | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[Attribute]:
    """Get all attribute definitions, with optional filters.

    Lists reusable inventory reference property templates (e.g. "Viscosity @
    25°C") that can be assigned to inventory items and lots. Use
    [`search`][albert.collections.attributes.AttributeCollection.search] for
    full-text and field filters across the catalogue.

    Parameters
    ----------
    category : AttributeCategory, optional
        Filter attributes by category (currently ``Property``).
    start_key : str, optional
        Pagination start key from a previous request.
    max_items : int, optional
        Maximum number of items to return.

    Returns
    -------
    Iterator[Attribute]
        An iterator over Attribute entities.
    """
    params: dict[str, Any] = {}
    if category is not None:
        params["category"] = category.value
    if start_key is not None:
        params["startKey"] = start_key

    return AlbertPaginator(
        path=self.base_path,
        mode=PaginationMode.KEY,
        session=self.session,
        deserialize=lambda items: [Attribute(**item) for item in items],
        params=params,
        max_items=max_items,
    )

get_by_id

get_by_id(*, id: AttributeId) -> Attribute

Get a single attribute definition by its ID.

Returns the full template: linked data column, parameter setpoints, unit, and validation rules used when assigning reference values.

Parameters:

Name Type Description Default
id str

The attribute ID (format ATR...).

required

Returns:

Type Description
Attribute

The fully populated attribute.

Source code in src/albert/collections/attributes.py
@validate_call
def get_by_id(self, *, id: AttributeId) -> Attribute:
    """Get a single attribute definition by its ID.

    Returns the full template: linked data column, parameter setpoints, unit,
    and validation rules used when assigning reference values.

    Parameters
    ----------
    id : str
        The attribute ID (format ``ATR...``).

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

get_by_ids

get_by_ids(*, ids: list[AttributeId]) -> list[Attribute]

Get multiple attributes by their IDs.

Arbitrarily long ID lists are supported.

Parameters:

Name Type Description Default
ids list[str]

A list of attribute IDs.

required

Returns:

Type Description
list[Attribute]

The fully populated attributes. Attributes not found are omitted. Order is not guaranteed to match the input list.

Source code in src/albert/collections/attributes.py
@validate_call
def get_by_ids(self, *, ids: list[AttributeId]) -> list[Attribute]:
    """Get multiple attributes by their IDs.

    Arbitrarily long ID lists are supported.

    Parameters
    ----------
    ids : list[str]
        A list of attribute IDs.

    Returns
    -------
    list[Attribute]
        The fully populated attributes. Attributes not found are omitted.
        Order is not guaranteed to match the input list.
    """
    url = f"{self.base_path}/ids"
    batches = [ids[i : i + _IDS_CHUNK_SIZE] for i in range(0, len(ids), _IDS_CHUNK_SIZE)]
    attributes: list[Attribute] = []
    for batch in batches:
        # The attribute service reports "not found" when no ID in a batch
        # matches; those batches contribute no attributes.
        with suppress(NotFoundError):
            data = self.session.get(url, params={"id": batch}).json()
            items = data.get("Items") or data.get("items") or []
            attributes.extend(Attribute(**item) for item in items)
    return attributes

create

create(*, attribute: Attribute) -> Attribute

Create a new inventory reference attribute definition.

Define the property template once in the attribute catalogue, then assign values to inventory items or lots with add_values. Uniqueness is enforced on name and on the data column, unit, and parameter setpoint combination.

Parameters:

Name Type Description Default
attribute Attribute

The attribute to create.

required

Returns:

Type Description
Attribute

The fully populated attribute.

Source code in src/albert/collections/attributes.py
@validate_call
def create(self, *, attribute: Attribute) -> Attribute:
    """Create a new inventory reference attribute definition.

    Define the property template once in the attribute catalogue, then assign values to
    inventory items or lots with [`add_values`][albert.collections.attributes.AttributeCollection.add_values].
    Uniqueness is enforced on name and on the data column, unit, and parameter
    setpoint combination.

    Parameters
    ----------
    attribute : Attribute
        The attribute to create.

    Returns
    -------
    Attribute
        The fully populated attribute.
    """
    payload = attribute.model_dump(
        by_alias=True, exclude_unset=True, mode="json", exclude={"id"}
    )
    response = self.session.post(self.base_path, json=payload)
    return Attribute(**response.json())

update

update(*, attribute: Attribute) -> Attribute

Update an existing attribute definition.

Changes apply to the attribute definition. Inventory items that already store reference values keep their assignments; worksheet cells that previously imported a value are not updated automatically.

Parameters:

Name Type Description Default
attribute Attribute

The updated Attribute object. Must have an ID set.

required

Returns:

Type Description
Attribute

The fully populated attribute.

Notes

The following fields can be updated: reference_name, parameters, validation. reference_name must remain unique across definitions. unit_id can only be set once (when no unit is currently assigned); it cannot be changed afterwards.

Source code in src/albert/collections/attributes.py
@validate_call
def update(self, *, attribute: Attribute) -> Attribute:
    """Update an existing attribute definition.

    Changes apply to the attribute definition. Inventory items that already
    store reference values keep their assignments; worksheet cells that
    previously imported a value are not updated automatically.

    Parameters
    ----------
    attribute : Attribute
        The updated Attribute object. Must have an ID set.

    Returns
    -------
    Attribute
        The fully populated attribute.

    Notes
    -----
    The following fields can be updated: ``reference_name``, ``parameters``,
    ``validation``. ``reference_name`` must remain unique across definitions.
    ``unit_id`` can only be set once (when no unit is currently assigned); it
    cannot be changed afterwards.
    """
    if attribute.id is None:
        raise ValueError("Attribute ID is required for update.")

    existing = self.get_by_id(id=attribute.id)

    enum_patches = self._generate_enum_patches(existing=existing, updated=attribute)
    if enum_patches:
        self.session.put(f"{self.base_path}/{attribute.id}/enums", json={"data": enum_patches})

    patch_payload = self._generate_attribute_patch_payload(
        existing=existing, updated=attribute, skip_validation=bool(enum_patches)
    )
    if len(patch_payload.data) > 0:
        self.session.patch(
            f"{self.base_path}/{attribute.id}",
            json=patch_payload.model_dump(by_alias=True, mode="json"),
        )

    return self.get_by_id(id=attribute.id)

delete

delete(*, id: AttributeId) -> None

Delete an attribute definition by its ID.

Removes the template from the attribute catalogue and its inventory-level reference values. Worksheet cells that already imported a value retain their local copy.

Parameters:

Name Type Description Default
id str

The attribute ID (format ATR...).

required

Returns:

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

    Removes the template from the attribute catalogue and its inventory-level
    reference values. Worksheet cells that already imported a value retain
    their local copy.

    Parameters
    ----------
    id : str
        The attribute ID (format ``ATR...``).

    Returns
    -------
    None
    """
    self.session.delete(f"{self.base_path}/{id}")

search

search(
    *,
    text: str | None = None,
    order_by: OrderBy = DESCENDING,
    sort_by: str | None = None,
    datacolumn_id: list[DataColumnId] | None = None,
    datacolumn_name: list[str] | None = None,
    parameter: list[str] | None = None,
    unit: list[str] | None = None,
    data_type: list[DataType] | None = None,
    facet_text: str | None = None,
    facet_field: str | None = None,
    contains_field: list[str] | None = None,
    contains_text: list[str] | None = None,
    max_items: int | None = None,
) -> Iterator[AttributeSearchItem]

Search the central attribute catalogue.

Full-text and field filters help locate reusable definitions when wiring worksheet lookup columns or assigning values on inventory details.

Parameters:

Name Type Description Default
text str

Full-text search term.

None
order_by OrderBy

Sort order. Default is DESCENDING.

DESCENDING
sort_by str

Field to sort results by.

None
datacolumn_id list[str]

Filter by data column IDs.

None
datacolumn_name list[str]

Filter by data column names.

None
parameter list[str]

Filter by parameter name(s) (e.g., ["Temperature", "Pressure"]).

None
unit list[str]

Filter by unit name(s) (e.g., ["cP", "MPa"]).

None
data_type list[DataType]

Filter by data type(s).

None
facet_text str

Facet text to search for.

None
facet_field str

Facet field to filter on.

None
contains_field list[str]

Fields to search inside.

None
contains_text list[str]

Values to search for within the contains_field.

None
max_items int

Maximum number of items to return.

None

Returns:

Type Description
Iterator[AttributeSearchItem]

An iterator over search results.

Source code in src/albert/collections/attributes.py
@validate_call
def search(
    self,
    *,
    text: str | None = None,
    order_by: OrderBy = OrderBy.DESCENDING,
    sort_by: str | None = None,
    datacolumn_id: list[DataColumnId] | None = None,
    datacolumn_name: list[str] | None = None,
    parameter: list[str] | None = None,
    unit: list[str] | None = None,
    data_type: list[DataType] | None = None,
    facet_text: str | None = None,
    facet_field: str | None = None,
    contains_field: list[str] | None = None,
    contains_text: list[str] | None = None,
    max_items: int | None = None,
) -> Iterator[AttributeSearchItem]:
    """Search the central attribute catalogue.

    Full-text and field filters help locate reusable definitions when wiring
    worksheet lookup columns or assigning values on inventory details.

    Parameters
    ----------
    text : str, optional
        Full-text search term.
    order_by : OrderBy, optional
        Sort order. Default is DESCENDING.
    sort_by : str, optional
        Field to sort results by.
    datacolumn_id : list[str], optional
        Filter by data column IDs.
    datacolumn_name : list[str], optional
        Filter by data column names.
    parameter : list[str], optional
        Filter by parameter name(s) (e.g., ``["Temperature", "Pressure"]``).
    unit : list[str], optional
        Filter by unit name(s) (e.g., ``["cP", "MPa"]``).
    data_type : list[DataType], optional
        Filter by data type(s).
    facet_text : str, optional
        Facet text to search for.
    facet_field : str, optional
        Facet field to filter on.
    contains_field : list[str], optional
        Fields to search inside.
    contains_text : list[str], optional
        Values to search for within the ``contains_field``.
    max_items : int, optional
        Maximum number of items to return.

    Returns
    -------
    Iterator[AttributeSearchItem]
        An iterator over search results.
    """
    body: dict[str, Any] = {"order": order_by}
    if text is not None:
        body["text"] = text
    if sort_by is not None:
        body["sortBy"] = sort_by
    if datacolumn_id is not None:
        body["datacolumnId"] = datacolumn_id
    if datacolumn_name is not None:
        body["datacolumnName"] = datacolumn_name
    if parameter is not None:
        body["parameter"] = parameter
    if unit is not None:
        body["unit"] = unit
    if data_type is not None:
        body["dataType"] = data_type
    if facet_text is not None:
        body["facetText"] = facet_text
    if facet_field is not None:
        body["facetField"] = facet_field
    if contains_field is not None:
        body["containsField"] = contains_field
    if contains_text is not None:
        body["containsText"] = contains_text

    return AlbertPaginator(
        path=f"{self.base_path}/search",
        mode=PaginationMode.OFFSET,
        session=self.session,
        deserialize=lambda items: [AttributeSearchItem(**item) for item in items],
        method="POST",
        json=body,
        max_items=max_items,
    )

add_values

add_values(
    *, parent_id: str, values: list[AttributeValue]
) -> AttributeValuesResponse

Add or update reference values on a parent entity.

Upserts values for the given attributes on parent_id (inventory item, lot, etc.). Values must match each attribute's datatype. Inventory-level values feed worksheet lookups; each inventory item holds its own values. Attributes not listed in values are left unchanged.

Parameters:

Name Type Description Default
parent_id str

The ID of the parent entity (inventory item, lot, etc.).

required
values list[AttributeValue]

The attribute values to add or update.

required

Returns:

Type Description
AttributeValuesResponse

The saved attribute values with full attribute definitions.

Source code in src/albert/collections/attributes.py
@validate_call
def add_values(
    self,
    *,
    parent_id: str,
    values: list[AttributeValue],
) -> AttributeValuesResponse:
    """Add or update reference values on a parent entity.

    Upserts values for the given attributes on ``parent_id`` (inventory item,
    lot, etc.). Values must match each attribute's datatype. Inventory-level
    values feed worksheet lookups; each inventory item holds its own values.
    Attributes not listed in ``values`` are left unchanged.

    Parameters
    ----------
    parent_id : str
        The ID of the parent entity (inventory item, lot, etc.).
    values : list[AttributeValue]
        The attribute values to add or update.

    Returns
    -------
    AttributeValuesResponse
        The saved attribute values with full attribute definitions.
    """
    attribute_ids = [v.attribute_id for v in values]
    with suppress(NotFoundError):
        self.delete_values(parent_id=parent_id, attribute_ids=attribute_ids)
    payload = [v.model_dump(by_alias=True, mode="json", exclude_none=True) for v in values]
    response = self.session.put(f"{self.base_path}/values/{parent_id}", json=payload)
    return AttributeValuesResponse(**response.json())

get_values

get_values(
    *,
    parent_id: str,
    scope: AttributeScope | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[AttributeValuesResponse]

Get reference values for a parent entity.

Returns one AttributeValuesResponse per entity when scope includes child lots. Inventory item values are the canonical source for worksheet reference columns.

Parameters:

Name Type Description Default
parent_id str

The ID of the parent entity.

required
scope AttributeScope

Defines which entities to fetch values for. SELF (default): the parent entity only. LOT: lot entities under the parent (inventory parents only). ALL: parent and all child entities.

None
start_key str

Pagination start key from a previous request.

None
max_items int

Maximum number of items to return.

None

Returns:

Type Description
Iterator[AttributeValuesResponse]

An iterator over attribute value responses, one per entity.

Source code in src/albert/collections/attributes.py
@validate_call
def get_values(
    self,
    *,
    parent_id: str,
    scope: AttributeScope | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[AttributeValuesResponse]:
    """Get reference values for a parent entity.

    Returns one [`AttributeValuesResponse`][albert.resources.attributes.AttributeValuesResponse]
    per entity when ``scope`` includes child lots. Inventory item values are
    the canonical source for worksheet reference columns.

    Parameters
    ----------
    parent_id : str
        The ID of the parent entity.
    scope : AttributeScope, optional
        Defines which entities to fetch values for.
        ``SELF`` (default): the parent entity only.
        ``LOT``: lot entities under the parent (inventory parents only).
        ``ALL``: parent and all child entities.
    start_key : str, optional
        Pagination start key from a previous request.
    max_items : int, optional
        Maximum number of items to return.

    Returns
    -------
    Iterator[AttributeValuesResponse]
        An iterator over attribute value responses, one per entity.
    """
    params: dict[str, Any] = {}
    if scope is not None:
        params["scope"] = scope.value
    if start_key is not None:
        params["startKey"] = start_key

    return AlbertPaginator(
        path=f"{self.base_path}/values/{parent_id}",
        mode=PaginationMode.KEY,
        session=self.session,
        deserialize=lambda items: [AttributeValuesResponse(**item) for item in items],
        params=params,
        max_items=max_items,
    )

get_by_parent_ids

get_by_parent_ids(
    *, parent_ids: list[str], max_items: int | None = None
) -> list[AttributeValuesResponse]

Get reference values for multiple parent entities.

Bulk read across inventory items (replaces deprecated get_specs).

Parameters:

Name Type Description Default
parent_ids list[str]

The IDs of the parent entities to fetch values for.

required
max_items int | None

Maximum total number of attribute value items to return across all parents. When None (default), all pages are fetched for every parent.

None

Returns:

Type Description
list[AttributeValuesResponse]

Attribute values for each parent entity that has values set.

Source code in src/albert/collections/attributes.py
@validate_call
def get_by_parent_ids(
    self,
    *,
    parent_ids: list[str],
    max_items: int | None = None,
) -> list[AttributeValuesResponse]:
    """Get reference values for multiple parent entities.

    Bulk read across inventory items (replaces deprecated
    [`get_specs`][albert.collections.inventory.InventoryCollection.get_specs]).

    Parameters
    ----------
    parent_ids : list[str]
        The IDs of the parent entities to fetch values for.
    max_items : int | None, optional
        Maximum total number of attribute value items to return across all
        parents. When None (default), all pages are fetched for every parent.

    Returns
    -------
    list[AttributeValuesResponse]
        Attribute values for each parent entity that has values set.
    """
    pending: list[dict] = [{"parentId": pid} for pid in parent_ids]
    accumulated: dict[str, list[AttributeValuesResponseItem]] = {}
    total = 0

    while pending:
        response = self.session.post(f"{self.base_path}/values", json=pending)
        next_pending: list[dict] = []
        for item in response.json().get("items") or []:
            parsed = AttributeValuesResponse.model_validate(item)
            accumulated.setdefault(parsed.parent_id, []).extend(parsed.attributes)
            total += len(parsed.attributes)
            if last_key := item.get("lastKey"):
                next_pending.append({"parentId": parsed.parent_id, "startKey": last_key})
        pending = next_pending
        if max_items is not None and total >= max_items:
            break

    return [
        AttributeValuesResponse.model_validate({"parentId": pid, "attributes": attrs})
        for pid, attrs in accumulated.items()
    ]

delete_values

delete_values(
    *,
    parent_id: str,
    attribute_ids: list[AttributeId],
    scope: AttributeScope | None = None,
) -> None

Delete specific reference values from a parent entity.

Removes assignments for the given attributes on parent_id without deleting the attribute definitions themselves. Use scope to target lot-level values under an inventory parent.

Parameters:

Name Type Description Default
parent_id str

The ID of the parent entity (inventory item INV... or lot LOT...).

required
attribute_ids list[str]

The attribute IDs whose values should be removed.

required
scope AttributeScope

Scope of deletion. Defaults to SELF (only parent_id).

None

Returns:

Type Description
None
Source code in src/albert/collections/attributes.py
@validate_call
def delete_values(
    self,
    *,
    parent_id: str,
    attribute_ids: list[AttributeId],
    scope: AttributeScope | None = None,
) -> None:
    """Delete specific reference values from a parent entity.

    Removes assignments for the given attributes on ``parent_id`` without
    deleting the attribute definitions themselves. Use ``scope`` to target lot-level
    values under an inventory parent.

    Parameters
    ----------
    parent_id : str
        The ID of the parent entity (inventory item ``INV...`` or lot ``LOT...``).
    attribute_ids : list[str]
        The attribute IDs whose values should be removed.
    scope : AttributeScope, optional
        Scope of deletion. Defaults to ``SELF`` (only ``parent_id``).

    Returns
    -------
    None
    """
    params: dict[str, Any] = {"attributeId": attribute_ids}
    if scope is not None:
        params["scope"] = scope.value
    self.session.delete(f"{self.base_path}/values/{parent_id}", params=params)

clear_values

clear_values(
    *, parent_id: str, scope: AttributeScope | None = None
) -> None

Remove all reference values from a parent entity.

Clears every assignment on parent_id while leaving attribute definitions in the attribute catalogue. Use scope to clear lot values under an inventory item.

Parameters:

Name Type Description Default
parent_id str

The ID of the parent entity (inventory item INV... or lot LOT...).

required
scope AttributeScope

Scope of deletion. Defaults to SELF (only parent_id).

None

Returns:

Type Description
None
Source code in src/albert/collections/attributes.py
@validate_call
def clear_values(
    self,
    *,
    parent_id: str,
    scope: AttributeScope | None = None,
) -> None:
    """Remove all reference values from a parent entity.

    Clears every assignment on ``parent_id`` while leaving attribute
    definitions in the attribute catalogue. Use ``scope`` to clear lot values
    under an inventory item.

    Parameters
    ----------
    parent_id : str
        The ID of the parent entity (inventory item ``INV...`` or lot ``LOT...``).
    scope : AttributeScope, optional
        Scope of deletion. Defaults to ``SELF`` (only ``parent_id``).

    Returns
    -------
    None
    """
    params: dict[str, Any] = {}
    if scope is not None:
        params["scope"] = scope.value
    self.session.delete(f"{self.base_path}/values/{parent_id}", params=params)