Skip to content

Data Templates

albert.collections.data_templates

DataTemplateId module-attribute

DataTemplateId = Annotated[
    str, AfterValidator(ensure_datatemplate_id)
]

AlbertHTTPError

AlbertHTTPError(response: Response)

Bases: AlbertException

Base class for all erors due to HTTP responses.

Source code in src/albert/exceptions.py
def __init__(self, response: requests.Response):
    message = self._format_message(response)
    super().__init__(message)
    self.response = response

response instance-attribute

response = response

AlbertSession

AlbertSession(
    *,
    base_url: str,
    token: str | None = None,
    client_credentials: ClientCredentials | None = None,
    retries: int | None = None,
)

Bases: Session

A session that has a base URL, which is prefixed to all request URLs.

Parameters:

Name Type Description Default
base_url str

The base URL to prefix to all requests. (e.g., "https://sandbox.albertinvent.com")

required
retries int

The number of retries for failed requests. Defaults to 3.

None
client_credentials ClientCredentials | None

The client credentials for programmatic authentication. Optional if token is provided.

None
token str | None

The JWT token for authentication. Optional if client credentials are provided.

None

Methods:

Name Description
request
Source code in src/albert/session.py
def __init__(
    self,
    *,
    base_url: str,
    token: str | None = None,
    client_credentials: ClientCredentials | None = None,
    retries: int | None = None,
):
    super().__init__()
    self.base_url = base_url
    self.headers.update(
        {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": f"albert-SDK V.{albert.__version__}",
        }
    )

    if token is None and client_credentials is None:
        raise ValueError("Either client credentials or token must be specified.")

    self._provided_token = token
    self._token_manager = (
        TokenManager(base_url, client_credentials) if client_credentials is not None else None
    )

    # Set up retry logic
    retries = retries if retries is not None else 3
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=0.3,
        status_forcelist=(500, 502, 503, 504, 403),
        raise_on_status=False,
    )
    adapter = HTTPAdapter(max_retries=retry)
    self.mount("http://", adapter)
    self.mount("https://", adapter)

base_url instance-attribute

base_url = base_url

request

request(
    method: str, path: str, *args, **kwargs
) -> Response
Source code in src/albert/session.py
def request(self, method: str, path: str, *args, **kwargs) -> requests.Response:
    self.headers["Authorization"] = f"Bearer {self._access_token}"
    full_url = urljoin(self.base_url, path) if not path.startswith("http") else path
    with handle_http_errors():
        response = super().request(method, full_url, *args, **kwargs)
        response.raise_for_status()
        return response

BaseCollection

BaseCollection(*, session: AlbertSession)

BaseCollection is the base class for all collection classes.

Parameters:

Name Type Description Default
session AlbertSession

The Albert API Session instance.

required
Source code in src/albert/collections/base.py
def __init__(self, *, session: AlbertSession):
    self.session = session

session instance-attribute

session = session

DCPatchDatum

Bases: PGPatchPayload

data class-attribute instance-attribute

data: list[GeneralPatchDatum] = Field(
    default_factory=list,
    description="The data to be updated in the data column.",
)

DataColumnValue

Bases: BaseAlbertModel

Methods:

Name Description
check_for_id

calculation class-attribute instance-attribute

calculation: str | None = None

data_column class-attribute instance-attribute

data_column: DataColumn = Field(exclude=True, default=None)

data_column_id class-attribute instance-attribute

data_column_id: str = Field(alias='id', default=None)

hidden class-attribute instance-attribute

hidden: bool = False

sequence class-attribute instance-attribute

sequence: str | None = Field(default=None, exclude=True)

unit class-attribute instance-attribute

unit: SerializeAsEntityLink[Unit] | None = Field(
    default=None, alias="Unit"
)

validation class-attribute instance-attribute

validation: list[ValueValidation] | None = Field(
    default_factory=list
)

value class-attribute instance-attribute

value: str | None = None

check_for_id

check_for_id()
Source code in src/albert/resources/data_templates.py
@model_validator(mode="after")
def check_for_id(self):
    if self.data_column_id is None and self.data_column is None:
        raise ValueError("Either data_column_id or data_column must be set")
    elif (
        self.data_column_id is not None
        and self.data_column is not None
        and self.data_column.id != self.data_column_id
    ):
        raise ValueError("If both are provided, data_column_id and data_column.id must match")
    elif self.data_column_id is None:
        self.data_column_id = self.data_column.id
    return self

DataTemplate

Bases: BaseTaggedResource

data_column_values class-attribute instance-attribute

data_column_values: list[DataColumnValue] | None = Field(
    alias="DataColumns", default=None
)

deleted_parameters class-attribute instance-attribute

deleted_parameters: list[ParameterValue] | None = Field(
    alias="DeletedParameters",
    default=None,
    frozen=True,
    exclude=True,
)

description class-attribute instance-attribute

description: str | None = None

id class-attribute instance-attribute

id: DataTemplateId | None = Field(None, alias='albertId')

metadata class-attribute instance-attribute

metadata: dict[str, MetadataItem] | None = Field(
    default=None, alias="Metadata"
)

name instance-attribute

name: str

parameter_values class-attribute instance-attribute

parameter_values: list[ParameterValue] | None = Field(
    alias="Parameters", default=None
)

security_class class-attribute instance-attribute

security_class: SecurityClass | None = None

users_with_access class-attribute instance-attribute

users_with_access: (
    list[SerializeAsEntityLink[User]] | None
) = Field(alias="ACL", default=None)

verified class-attribute instance-attribute

verified: bool = False

DataTemplateCollection

DataTemplateCollection(*, session: AlbertSession)

Bases: BaseCollection

DataTemplateCollection is a collection class for managing DataTemplate entities in the Albert platform.

Methods:

Name Description
add_data_columns

Adds data columns to a data template.

add_parameters

Adds parameters to a data template.

create

Creates a new data template.

delete

Deletes a data template by its ID.

get_by_id

Get a data template by its ID.

get_by_ids

Get a list of data templates by their IDs.

get_by_name

Get a data template by its name.

list

Lists data template entities with optional filters.

update

Updates a data template.

Source code in src/albert/collections/data_templates.py
def __init__(self, *, session: AlbertSession):
    super().__init__(session=session)
    self.base_path = f"/api/{DataTemplateCollection._api_version}/datatemplates"

base_path instance-attribute

base_path = f'/api/{_api_version}/datatemplates'

add_data_columns

add_data_columns(
    *,
    data_template_id: DataTemplateId,
    data_columns: list[DataColumnValue],
) -> DataTemplate

Adds data columns to a data template.

Parameters:

Name Type Description Default
data_template_id str

The ID of the data template to add the columns to.

required
data_columns list[DataColumnValue]

The list of DataColumnValue objects to add to the data template.

required

Returns:

Type Description
DataTemplate

The updated DataTemplate object.

Source code in src/albert/collections/data_templates.py
def add_data_columns(
    self, *, data_template_id: DataTemplateId, data_columns: list[DataColumnValue]
) -> DataTemplate:
    """Adds data columns to a data template.

    Parameters
    ----------
    data_template_id : str
        The ID of the data template to add the columns to.
    data_columns : list[DataColumnValue]
        The list of DataColumnValue objects to add to the data template.

    Returns
    -------
    DataTemplate
        The updated DataTemplate object.
    """
    # if there are enum values, we need to add them as an allowed enum
    for column in data_columns:
        if (
            column.validation
            and len(column.validation) > 0
            and isinstance(column.validation[0].value, list)
        ):
            for enum_value in column.validation[0].value:
                self.session.put(
                    f"{self.base_path}/{data_template_id}/datacolumns/{column.sequence}/enums",
                    json=[
                        enum_value.model_dump(mode="json", by_alias=True, exclude_none=True)
                    ],
                )

    payload = {
        "DataColumns": [
            x.model_dump(mode="json", by_alias=True, exclude_none=True) for x in data_columns
        ]
    }
    self.session.put(
        f"{self.base_path}/{data_template_id}/datacolumns",
        json=payload,
    )
    return self.get_by_id(id=data_template_id)

add_parameters

add_parameters(
    *,
    data_template_id: DataTemplateId,
    parameters: list[ParameterValue],
) -> DataTemplate

Adds parameters to a data template.

Parameters:

Name Type Description Default
data_template_id str

The ID of the data template to add the columns to.

required
parameters list[ParameterValue]

The list of ParameterValue objects to add to the data template.

required

Returns:

Type Description
DataTemplate

The updated DataTemplate object.

Source code in src/albert/collections/data_templates.py
def add_parameters(
    self, *, data_template_id: DataTemplateId, parameters: list[ParameterValue]
) -> DataTemplate:
    """Adds parameters to a data template.

    Parameters
    ----------
    data_template_id : str
        The ID of the data template to add the columns to.
    parameters : list[ParameterValue]
        The list of ParameterValue objects to add to the data template.

    Returns
    -------
    DataTemplate
        The updated DataTemplate object.
    """
    # make sure the parameter values have a default validaion of string type.
    initial_enum_values = {}  # use index to track the enum values
    if parameters is None or len(parameters) == 0:
        return self.get_by_id(id=data_template_id)
    for i, param in enumerate(parameters):
        if (
            param.validation
            and len(param.validation) > 0
            and param.validation[0].datatype == DataType.ENUM
        ):
            initial_enum_values[i] = param.validation[0].value
            param.validation[0].value = None
            param.validation[0].datatype = DataType.STRING

    payload = {
        "Parameters": [
            x.model_dump(mode="json", by_alias=True, exclude_none=True) for x in parameters
        ]
    }
    # if there are enum values, we need to add them as an allowed enum
    response = self.session.put(
        f"{self.base_path}/{data_template_id}/parameters",
        json=payload,
    )
    returned_parameters = [ParameterValue(**x) for x in response.json()["Parameters"]]
    for i, param in enumerate(returned_parameters):
        if i in initial_enum_values:
            param.validation[0].value = initial_enum_values[i]
            param.validation[0].datatype = DataType.ENUM
    self._add_param_enums(
        data_template_id=data_template_id,
        new_parameters=returned_parameters,
    )
    dt_with_params = self.get_by_id(id=data_template_id)
    for i, param in enumerate(dt_with_params.parameter_values):
        if i in initial_enum_values:
            param.validation[0].value = initial_enum_values[i]
            param.validation[0].datatype = DataType.ENUM

    return self.update(data_template=dt_with_params)

create

create(*, data_template: DataTemplate) -> DataTemplate

Creates a new data template.

Parameters:

Name Type Description Default
data_template DataTemplate

The DataTemplate object to create.

required

Returns:

Type Description
DataTemplate

The registered DataTemplate object with an ID.

Source code in src/albert/collections/data_templates.py
def create(self, *, data_template: DataTemplate) -> DataTemplate:
    """Creates a new data template.

    Parameters
    ----------
    data_template : DataTemplate
        The DataTemplate object to create.

    Returns
    -------
    DataTemplate
        The registered DataTemplate object with an ID.
    """
    # Preprocess data_column_values to set validation to None if it is an empty list
    # Handle a bug in the API where validation is an empty list
    # https://support.albertinvent.com/hc/en-us/requests/9177
    for column_value in data_template.data_column_values:
        if isinstance(column_value.validation, list) and len(column_value.validation) == 0:
            column_value.validation = None
    # remove them on the initial post
    parameter_values = data_template.parameter_values
    data_template.parameter_values = None
    response = self.session.post(
        self.base_path,
        json=data_template.model_dump(mode="json", by_alias=True, exclude_none=True),
    )
    dt = DataTemplate(**response.json())
    dt.parameter_values = parameter_values
    if data_template.parameter_values is None or len(data_template.parameter_values) == 0:
        return dt
    else:
        return self.add_parameters(
            data_template_id=dt.id, parameters=data_template.parameter_values
        )

delete

delete(*, id: DataTemplateId) -> None

Deletes a data template by its ID.

Parameters:

Name Type Description Default
id str

The ID of the data template to delete.

required
Source code in src/albert/collections/data_templates.py
def delete(self, *, id: DataTemplateId) -> None:
    """Deletes a data template by its ID.

    Parameters
    ----------
    id : str
        The ID of the data template to delete.
    """
    self.session.delete(f"{self.base_path}/{id}")

get_by_id

get_by_id(*, id: DataTemplateId) -> DataTemplate

Get a data template by its ID.

Parameters:

Name Type Description Default
id DataTemplateId

The ID of the data template to get.

required

Returns:

Type Description
DataTemplate

The data template object on match or None

Source code in src/albert/collections/data_templates.py
def get_by_id(self, *, id: DataTemplateId) -> DataTemplate:
    """Get a data template by its ID.

    Parameters
    ----------
    id : DataTemplateId
        The ID of the data template to get.

    Returns
    -------
    DataTemplate
        The data template object on match or None
    """
    response = self.session.get(f"{self.base_path}/{id}")
    return DataTemplate(**response.json())

get_by_ids

get_by_ids(
    *, ids: list[DataTemplateId]
) -> list[DataTemplate]

Get a list of data templates by their IDs.

Parameters:

Name Type Description Default
ids list[DataTemplateId]

The list of DataTemplate IDs to get.

required

Returns:

Type Description
list[DataTemplate]

A list of DataTemplate objects with the provided IDs.

Source code in src/albert/collections/data_templates.py
def get_by_ids(self, *, ids: list[DataTemplateId]) -> list[DataTemplate]:
    """Get a list of data templates by their IDs.

    Parameters
    ----------
    ids : list[DataTemplateId]
        The list of DataTemplate IDs to get.

    Returns
    -------
    list[DataTemplate]
        A list of DataTemplate objects with the provided IDs.
    """
    url = f"{self.base_path}/ids"
    batches = [ids[i : i + 250] for i in range(0, len(ids), 250)]
    return [
        DataTemplate(**item)
        for batch in batches
        for item in self.session.get(url, params={"id": batch}).json()["Items"]
    ]

get_by_name

get_by_name(*, name: str) -> DataTemplate | None

Get a data template by its name.

Parameters:

Name Type Description Default
name str

The name of the data template to get.

required

Returns:

Type Description
DataTemplate | None

The matching data template object or None if not found.

Source code in src/albert/collections/data_templates.py
def get_by_name(self, *, name: str) -> DataTemplate | None:
    """Get a data template by its name.

    Parameters
    ----------
    name : str
        The name of the data template to get.

    Returns
    -------
    DataTemplate | None
        The matching data template object or None if not found.
    """
    hits = list(self.list(name=name))
    for h in hits:
        if h.name.lower() == name.lower():
            return h
    return None

list

list(
    *,
    name: str | None = None,
    user_id: str | None = None,
    order_by: OrderBy = DESCENDING,
    limit: int = 100,
    offset: int = 0,
) -> Iterator[DataTemplate]

Lists data template entities with optional filters.

Parameters:

Name Type Description Default
name Union[str, None]

The name of the data template to filter by, by default None.

None
user_id str

user_id to filter by, by default None.

None
order_by OrderBy

The order by which to sort the results, by default OrderBy.DESCENDING.

DESCENDING

Returns:

Type Description
Iterator[DataTemplate]

An iterator of DataTemplate objects matching the provided criteria.

Source code in src/albert/collections/data_templates.py
def list(
    self,
    *,
    name: str | None = None,
    user_id: str | None = None,
    order_by: OrderBy = OrderBy.DESCENDING,
    limit: int = 100,
    offset: int = 0,
) -> Iterator[DataTemplate]:
    """
    Lists data template entities with optional filters.

    Parameters
    ----------
    name : Union[str, None], optional
        The name of the data template to filter by, by default None.
    user_id : str, optional
        user_id to filter by, by default None.
    order_by : OrderBy, optional
        The order by which to sort the results, by default OrderBy.DESCENDING.

    Returns
    -------
    Iterator[DataTemplate]
        An iterator of DataTemplate objects matching the provided criteria.
    """

    def deserialize(items: list[dict]) -> Iterator[DataTemplate]:
        for item in items:
            id = item["albertId"]
            try:
                yield self.get_by_id(id=id)
            except AlbertHTTPError as e:
                logger.warning(f"Error fetching parameter group {id}: {e}")
        # get by ids is not currently returning metadata correctly, so temp fixing this
        # return self.get_by_ids(ids=[x["albertId"] for x in items])

    params = {
        "limit": limit,
        "offset": offset,
        "order": OrderBy(order_by).value if order_by else None,
        "text": name,
        "userId": user_id,
    }

    return AlbertPaginator(
        mode=PaginationMode.OFFSET,
        path=f"{self.base_path}/search",
        session=self.session,
        deserialize=deserialize,
        params=params,
    )

update

update(*, data_template: DataTemplate) -> DataTemplate

Updates a data template.

Parameters:

Name Type Description Default
data_template DataTemplate

The DataTemplate object to update. The ID must be set and matching the ID of the DataTemplate to update.

required

Returns:

Type Description
DataTemplate

The Updated DataTemplate object.

Source code in src/albert/collections/data_templates.py
def update(self, *, data_template: DataTemplate) -> DataTemplate:
    """Updates a data template.

    Parameters
    ----------
    data_template : DataTemplate
        The DataTemplate object to update. The ID must be set and matching the ID of the DataTemplate to update.

    Returns
    -------
    DataTemplate
        The Updated DataTemplate object.
    """

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

    base_payload = self._generate_patch_payload(existing=existing, updated=data_template)

    path = f"{self.base_path}/{existing.id}"
    (
        general_patches,
        new_data_columns,
        data_column_enum_patches,
        new_parameters,
        parameter_enum_patches,
        parameter_patches,
    ) = generate_data_template_patches(
        initial_patches=base_payload,
        updated_data_template=data_template,
        existing_data_template=existing,
    )

    if len(new_data_columns) > 0:
        self.session.put(
            f"{self.base_path}/{existing.id}/datacolumns",
            json={
                "DataColumns": [
                    x.model_dump(mode="json", by_alias=True, exclude_none=True)
                    for x in new_data_columns
                ],
            },
        )
    if len(data_column_enum_patches) > 0:
        for sequence, enum_patches in data_column_enum_patches.items():
            if len(enum_patches) == 0:
                continue
            self.session.put(
                f"{self.base_path}/{existing.id}/datacolumns/{sequence}/enums",
                json=enum_patches,  # these are simple dicts for now
            )
    if len(new_parameters) > 0:
        self.session.put(
            f"{self.base_path}/{existing.id}/parameters",
            json={
                "Parameters": [
                    x.model_dump(mode="json", by_alias=True, exclude_none=True)
                    for x in new_parameters
                ],
            },
        )
    if len(parameter_enum_patches) > 0:
        for sequence, enum_patches in parameter_enum_patches.items():
            if len(enum_patches) == 0:
                continue
            self.session.put(
                f"{self.base_path}/{existing.id}/parameters/{sequence}/enums",
                json=enum_patches,  # these are simple dicts for now
            )
    if len(parameter_patches) > 0:
        payload = PGPatchPayload(data=parameter_patches)
        self.session.patch(
            path + "/parameters",
            json=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
        )
    if len(general_patches.data) > 0:
        payload = GeneralPatchPayload(data=general_patches.data)
        self.session.patch(
            path,
            json=payload.model_dump(mode="json", by_alias=True, exclude_none=True),
        )
    return self.get_by_id(id=data_template.id)

DataType

Bases: str, Enum

ENUM class-attribute instance-attribute

ENUM = 'enum'

NUMBER class-attribute instance-attribute

NUMBER = 'number'

STRING class-attribute instance-attribute

STRING = 'string'

EnumValidationValue

Bases: BaseAlbertModel

Represents a value for an enum type validation.

Attributes:

Name Type Description
text str

The text of the enum value.

id str | None

The ID of the enum value. If not provided, the ID will be generated upon creation.

id class-attribute instance-attribute

id: str | None = Field(default=None)

original_text class-attribute instance-attribute

original_text: str | None = Field(
    default=None,
    exclude=True,
    frozen=True,
    alias="originalText",
)

text class-attribute instance-attribute

text: str = Field()

OrderBy

Bases: str, Enum

ASCENDING class-attribute instance-attribute

ASCENDING = 'asc'

DESCENDING class-attribute instance-attribute

DESCENDING = 'desc'

ParameterValue

Bases: BaseAlbertModel

The value of a parameter in a parameter group.

Attributes:

Name Type Description
parameter Parameter

The Parameter resource this value is associated with. Provide either an id or a parameter keyword argument.

id str | None

The Albert ID of the Parameter resource this value is associated with. Provide either an id or a parameter keyword argument.

category ParameterCategory

The category of the parameter.

short_name str | None

The short name of the parameter value.

value str | None

The default value of the parameter. Can be a string or an InventoryItem (if, for example, the parameter is an instrumnt choice).

unit Unit | None

The unit of measure for the provided parameter value.

name str

The name of the parameter. Read-only.

sequence int

The sequence of the parameter. Read-only.

Methods:

Name Description
set_parameter_fields
validate_parameter_value

added class-attribute instance-attribute

added: AuditFields | None = Field(
    alias="Added", default=None, exclude=True
)

category class-attribute instance-attribute

category: ParameterCategory | None = Field(default=None)

id class-attribute instance-attribute

id: str | None = Field(default=None)

name class-attribute instance-attribute

name: str | None = Field(
    default=None, exclude=True, frozen=True
)

original_name class-attribute instance-attribute

original_name: str | None = Field(
    default=None,
    alias="originalName",
    frozen=True,
    exclude=True,
)

original_short_name class-attribute instance-attribute

original_short_name: str | None = Field(
    default=None,
    alias="originalShortName",
    frozen=True,
    exclude=True,
)

parameter class-attribute instance-attribute

parameter: Parameter = Field(default=None, exclude=True)

sequence class-attribute instance-attribute

sequence: str | None = Field(
    default=None, exclude=True, frozen=True
)

short_name class-attribute instance-attribute

short_name: str | None = Field(
    alias="shortName", default=None
)

unit class-attribute instance-attribute

unit: SerializeAsEntityLink[Unit] | None = Field(
    alias="Unit", default=None
)

validation class-attribute instance-attribute

validation: list[ValueValidation] | None = Field(
    default_factory=list
)

value class-attribute instance-attribute

value: str | SerializeAsEntityLink[InventoryItem] | None = (
    Field(default=None)
)

set_parameter_fields

set_parameter_fields() -> ParameterValue
Source code in src/albert/resources/parameter_groups.py
@model_validator(mode="after")
def set_parameter_fields(self) -> "ParameterValue":
    if self.parameter is None and self.id is None:
        raise ValueError("Please provide either an id or an parameter object.")

    if self.parameter is not None:
        object.__setattr__(self, "id", self.parameter.id)
        object.__setattr__(self, "category", self.parameter.category)
        object.__setattr__(self, "name", self.parameter.name)

    return self

validate_parameter_value classmethod

validate_parameter_value(value: Any) -> Any
Source code in src/albert/resources/parameter_groups.py
@field_validator("value", mode="before")
@classmethod
def validate_parameter_value(cls, value: Any) -> Any:
    # Bug in ParameterGroups sometimes returns incorrect JSON from batch endpoint
    # Set to None if value is a dict but no ID field
    # Reference: https://linear.app/albert-invent/issue/IN-10
    if isinstance(value, dict) and "id" not in value:
        return None
    return value