Skip to content

Notebooks

albert.collections.notebooks.NotebookCollection

NotebookCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage Notebooks in the Albert platform.

A Notebook is an electronic lab notebook (ELN): an ordered document made up of content blocks (paragraphs, headers, checklists, tables, images, file attachments, and Ketcher chemical drawings). Each Notebook is attached to a parent entity, which is a Project, a Task, or a custom template, and is referenced by its Notebook ID (format NTB..., e.g. "NTB123").

Notebook content is edited block-by-block rather than by overwriting the whole document. Create an empty Notebook with create, then add or change blocks with update_block_content or append_blocks. The update method changes only the Notebook name.

This collection is accessed as client.notebooks.

Example

from albert import Albert

client = Albert()
notebook = client.notebooks.get_by_id(id="NTB123")
for block in notebook.blocks:
    print(block.id, block.type)

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

Methods:

Name Description
get_by_id

Get a single notebook by its ID.

list_by_parent_id

List the notebooks attached to a given parent (project or task).

create

Find or create an (empty) notebook for the given parent.

delete

Delete a notebook by its ID.

update

Update a notebook's name.

update_block_content

Replace the notebook's block content with the blocks on the object.

append_blocks

Append blocks to the end of a notebook, preserving existing blocks.

get_block_by_id

Get a single block from a notebook by block ID.

copy

Copy a notebook into a specified parent.

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

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

    Parameters
    ----------
    session : AlbertSession
        The authenticated Albert session used for API calls.
    """
    super().__init__(session=session)
    self.base_path = f"/api/{NotebookCollection._api_version}/notebooks"
    self._files = FileCollection(session=session)
    self._synthesis = SynthesisCollection(session=session)

base_path

base_path = (
    f"/api/{NotebookCollection._api_version}/notebooks"
)

get_by_id

get_by_id(*, id: NotebookId) -> Notebook

Get a single Notebook by its ID.

Example

notebook = client.notebooks.get_by_id(id="NTB123")
print(notebook.name)

Parameters:

Name Type Description Default
id NotebookId

The Notebook ID to retrieve (format NTB...).

required

Returns:

Type Description
Notebook

The fully populated notebook.

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

    !!! example
        ```python
        notebook = client.notebooks.get_by_id(id="NTB123")
        print(notebook.name)
        ```

    Parameters
    ----------
    id : NotebookId
        The Notebook ID to retrieve (format ``NTB...``).

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

list_by_parent_id

list_by_parent_id(
    *, parent_id: ProjectId | TaskId
) -> list[Notebook]

List the Notebooks attached to a given parent entity.

Example

notebooks = client.notebooks.list_by_parent_id(parent_id="PRO123")
for notebook in notebooks:
    print(notebook.id, notebook.name)

Parameters:

Name Type Description Default
parent_id ProjectId or TaskId

The ID of the parent entity whose notebooks should be listed (a Project ID, format PRO..., or a Task ID, format TAS...).

required

Returns:

Type Description
list[Notebook]

The fully populated notebooks attached to the parent.

Source code in src/albert/collections/notebooks.py
@validate_call
def list_by_parent_id(self, *, parent_id: ProjectId | TaskId) -> list[Notebook]:
    """List the Notebooks attached to a given parent entity.

    !!! example
        ```python
        notebooks = client.notebooks.list_by_parent_id(parent_id="PRO123")
        for notebook in notebooks:
            print(notebook.id, notebook.name)
        ```

    Parameters
    ----------
    parent_id : ProjectId or TaskId
        The ID of the parent entity whose notebooks should be listed
        (a Project ID, format ``PRO...``, or a Task ID, format ``TAS...``).

    Returns
    -------
    list[Notebook]
        The fully populated notebooks attached to the parent.
    """

    # search
    response = self.session.get(f"{self.base_path}/{parent_id}/search")
    # return
    return [self.get_by_id(id=x["id"]) for x in response.json()["Items"]]

create

create(*, notebook: Notebook) -> Notebook

Find or create a Notebook for the provided notebook.

The endpoint first tries to find an existing notebook for the same parent with matching properties; if one is found it is returned, otherwise a new notebook is created.

The notebook must be created empty: the blocks field must be empty. Add content afterward with update_block_content or append_blocks.

Example

from albert.resources.notebooks import Notebook

notebook = client.notebooks.create(
    notebook=Notebook(name="Trial 1 log", parent_id="PRO123")
)

Parameters:

Name Type Description Default
notebook Notebook

The notebook to find or create. Must have a parent_id and no pre-filled blocks.

required

Returns:

Type Description
Notebook

The found or newly created notebook.

Raises:

Type Description
AlbertException

If the notebook has pre-filled blocks.

Source code in src/albert/collections/notebooks.py
def create(self, *, notebook: Notebook) -> Notebook:
    """Find or create a Notebook for the provided notebook.

    The endpoint first tries to find an existing notebook for the same parent
    with matching properties; if one is found it is returned, otherwise a new
    notebook is created.

    The notebook must be created empty: the ``blocks`` field must be empty.
    Add content afterward with [`update_block_content`][albert.collections.notebooks.NotebookCollection.update_block_content] or
    [`append_blocks`][albert.collections.notebooks.NotebookCollection.append_blocks].

    !!! example
        ```python
        from albert.resources.notebooks import Notebook

        notebook = client.notebooks.create(
            notebook=Notebook(name="Trial 1 log", parent_id="PRO123")
        )
        ```

    Parameters
    ----------
    notebook : Notebook
        The notebook to find or create. Must have a ``parent_id`` and no
        pre-filled ``blocks``.

    Returns
    -------
    Notebook
        The found or newly created notebook.

    Raises
    ------
    AlbertException
        If the notebook has pre-filled blocks.
    """
    if notebook.blocks:
        # This check keeps a user from corrupting the Notebook data.
        msg = (
            "Cannot create a Notebook with pre-filled blocks. "
            "Set `blocks=[]` (or do not set it) when creating it. "
            "Use `.update_block_content()` afterward to add, update, or delete blocks."
        )
        raise AlbertException(msg)
    response = self.session.post(
        url=self.base_path,
        json=notebook.model_dump(mode="json", by_alias=True, exclude_none=True),
        params={"parentId": notebook.parent_id},
    )
    return Notebook(**response.json())

delete

delete(*, id: NotebookId) -> None

Delete a Notebook by its ID.

Example

client.notebooks.delete(id="NTB123")

Parameters:

Name Type Description Default
id NotebookId

The Notebook ID to delete (format NTB...).

required

Returns:

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

    !!! example
        ```python
        client.notebooks.delete(id="NTB123")
        ```

    Parameters
    ----------
    id : NotebookId
        The Notebook ID to delete (format ``NTB...``).

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

update

update(*, notebook: Notebook) -> Notebook

Update a Notebook's name.

This method changes only the notebook name; it does not modify block content. Use update_block_content to change the blocks.

Example

notebook = client.notebooks.get_by_id(id="NTB123")
notebook.name = "Revised trial log"
notebook = client.notebooks.update(notebook=notebook)

Parameters:

Name Type Description Default
notebook Notebook

The notebook carrying the desired name. It must have an id.

required

Returns:

Type Description
Notebook

The updated notebook.

Notes

The following fields can be updated: name.

Source code in src/albert/collections/notebooks.py
def update(self, *, notebook: Notebook) -> Notebook:
    """Update a Notebook's name.

    This method changes only the notebook name; it does not modify block
    content. Use [`update_block_content`][albert.collections.notebooks.NotebookCollection.update_block_content] to change the blocks.

    !!! example
        ```python
        notebook = client.notebooks.get_by_id(id="NTB123")
        notebook.name = "Revised trial log"
        notebook = client.notebooks.update(notebook=notebook)
        ```

    Parameters
    ----------
    notebook : Notebook
        The notebook carrying the desired name. It must have an ``id``.

    Returns
    -------
    Notebook
        The updated notebook.

    Notes
    -----
    The following fields can be updated: ``name``.
    """
    existing_notebook = self.get_by_id(id=notebook.id)
    patch_data = self._generate_patch_payload(existing=existing_notebook, updated=notebook)
    url = f"{self.base_path}/{notebook.id}"

    self.session.patch(url, json=patch_data.model_dump(mode="json", by_alias=True))

    return self.get_by_id(id=notebook.id)

update_block_content

update_block_content(*, notebook: Notebook) -> Notebook

Replace a Notebook's block content with the blocks on the object.

The notebook's blocks list is treated as the desired final state: the order of the blocks is preserved, any block not already on Albert is created, and any existing block that is no longer present is deleted. This does not change the notebook name (use update for that).

Warning

Updating existing Ketcher blocks is not supported. To change a Ketcher block, delete it and create a new one instead.

Example

# Add a Ketcher block from SMILES
from albert.resources.notebooks import KetcherBlock, KetcherContent

notebook = client.notebooks.get_by_id(id="NTB123")
notebook.blocks.append(
    KetcherBlock(content=KetcherContent(smiles="CCO"))
)
notebook = client.notebooks.update_block_content(notebook=notebook)

Parameters:

Name Type Description Default
notebook Notebook

The notebook whose blocks describe the desired content. It must have an id.

required

Returns:

Type Description
Notebook

The updated notebook.

Raises:

Type Description
AlbertException

If the notebook has no id, if two blocks share the same id, or if an existing block's type is changed in place.

Source code in src/albert/collections/notebooks.py
def update_block_content(self, *, notebook: Notebook) -> Notebook:
    """Replace a Notebook's block content with the blocks on the object.

    The notebook's ``blocks`` list is treated as the desired final state: the
    order of the blocks is preserved, any block not already on Albert is
    created, and any existing block that is no longer present is deleted. This
    does not change the notebook name (use [`update`][albert.collections.notebooks.NotebookCollection.update] for that).

    !!! warning
        Updating existing Ketcher blocks is not supported. To change a Ketcher
        block, delete it and create a new one instead.

    !!! example
        ```python
        # Add a Ketcher block from SMILES
        from albert.resources.notebooks import KetcherBlock, KetcherContent

        notebook = client.notebooks.get_by_id(id="NTB123")
        notebook.blocks.append(
            KetcherBlock(content=KetcherContent(smiles="CCO"))
        )
        notebook = client.notebooks.update_block_content(notebook=notebook)
        ```

    Parameters
    ----------
    notebook : Notebook
        The notebook whose ``blocks`` describe the desired content. It must
        have an ``id``.

    Returns
    -------
    Notebook
        The updated notebook.

    Raises
    ------
    AlbertException
        If the notebook has no ``id``, if two blocks share the same id, or if
        an existing block's type is changed in place.
    """
    if notebook.id is None:
        raise AlbertException("Notebook id is required to update block content.")
    put_data, ketcher_updates = self._generate_put_block_payload(notebook=notebook)
    url = f"{self.base_path}/{notebook.id}/content"

    self.session.put(url, json=put_data.model_dump(mode="json", by_alias=True))

    for action in ketcher_updates:
        self._synthesis.update_canvas_data(
            synthesis_id=action.synthesis_id,
            smiles=action.smiles,
            data=action.data,
            png=action.png,
        )
        self._synthesis.create_reactant_productant_table(synthesis_id=action.synthesis_id)
    return self.get_by_id(id=notebook.id)

append_blocks

append_blocks(
    *, id: NotebookId, blocks: list[NotebookBlock]
) -> Notebook

Append blocks to the end of a Notebook, preserving existing blocks.

This is a convenience wrapper around update_block_content: it fetches the current notebook, adds the given blocks after the existing ones, and saves.

Example

# Append a paragraph block
from albert.resources.notebooks import ParagraphBlock, ParagraphContent

notebook = client.notebooks.append_blocks(
    id="NTB123",
    blocks=[ParagraphBlock(content=ParagraphContent(text="Hello"))],
)

Parameters:

Name Type Description Default
id NotebookId

The Notebook ID to append to (format NTB...).

required
blocks list[NotebookBlock]

The blocks to append to the end of the notebook.

required

Returns:

Type Description
Notebook

The updated notebook.

Source code in src/albert/collections/notebooks.py
@validate_call
def append_blocks(self, *, id: NotebookId, blocks: list[NotebookBlock]) -> Notebook:
    """Append blocks to the end of a Notebook, preserving existing blocks.

    This is a convenience wrapper around [`update_block_content`][albert.collections.notebooks.NotebookCollection.update_block_content]: it
    fetches the current notebook, adds the given blocks after the existing
    ones, and saves.

    !!! example
        ```python
        # Append a paragraph block
        from albert.resources.notebooks import ParagraphBlock, ParagraphContent

        notebook = client.notebooks.append_blocks(
            id="NTB123",
            blocks=[ParagraphBlock(content=ParagraphContent(text="Hello"))],
        )
        ```

    Parameters
    ----------
    id : NotebookId
        The Notebook ID to append to (format ``NTB...``).
    blocks : list[NotebookBlock]
        The blocks to append to the end of the notebook.

    Returns
    -------
    Notebook
        The updated notebook.
    """
    notebook = self.get_by_id(id=id)
    notebook.blocks.extend(blocks)
    return self.update_block_content(notebook=notebook)

get_block_by_id

get_block_by_id(
    *, notebook_id: NotebookId, block_id: str
) -> NotebookBlock

Get a single block from a Notebook by block ID.

Example

block = client.notebooks.get_block_by_id(
    notebook_id="NTB123", block_id="abc-123"
)

Parameters:

Name Type Description Default
notebook_id NotebookId

The Notebook ID the block belongs to (format NTB...).

required
block_id str

The ID of the block to retrieve.

required

Returns:

Type Description
NotebookBlock

The requested block, typed according to its block type (e.g. ParagraphBlock).

Source code in src/albert/collections/notebooks.py
@validate_call
def get_block_by_id(self, *, notebook_id: NotebookId, block_id: str) -> NotebookBlock:
    """Get a single block from a Notebook by block ID.

    !!! example
        ```python
        block = client.notebooks.get_block_by_id(
            notebook_id="NTB123", block_id="abc-123"
        )
        ```

    Parameters
    ----------
    notebook_id : NotebookId
        The Notebook ID the block belongs to (format ``NTB...``).
    block_id : str
        The ID of the block to retrieve.

    Returns
    -------
    NotebookBlock
        The requested block, typed according to its block type (e.g.
        [`ParagraphBlock`][albert.resources.notebooks.ParagraphBlock]).
    """
    response = self.session.get(f"{self.base_path}/{notebook_id}/blocks/{block_id}")
    return TypeAdapter(NotebookBlock).validate_python(response.json())

copy

copy(
    *,
    notebook_copy_info: NotebookCopyInfo,
    type: NotebookCopyType,
) -> Notebook

Copy a Notebook into a specified parent.

Example

from albert.resources.notebooks import NotebookCopyInfo, NotebookCopyType

copy = client.notebooks.copy(
    notebook_copy_info=NotebookCopyInfo(id="NTB123", parent_id="PRO456"),
    type=NotebookCopyType.PROJECT,
)

Parameters:

Name Type Description Default
notebook_copy_info NotebookCopyInfo

Describes the source notebook and the destination parent for the copy.

required
type NotebookCopyType

The kind of copy to perform (e.g. into a template, task, or project, or restoring a template).

required

Returns:

Type Description
Notebook

The newly created copy.

Source code in src/albert/collections/notebooks.py
def copy(self, *, notebook_copy_info: NotebookCopyInfo, type: NotebookCopyType) -> Notebook:
    """Copy a Notebook into a specified parent.

    !!! example
        ```python
        from albert.resources.notebooks import NotebookCopyInfo, NotebookCopyType

        copy = client.notebooks.copy(
            notebook_copy_info=NotebookCopyInfo(id="NTB123", parent_id="PRO456"),
            type=NotebookCopyType.PROJECT,
        )
        ```

    Parameters
    ----------
    notebook_copy_info : NotebookCopyInfo
        Describes the source notebook and the destination parent for the copy.
    type : NotebookCopyType
        The kind of copy to perform (e.g. into a template, task, or project,
        or restoring a template).

    Returns
    -------
    Notebook
        The newly created copy.
    """
    response = self.session.post(
        url=f"{self.base_path}/copy",
        json=notebook_copy_info.model_dump(mode="json", by_alias=True, exclude_none=True),
        params={"type": type, "parentId": notebook_copy_info.parent_id},
    )
    return Notebook(**response.json())