Skip to content

Units

albert.collections.units.UnitCollection

UnitCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage Units of measure in the Albert platform.

A Unit is a unit of measure (e.g. g, mL, °C). Units are referenced throughout the platform: they qualify inventory quantities, parameter values, and property results. Each unit has a name, an optional display symbol, an optional list of synonyms (alternate spellings), and a category (UnitCategory, e.g. Mass or Volume).

Units are referenced by their Unit ID (format UNI..., e.g. "UNI9999999").

This collection is accessed as client.units.

Example

from albert import Albert
client = Albert()
unit = client.units.get_by_id(id="UNI9999999")
print(unit.name, unit.symbol, unit.category)

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

Methods:

Name Description
create

Create a new unit.

get_or_create

Return the existing unit matching the name, or create it.

get_by_id

Get a single unit by its ID.

get_by_ids

Get many units by their IDs.

get_by_name

Get a unit by name, or None if not found.

get_all

Iterate over units with optional filters.

update

Update an existing unit.

delete

Delete a unit by its ID.

exists

Check whether a unit with the given name exists.

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

required
Source code in src/albert/collections/units.py
def __init__(self, *, session: AlbertSession):
    """Initialize a UnitCollection.

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

base_path

base_path = f'/api/{UnitCollection._api_version}/units'

create

create(*, unit: Unit) -> Unit

Create a new unit.

Example

from albert.resources.units import Unit, UnitCategory
unit = client.units.create(
    unit=Unit(name="milligram", symbol="mg", category=UnitCategory.MASS)
)

Parameters:

Name Type Description Default
unit Unit

The unit to create.

required

Returns:

Type Description
Unit

The newly created unit, including its assigned Unit ID.

Source code in src/albert/collections/units.py
def create(self, *, unit: Unit) -> Unit:
    """Create a new unit.

    !!! example
        ```python
        from albert.resources.units import Unit, UnitCategory
        unit = client.units.create(
            unit=Unit(name="milligram", symbol="mg", category=UnitCategory.MASS)
        )
        ```

    Parameters
    ----------
    unit : Unit
        The unit to create.

    Returns
    -------
    Unit
        The newly created unit, including its assigned Unit ID.
    """
    response = self.session.post(
        self.base_path, json=unit.model_dump(by_alias=True, exclude_unset=True, mode="json")
    )
    unit = Unit(**response.json())
    return unit

get_or_create

get_or_create(*, unit: Unit) -> Unit

Return the existing unit matching the given name, or create it.

Looks for an existing unit with the same name (exact match). If one is found it is returned unchanged; otherwise a new unit is created.

Example

from albert.resources.units import Unit, UnitCategory
unit = client.units.get_or_create(
    unit=Unit(name="gram", symbol="g", category=UnitCategory.MASS)
)

Parameters:

Name Type Description Default
unit Unit

The unit to find or create.

required

Returns:

Type Description
Unit

The existing or newly created unit.

Source code in src/albert/collections/units.py
def get_or_create(self, *, unit: Unit) -> Unit:
    """Return the existing unit matching the given name, or create it.

    Looks for an existing unit with the same name (exact match). If one is
    found it is returned unchanged; otherwise a new unit is created.

    !!! example
        ```python
        from albert.resources.units import Unit, UnitCategory
        unit = client.units.get_or_create(
            unit=Unit(name="gram", symbol="g", category=UnitCategory.MASS)
        )
        ```

    Parameters
    ----------
    unit : Unit
        The unit to find or create.

    Returns
    -------
    Unit
        The existing or newly created unit.
    """
    found = self.get_all(name=unit.name, max_items=50)
    for existing in found:
        if existing.name.lower() == unit.name.lower():
            logging.warning(
                f"Unit with the name {unit.name} already exists. Returning the existing unit."
            )
            return existing
    return self.create(unit=unit)

get_by_id

get_by_id(*, id: UnitId) -> Unit

Get a single unit by its ID.

Example

unit = client.units.get_by_id(id="UNI9999999")

Parameters:

Name Type Description Default
id UnitId

The Unit ID to retrieve (format UNI...).

required

Returns:

Type Description
Unit

The fully populated unit.

Source code in src/albert/collections/units.py
@validate_call
def get_by_id(self, *, id: UnitId) -> Unit:
    """Get a single unit by its ID.

    !!! example
        ```python
        unit = client.units.get_by_id(id="UNI9999999")
        ```

    Parameters
    ----------
    id : UnitId
        The Unit ID to retrieve (format ``UNI...``).

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

get_by_ids

get_by_ids(*, ids: list[UnitId]) -> list[Unit]

Get many units by their IDs.

IDs are fetched in batches, so arbitrarily long lists are supported.

Example

units = client.units.get_by_ids(ids=["UNI9999999", "UNI2"])

Parameters:

Name Type Description Default
ids list[UnitId]

The Unit IDs to retrieve.

required

Returns:

Type Description
list[Unit]

The matching units. Units not found are omitted.

Source code in src/albert/collections/units.py
@validate_call
def get_by_ids(self, *, ids: list[UnitId]) -> list[Unit]:
    """Get many units by their IDs.

    IDs are fetched in batches, so arbitrarily long lists are supported.

    !!! example
        ```python
        units = client.units.get_by_ids(ids=["UNI9999999", "UNI2"])
        ```

    Parameters
    ----------
    ids : list[UnitId]
        The Unit IDs to retrieve.

    Returns
    -------
    list[Unit]
        The matching units. Units not found are omitted.
    """
    url = f"{self.base_path}/ids"
    batches = [ids[i : i + 500] for i in range(0, len(ids), 500)]
    return [
        Unit(**item)
        for batch in batches
        for item in self.session.get(url, params={"id": batch}).json()["Items"]
    ]

update

update(*, unit: Unit) -> Unit

Update an existing unit.

Fetch a unit (e.g. via get_by_id), modify the updatable fields on the returned object, then pass it here. The unit is matched by its id.

Example

unit = client.units.get_by_id(id="UNI9999999")
unit.symbol = "g"
unit.synonyms = ["gram", "grams"]
updated = client.units.update(unit=unit)

Parameters:

Name Type Description Default
unit Unit

The unit carrying the desired changes. Must have its id set.

required

Returns:

Type Description
Unit

The updated unit, re-fetched from Albert.

Notes

The following fields can be updated: category, symbol, synonyms.

Source code in src/albert/collections/units.py
@validate_call
def update(self, *, unit: Unit) -> Unit:
    """Update an existing unit.

    Fetch a unit (e.g. via [`get_by_id`][albert.collections.units.UnitCollection.get_by_id]), modify the updatable fields on
    the returned object, then pass it here. The unit is matched by its ``id``.

    !!! example
        ```python
        unit = client.units.get_by_id(id="UNI9999999")
        unit.symbol = "g"
        unit.synonyms = ["gram", "grams"]
        updated = client.units.update(unit=unit)
        ```

    Parameters
    ----------
    unit : Unit
        The unit carrying the desired changes. Must have its ``id`` set.

    Returns
    -------
    Unit
        The updated unit, re-fetched from Albert.

    Notes
    -----
    The following fields can be updated: ``category``, ``symbol``, ``synonyms``.
    """
    unit_id = unit.id
    original_unit = self.get_by_id(id=unit_id)
    payload = self._generate_unit_patch_payload(existing=original_unit, updated=unit)
    url = f"{self.base_path}/{unit_id}"

    # The backend rejects more than one operation on the same attribute in a
    # single request, so each Synonyms edit is sent as its own request.
    synonym_data = [d for d in payload.data if d.attribute == "Synonyms"]
    other_data = [d for d in payload.data if d.attribute != "Synonyms"]
    batches = [other_data] if other_data else []
    batches.extend([datum] for datum in synonym_data)
    for batch in batches:
        self.session.patch(
            url, json=PatchPayload(data=batch).model_dump(mode="json", by_alias=True)
        )

    unit = self.get_by_id(id=unit_id)
    return unit

delete

delete(*, id: UnitId) -> None

Delete a unit by its ID.

Example

client.units.delete(id="UNI9999999")

Parameters:

Name Type Description Default
id UnitId

The Unit ID to delete.

required

Returns:

Type Description
None
Source code in src/albert/collections/units.py
@validate_call
def delete(self, *, id: UnitId) -> None:
    """Delete a unit by its ID.

    !!! example
        ```python
        client.units.delete(id="UNI9999999")
        ```

    Parameters
    ----------
    id : UnitId
        The Unit ID to delete.

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

get_all

get_all(
    *,
    name: str | list[str] | None = None,
    category: UnitCategory | None = None,
    order_by: OrderBy = DESCENDING,
    exact_match: bool = False,
    verified: bool | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[Unit]

Iterate over units, with optional filters.

Results are fetched page by page as you iterate, so this scales to large result sets without loading everything at once.

Example

from albert.resources.units import UnitCategory
for unit in client.units.get_all(category=UnitCategory.MASS, max_items=50):
    print(unit.name, unit.symbol)

Parameters:

Name Type Description Default
name str or list[str]

One or more unit names to filter by.

None
category UnitCategory

Restrict results to a single unit category (e.g. Mass, Volume).

None
order_by OrderBy

Sort direction for results. Defaults to OrderBy.DESCENDING.

DESCENDING
exact_match bool

Whether name must match exactly. Defaults to False (substring match).

False
verified bool

Filter by whether the unit is verified. Defaults to None (no filter).

None
start_key str

Pagination key to resume iteration from a previous position.

None
max_items int

Maximum number of units to return in total. If None, iterates over all matching units.

None

Returns:

Type Description
Iterator[Unit]

An iterator over the matching units.

Source code in src/albert/collections/units.py
def get_all(
    self,
    *,
    name: str | list[str] | None = None,
    category: UnitCategory | None = None,
    order_by: OrderBy = OrderBy.DESCENDING,
    exact_match: bool = False,
    verified: bool | None = None,
    start_key: str | None = None,
    max_items: int | None = None,
) -> Iterator[Unit]:
    """Iterate over units, with optional filters.

    Results are fetched page by page as you iterate, so this scales to large
    result sets without loading everything at once.

    !!! example
        ```python
        from albert.resources.units import UnitCategory
        for unit in client.units.get_all(category=UnitCategory.MASS, max_items=50):
            print(unit.name, unit.symbol)
        ```

    Parameters
    ----------
    name : str or list[str], optional
        One or more unit names to filter by.
    category : UnitCategory, optional
        Restrict results to a single unit category (e.g. ``Mass``, ``Volume``).
    order_by : OrderBy, optional
        Sort direction for results. Defaults to ``OrderBy.DESCENDING``.
    exact_match : bool, optional
        Whether ``name`` must match exactly. Defaults to False (substring match).
    verified : bool, optional
        Filter by whether the unit is verified. Defaults to None (no filter).
    start_key : str, optional
        Pagination key to resume iteration from a previous position.
    max_items : int, optional
        Maximum number of units to return in total. If None, iterates over all
        matching units.

    Returns
    -------
    Iterator[Unit]
        An iterator over the matching units.
    """
    params = {
        "orderBy": order_by,
        "name": ensure_list(name),
        "exactMatch": exact_match,
        "verified": verified,
        "category": category,
        "startKey": start_key,
    }

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

get_by_name

get_by_name(
    *, name: str, exact_match: bool = False
) -> Unit | None

Get a unit by its name.

Example

unit = client.units.get_by_name(name="gram", exact_match=True)

Parameters:

Name Type Description Default
name str

The unit name to retrieve.

required
exact_match bool

Whether to match the name exactly, by default False.

False

Returns:

Type Description
Unit or None

The matching unit, or None if no unit with that name exists.

Source code in src/albert/collections/units.py
def get_by_name(self, *, name: str, exact_match: bool = False) -> Unit | None:
    """Get a unit by its name.

    !!! example
        ```python
        unit = client.units.get_by_name(name="gram", exact_match=True)
        ```

    Parameters
    ----------
    name : str
        The unit name to retrieve.
    exact_match : bool, optional
        Whether to match the name exactly, by default False.

    Returns
    -------
    Unit or None
        The matching unit, or None if no unit with that name exists.
    """
    found = self.get_all(name=name, exact_match=exact_match, max_items=10)
    # return the first with exactly that name
    for unit in found:
        if unit.name == name:
            return unit
    return None

exists

exists(*, name: str, exact_match: bool = True) -> bool

Check whether a unit with the given name exists.

Example

if client.units.exists(name="gram"):
    print("gram is defined")

Parameters:

Name Type Description Default
name str

The unit name to check.

required
exact_match bool

Whether to match the name exactly, by default True.

True

Returns:

Type Description
bool

True if a matching unit exists, False otherwise.

Source code in src/albert/collections/units.py
def exists(self, *, name: str, exact_match: bool = True) -> bool:
    """Check whether a unit with the given name exists.

    !!! example
        ```python
        if client.units.exists(name="gram"):
            print("gram is defined")
        ```

    Parameters
    ----------
    name : str
        The unit name to check.
    exact_match : bool, optional
        Whether to match the name exactly, by default True.

    Returns
    -------
    bool
        True if a matching unit exists, False otherwise.
    """
    return self.get_by_name(name=name, exact_match=exact_match) is not None