Skip to content

Synthesis

albert.collections.synthesis.SynthesisCollection

SynthesisCollection(*, session: AlbertSession)

Bases: BaseCollection

Manage synthesis (reaction) records on the Albert platform.

A synthesis record documents a chemical reaction on a drawing canvas: the reactants and products of the reaction, drawn as chemical structures (via the Ketcher structure editor) and laid out in a reaction worksheet table. Each row of that table is a reaction participant (a reactant or a product), and its quantities (mass, moles, equivalents, concentration) can be filled in.

A synthesis always belongs to a block inside a Notebook (see NotebookCollection); the parent notebook is supplied when the record is created. Synthesis records are referenced by their Synthesis ID (format SYN..., e.g. "SYNA1").

A typical flow is: create the record, draw the reaction and push the canvas with update_canvas_data, initialize the reactant/product table with create_reactant_productant_table, then set per-row quantities with update_reactant_row_values.

This collection is accessed as client.synthesis.

Example

from albert import Albert
client = Albert()
synthesis = client.synthesis.create(
    parent_id="NTBA1",
    name="Amide coupling",
)
print(synthesis.id)

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

Methods:

Name Description
create

Create a synthesis record for a notebook Ketcher block.

get_by_id

Get a single synthesis record by its ID.

update

Update an existing synthesis record.

update_canvas_data

Replace the drawn reaction (SMILES, canvas data, and preview image).

update_reactant_row_values

Set the quantities (mass, moles, eq, concentration) for one reactant row.

create_reactant_productant_table

Initialize the reactant/product table and reveal the reaction worksheet.

Parameters:

Name Type Description Default
session AlbertSession

The authenticated Albert session used for API calls.

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

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

base_path

base_path = (
    f"/api/{SynthesisCollection._api_version}/synthesis"
)

create

create(
    *,
    parent_id: NotebookId | str,
    name: str,
    block_id: str | None = None,
    smiles: str | None = None,
) -> Synthesis

Create a synthesis record for a notebook Ketcher block.

Use this to start documenting a reaction inside a notebook. The new record is empty; draw the reaction and push it with update_canvas_data, and build out the reactant/product table with create_reactant_productant_table.

Example

synthesis = client.synthesis.create(
    parent_id="NTBA1",
    name="Amide coupling",
    smiles="CC(=O)O.CN>>CC(=O)NC",
)
synthesis.id
# 'SYNA1'

Parameters:

Name Type Description Default
parent_id NotebookId or str

The Notebook ID that owns the synthesis record (format NTB...).

required
name str

A human-readable name for the synthesis.

required
block_id str

The Ketcher block ID to associate with the synthesis. A new ID is generated when not provided.

None
smiles str

An initial reaction SMILES string to seed the canvas.

None

Returns:

Type Description
Synthesis

The created synthesis record, populated with its assigned Synthesis ID.

Source code in src/albert/collections/synthesis.py
@validate_call
def create(
    self,
    *,
    parent_id: NotebookId | str,
    name: str,
    block_id: str | None = None,
    smiles: str | None = None,
) -> Synthesis:
    """Create a synthesis record for a notebook Ketcher block.

    Use this to start documenting a reaction inside a notebook. The new record
    is empty; draw the reaction and push it with [`update_canvas_data`][albert.collections.synthesis.SynthesisCollection.update_canvas_data], and
    build out the reactant/product table with
    [`create_reactant_productant_table`][albert.collections.synthesis.SynthesisCollection.create_reactant_productant_table].

    !!! example
        ```python
        synthesis = client.synthesis.create(
            parent_id="NTBA1",
            name="Amide coupling",
            smiles="CC(=O)O.CN>>CC(=O)NC",
        )
        synthesis.id
        # 'SYNA1'
        ```

    Parameters
    ----------
    parent_id : NotebookId or str
        The Notebook ID that owns the synthesis record (format ``NTB...``).
    name : str
        A human-readable name for the synthesis.
    block_id : str, optional
        The Ketcher block ID to associate with the synthesis. A new ID is
        generated when not provided.
    smiles : str, optional
        An initial reaction SMILES string to seed the canvas.

    Returns
    -------
    Synthesis
        The created synthesis record, populated with its assigned Synthesis ID.
    """
    payload: dict[str, Any] = {"name": name, "blockId": block_id or str(uuid.uuid4())}
    if smiles is not None:
        payload["smiles"] = smiles
    response = self.session.post(
        url=self.base_path,
        params={"parentId": parent_id},
        json=payload,
    )
    return Synthesis(**response.json())

get_by_id

get_by_id(
    *,
    id: SynthesisId,
    include_recommendations: bool = False,
    include_predictions: bool = False,
    version: str | None = None,
) -> Synthesis

Get a synthesis record by its ID.

Example

synthesis = client.synthesis.get_by_id(id="SYNA1")
print(synthesis.name)

Parameters:

Name Type Description Default
id SynthesisId

The Synthesis ID to retrieve (format SYN..., e.g. "SYNA1").

required
include_recommendations bool

When True, include reaction recommendations in the response. Defaults to False.

False
include_predictions bool

When True, include reaction predictions in the response. Defaults to False.

False
version str

A specific version of the record to retrieve. Defaults to the latest.

None

Returns:

Type Description
Synthesis

The fully populated synthesis record.

Source code in src/albert/collections/synthesis.py
@validate_call
def get_by_id(
    self,
    *,
    id: SynthesisId,
    include_recommendations: bool = False,
    include_predictions: bool = False,
    version: str | None = None,
) -> Synthesis:
    """Get a synthesis record by its ID.

    !!! example
        ```python
        synthesis = client.synthesis.get_by_id(id="SYNA1")
        print(synthesis.name)
        ```

    Parameters
    ----------
    id : SynthesisId
        The Synthesis ID to retrieve (format ``SYN...``, e.g. ``"SYNA1"``).
    include_recommendations : bool, optional
        When True, include reaction recommendations in the response.
        Defaults to False.
    include_predictions : bool, optional
        When True, include reaction predictions in the response.
        Defaults to False.
    version : str, optional
        A specific version of the record to retrieve. Defaults to the latest.

    Returns
    -------
    Synthesis
        The fully populated synthesis record.
    """
    params: dict[str, Any] = {
        "recommendations": include_recommendations,
        "predictions": include_predictions,
    }
    if version:
        params["version"] = version
    response = self.session.get(
        url=f"{self.base_path}/{id}",
        params=params,
    )
    return Synthesis(**response.json())

update_canvas_data

update_canvas_data(
    *,
    synthesis_id: SynthesisId,
    smiles: str,
    data: str,
    png: str,
) -> Synthesis

Update the Ketcher canvas data for a synthesis record.

Use this to save the drawn reaction after editing it in the structure editor. It replaces the reaction SMILES, the serialized canvas, and the rendered preview image together.

Example

synthesis = client.synthesis.update_canvas_data(
    synthesis_id="SYNA1",
    smiles="CC(=O)O.CN>>CC(=O)NC",
    data=serialized_canvas,
    png=base64_png,
)

Parameters:

Name Type Description Default
synthesis_id SynthesisId

The Synthesis ID to update (format SYN...).

required
smiles str

The updated reaction SMILES string.

required
data str

The serialized canvas data from the structure editor.

required
png str

The base64-encoded PNG preview of the canvas.

required

Returns:

Type Description
Synthesis

The updated synthesis record.

Source code in src/albert/collections/synthesis.py
@validate_call
def update_canvas_data(
    self, *, synthesis_id: SynthesisId, smiles: str, data: str, png: str
) -> Synthesis:
    """Update the Ketcher canvas data for a synthesis record.

    Use this to save the drawn reaction after editing it in the structure
    editor. It replaces the reaction SMILES, the serialized canvas, and the
    rendered preview image together.

    !!! example
        ```python
        synthesis = client.synthesis.update_canvas_data(
            synthesis_id="SYNA1",
            smiles="CC(=O)O.CN>>CC(=O)NC",
            data=serialized_canvas,
            png=base64_png,
        )
        ```

    Parameters
    ----------
    synthesis_id : SynthesisId
        The Synthesis ID to update (format ``SYN...``).
    smiles : str
        The updated reaction SMILES string.
    data : str
        The serialized canvas data from the structure editor.
    png : str
        The base64-encoded PNG preview of the canvas.

    Returns
    -------
    Synthesis
        The updated synthesis record.
    """
    payload = {
        "smiles": smiles,
        "canvasData": {"data": data, "png": png},
    }
    response = self.session.put(
        url=f"{self.base_path}/{synthesis_id}",
        json=payload,
    )
    return Synthesis(**response.json())

update

update(*, synthesis: Synthesis) -> Synthesis

Update an existing synthesis record.

Fetch the record with get_by_id, modify the updatable fields on the returned object, then pass it here. Only the fields listed in Notes are sent; other differences are ignored. If nothing changed, the existing record is returned unmodified.

Example

synthesis = client.synthesis.get_by_id(id="SYNA1")
synthesis.name = "Amide coupling (revised)"
updated = client.synthesis.update(synthesis=synthesis)

Parameters:

Name Type Description Default
synthesis Synthesis

The synthesis record containing updated fields. Its id must be set.

required

Returns:

Type Description
Synthesis

The updated synthesis record.

Raises:

Type Description
AlbertException

If the synthesis record is missing an ID.

Notes

The following fields can be updated: name, status, hide_reaction_worksheet.

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

    Fetch the record with [`get_by_id`][albert.collections.synthesis.SynthesisCollection.get_by_id], modify the updatable fields on the
    returned object, then pass it here. Only the fields listed in Notes are
    sent; other differences are ignored. If nothing changed, the existing
    record is returned unmodified.

    !!! example
        ```python
        synthesis = client.synthesis.get_by_id(id="SYNA1")
        synthesis.name = "Amide coupling (revised)"
        updated = client.synthesis.update(synthesis=synthesis)
        ```

    Parameters
    ----------
    synthesis : Synthesis
        The synthesis record containing updated fields. Its ``id`` must be set.

    Returns
    -------
    Synthesis
        The updated synthesis record.

    Raises
    ------
    AlbertException
        If the synthesis record is missing an ID.

    Notes
    -----
    The following fields can be updated: ``name``, ``status``,
    ``hide_reaction_worksheet``.
    """
    if synthesis.id is None:
        msg = "Synthesis id is required to update the record."
        raise AlbertException(msg)
    existing = self.get_by_id(id=synthesis.id)
    patch_data = self._generate_patch_payload(existing=existing, updated=synthesis)
    if len(patch_data.data) == 0:
        return existing
    self.session.patch(
        url=f"{self.base_path}/{synthesis.id}",
        json=patch_data.model_dump(by_alias=True, mode="json"),
    )
    return self.get_by_id(id=synthesis.id)

update_reactant_row_values

update_reactant_row_values(
    *,
    synthesis_id: SynthesisId,
    row_id: str,
    values: ReactantValues,
) -> Synthesis

Update the quantities for a single reactant row.

Sets the mass, moles, equivalents, and concentration for one row of the reaction worksheet table. The row is identified by its row ID, which can be read from Synthesis.reactants (each ReactionParticipant has a row_id) or from Synthesis.row_sequence.reactants.

Example

from albert.resources.synthesis import ReactantValues
synthesis = client.synthesis.get_by_id(id="SYNA1")
row_id = synthesis.reactants[0].row_id
updated = client.synthesis.update_reactant_row_values(
    synthesis_id="SYNA1",
    row_id=row_id,
    values=ReactantValues(mass=10.0, eq=1.0),
)

Parameters:

Name Type Description Default
synthesis_id SynthesisId

The Synthesis ID to update (format SYN...).

required
row_id str

The reactant row ID to update.

required
values ReactantValues

The quantities to apply to the reactant row.

required

Returns:

Type Description
Synthesis

The updated synthesis record.

Source code in src/albert/collections/synthesis.py
@validate_call
def update_reactant_row_values(
    self,
    *,
    synthesis_id: SynthesisId,
    row_id: str,
    values: ReactantValues,
) -> Synthesis:
    """Update the quantities for a single reactant row.

    Sets the mass, moles, equivalents, and concentration for one row of the
    reaction worksheet table. The row is identified by its row ID, which can be
    read from ``Synthesis.reactants`` (each
    [`ReactionParticipant`][albert.resources.synthesis.ReactionParticipant] has a ``row_id``)
    or from ``Synthesis.row_sequence.reactants``.

    !!! example
        ```python
        from albert.resources.synthesis import ReactantValues
        synthesis = client.synthesis.get_by_id(id="SYNA1")
        row_id = synthesis.reactants[0].row_id
        updated = client.synthesis.update_reactant_row_values(
            synthesis_id="SYNA1",
            row_id=row_id,
            values=ReactantValues(mass=10.0, eq=1.0),
        )
        ```

    Parameters
    ----------
    synthesis_id : SynthesisId
        The Synthesis ID to update (format ``SYN...``).
    row_id : str
        The reactant row ID to update.
    values : ReactantValues
        The quantities to apply to the reactant row.

    Returns
    -------
    Synthesis
        The updated synthesis record.
    """
    payload = {
        "data": [
            {
                "rowId": row_id,
                "operation": "update",
                "attribute": "values",
                "newValue": values.model_dump(by_alias=True, mode="json"),
            }
        ]
    }
    self.session.patch(
        url=f"{self.base_path}/{synthesis_id}/reactants/rows",
        json=payload,
    )
    return self.get_by_id(id=synthesis_id)

create_reactant_productant_table

create_reactant_productant_table(
    *, synthesis_id: SynthesisId
) -> Synthesis

Initialize the reactant/product table for a synthesis.

Sets up the reaction worksheet so quantities can be entered: it seeds the first reactant row (concentration 100), reveals the reaction worksheet, and attaches the backing inventory. If the table has already been initialized (the record already has an inventory ID) or there are no reactant rows to seed, the record is returned unchanged.

Call this after the reaction has been drawn (see update_canvas_data) and before setting per-row quantities with update_reactant_row_values.

Example

synthesis = client.synthesis.create_reactant_productant_table(
    synthesis_id="SYNA1",
)

Parameters:

Name Type Description Default
synthesis_id SynthesisId

The Synthesis ID to initialize (format SYN...).

required

Returns:

Type Description
Synthesis

The synthesis record with its reactant/product table initialized.

Source code in src/albert/collections/synthesis.py
@validate_call
def create_reactant_productant_table(self, *, synthesis_id: SynthesisId) -> Synthesis:
    """Initialize the reactant/product table for a synthesis.

    Sets up the reaction worksheet so quantities can be entered: it seeds the
    first reactant row (concentration 100), reveals the reaction worksheet, and
    attaches the backing inventory. If the table has already been initialized
    (the record already has an inventory ID) or there are no reactant rows to
    seed, the record is returned unchanged.

    Call this after the reaction has been drawn (see
    [`update_canvas_data`][albert.collections.synthesis.SynthesisCollection.update_canvas_data]) and before setting per-row quantities with
    [`update_reactant_row_values`][albert.collections.synthesis.SynthesisCollection.update_reactant_row_values].

    !!! example
        ```python
        synthesis = client.synthesis.create_reactant_productant_table(
            synthesis_id="SYNA1",
        )
        ```

    Parameters
    ----------
    synthesis_id : SynthesisId
        The Synthesis ID to initialize (format ``SYN...``).

    Returns
    -------
    Synthesis
        The synthesis record with its reactant/product table initialized.
    """
    synthesis = self.get_by_id(id=synthesis_id)
    if synthesis.inventory_id is not None:
        return synthesis
    row_sequence: RowSequence | None = synthesis.row_sequence
    reactant_row_ids = row_sequence.reactants if row_sequence else []
    if not reactant_row_ids and synthesis.reactants:
        reactant_row_ids = [r.row_id for r in synthesis.reactants if r.row_id]
    if not reactant_row_ids:
        return synthesis

    self.update_reactant_row_values(
        synthesis_id=synthesis_id,
        row_id=reactant_row_ids[0],
        values=ReactantValues(
            mass=None,
            moles=None,
            eq=None,
            concentration=100,
        ),
    )

    self._send_patch(
        synthesis_id=synthesis_id,
        payload={
            "data": [
                {
                    "attribute": "hideReactionWorksheet",
                    "operation": "update",
                    "newValue": "false",
                }
            ]
        },
    )

    self._send_patch(
        synthesis_id=synthesis_id,
        payload={
            "data": [
                {
                    "attribute": "inventoryId",
                    "operation": "add",
                }
            ]
        },
    )
    return self.get_by_id(id=synthesis_id)