Skip to content

Attributes (🧪 Beta)

albert.collections.attributes.AttributeCollection

AttributeCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage inventory reference Attributes in the Albert platform (🧪 Beta).

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.

Beta Feature!

Please do not use in production or without explicit guidance from Albert. You might otherwise have a bad experience. This feature currently falls outside of the Albert support contract, but we'd love your feedback!

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.

Parameters:

Name Type Description Default
ids list[str]

A list of attribute IDs.

required

Returns:

Type Description
list[Attribute]

The fully populated attributes.

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.

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

    Returns
    -------
    list[Attribute]
        The fully populated attributes.
    """
    response = self.session.get(f"{self.base_path}/ids", params={"id": ids})
    data = response.json()
    items = data.get("Items") or data.get("items") or []
    return [Attribute(**item) for item in items]

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)