Protocols

Author, save, and convert UCP protocols: write a protocol by hand, parse a vendor cycler file, validate it, save it to a project so simulations and planned measurements can reference it, and export it to a cycler’s native format.

For usage examples and guides, see Managing protocols on docs.ionworks.com.

Protocol client for authoring, saving, and converting UCP protocols.

Provides ProtocolClient for the whole protocol lifecycle: writing a protocol (by hand or by parsing a vendor file), validating it, saving it to a project so simulations and planned measurements can reference it by id, and converting it back out to a vendor-native protocol file (Maccor, Arbin, Neware, BioLogic BT-Test, Novonix).

Saved protocols are stored as experiment_template rows server-side; the SDK calls them protocols throughout.

ionworks.api.protocol.ConversionTarget

"arbin" is the .sdu dialect (MITS Pro <=7); "arbin_sdx" is .sdx (MITS Pro 8+). Both are first-class – “arbin” is not deprecated.

alias of Literal[‘maccor’, ‘arbin’, ‘arbin_sdx’, ‘neware’, ‘biologic_bttest’, ‘novonix’]

ionworks.api.protocol.INCLUDABLE_FIELDS = ('protocol_config', 'parameters_schema', 'time_series_spec', 'metrics_spec', 'plot_options', 'source_protocol')

Heavy columns omitted from list responses unless named in include.

class ionworks.api.protocol.ConvertResult(target: str, primary_filename: str, primary_bytes: bytes, media_type: str, assets: list[tuple[str, bytes]]=<factory>)[source]

Bases: object

Result of converting a UCP to a vendor-native protocol file.

target: str

The vendor target the protocol was converted for.

primary_filename: str

Suggested filename for the primary artifact (with extension).

primary_bytes: bytes

Raw bytes of the primary artifact.

media_type: str

Media type of the primary artifact.

assets: list[tuple[str, bytes]]

Side files (e.g. Maccor MWF drive-cycle assets), each as (filename, bytes).

text(encoding: str = 'utf-8') → str[source]

Decode the primary artifact as text.

Parameters:

encoding (str, optional) – Encoding to decode with. Defaults to utf-8.

Returns:

The decoded primary artifact.

Return type:

str

save(directory: str | Path) → list[Path][source]

Write the primary artifact and any assets to directory.

Parameters:

directory (str or Path) – Target directory. Created if it does not exist.

Returns:

Paths written, primary first followed by assets.

Return type:

list[Path]

class ionworks.api.protocol.ProtocolClient(client: Any)[source]

Bases: object

Client for authoring, saving, and converting protocols.

Write UCP or parse_file() a vendor file; check it with validate() and find_input_references(); store it with create() or create_or_get() and pass its id to a simulation or planned measurement; convert() exports a vendor-native file. Saved protocols are project-scoped: project_id defaults to the client’s (IONWORKS_PROJECT_ID), and with neither, methods raise ValueError.

__init__(client: Any) → None[source]

Initialize the ProtocolClient.

Parameters:

client (Any) – The HTTP client instance for making API requests.

list(project_id: str | None = None, limit: int | None = None, offset: int | None = None, *, include: list[str] | None = None, protocol_group_id: str | None = None, name: str | None = None, name_exact: str | None = None, created_by_email: str | None = None, created_after: str | None = None, created_before: str | None = None, updated_after: str | None = None, updated_before: str | None = None, order_by: str | None = None, order: str | None = None) → PaginatedList[Protocol][source]

List a project’s saved protocols.

The response is lightweight by default: the protocol body and other large columns are omitted and come back as None. Name them in include to fetch them, or call get() for one full protocol.

Filtering, ordering, and pagination are all applied by the database, so total reflects every protocol matching the filters rather than the size of the page returned.

Parameters:
  • project_id (str | None, optional) – Project whose protocols to list. Defaults to the project_id set on the Ionworks client.

  • limit (int | None, optional) – Page size (1-100). When omitted the full list is returned.

  • offset (int | None, optional) – Number of records to skip before the page starts.

  • include (list[str] | None, optional) – Heavy columns to include. Any of INCLUDABLE_FIELDS; unknown names are ignored by the API.

  • protocol_group_id (str | None, optional) – Only protocols assigned to this group. Omit to include all groups.

  • name (str | None, optional) – Case-insensitive substring match on the protocol name.

  • name_exact (str | None, optional) – Exact match on the protocol name. Takes precedence over name.

  • created_by_email (str | None, optional) – Case-insensitive substring match on the creator’s email.

  • created_after (str | None, optional) – ISO datetime bounds on when the protocol was saved.

  • created_before (str | None, optional) – ISO datetime bounds on when the protocol was saved.

  • updated_after (str | None, optional) – ISO datetime bounds on when the protocol was last changed.

  • updated_before (str | None, optional) – ISO datetime bounds on when the protocol was last changed.

  • order_by (str | None, optional) – Column to sort by: name, created_at, or updated_at.

  • order (str | None, optional) – Sort direction, "asc" or "desc".

Returns:

The matching protocols, with .count and .total.

Return type:

PaginatedList[Protocol]

get(protocol_id: str) → Protocol[source]

Get a saved protocol by id, including its full body.

Parameters:

protocol_id (str) – Id of the protocol to retrieve.

Returns:

The protocol, with protocol_config and the other heavy columns populated.

Return type:

Protocol

find_by_name(name: str, project_id: str | None = None) → Protocol | None[source]

Find a saved protocol by exact name within a project.

Parameters:
  • name (str) – Exact protocol name to match.

  • project_id (str | None, optional) – Project to search. Defaults to the project_id set on the Ionworks client.

Returns:

The matching protocol (lightweight — call get() for the body), or None when the project has no protocol by that name.

Return type:

Protocol | None

Raises:

ValueError – If more than one protocol in the project has this name. Names are not unique — protocols are deduplicated on their body, so two different protocols may share one — and picking arbitrarily between them would quietly simulate the wrong one. List them with list(name_exact=...) and select by id.

human_readable(protocol_id: str) → str[source]

Render a saved protocol as human-readable text.

Parameters:

protocol_id (str) – Id of the protocol.

Returns:

The protocol described in prose, one line per step.

Return type:

str

source_protocol(protocol_id: str) → str[source]

Return the original protocol text a saved protocol was created from.

Parameters:

protocol_id (str) – Id of the protocol.

Returns:

The source text as originally entered or parsed.

Return type:

str

Raises:

IonworksError – 404 when the protocol has no recorded source text (it was created directly from a UCP dict rather than from source).

create(name: str, protocol: str | dict, project_id: str | None = None, *, description: str | None = None, parameters_schema: dict[str, Any] | None = None, source_protocol: str | None = None, force_create: bool = False, protocol_group_id: str | None = None) → Protocol[source]

Save a protocol to a project.

By default this is content-addressed: saving a protocol whose body already exists in the project returns the existing row rather than a duplicate, so re-running a script is safe. Pass force_create=True to always write a new row — useful for keeping two differently-named copies of the same protocol.

Parameters:
  • name (str) – Name for the protocol.

  • protocol (str or dict) – The protocol as UCP YAML text or an already-parsed dict. A string is sent as source_protocol too unless one is given explicitly, so the original text is preserved.

  • project_id (str | None, optional) – Project to save into. Defaults to the project_id set on the Ionworks client.

  • description (str | None, optional) – Free-text description.

  • parameters_schema (dict | None, optional) – Schema of the parameters the protocol leaves open. Defaults to {} (no open parameters).

  • source_protocol (str | None, optional) – Original protocol text to record. Defaults to protocol when it is a string.

  • protocol_group_id (str | None, optional) – Group in the same project to assign this protocol to. When content already exists, supplying a group also assigns the existing row.

  • force_create (bool, optional) – Write a new row even when an identical protocol already exists. Defaults to False.

Returns:

The saved protocol.

Return type:

Protocol

Raises:

IonworksError – 409 when force_create is set and an identical protocol already exists — the content-hash uniqueness rule cannot be bypassed.

create_or_get(name: str, protocol: str | dict, project_id: str | None = None, **kwargs: Any) → Protocol[source]

Save a protocol, returning the existing one if it is already saved.

Thin alias for create() with force_create=False, named to match the create-or-get helpers on the other sub-clients. Matching is on protocol content, not on name.

Parameters:
  • name (str) – Name for the protocol, used only when creating.

  • protocol (str or dict) – The protocol as UCP YAML text or a parsed dict.

  • project_id (str | None, optional) – Project to save into. Defaults to the client’s project_id.

  • **kwargs (Any) – Passed through to create().

Returns:

The newly saved protocol, or the existing identical one.

Return type:

Protocol

update(protocol_id: str, *, name: str | None = None, description: str | None = None, protocol_group_id: str | None | Unset = Unset.TOKEN) → Protocol[source]

Rename, describe, or group a saved protocol.

name, description, and group membership are editable. A protocol’s body is immutable — saved simulations reference it, so changing it in place would silently rewrite what they ran. Save a new protocol instead.

Parameters:
  • protocol_id (str) – Id of the protocol to update.

  • name (str | None, optional) – New name. Left unchanged when omitted.

  • description (str | None, optional) – New description. Left unchanged when omitted.

  • protocol_group_id (str | None | Unset, optional) – Omit to preserve membership; pass a group id to assign, or None to remove the protocol from its group.

Returns:

The updated protocol.

Return type:

Protocol

delete(protocol_id: str) → None[source]

Delete a saved protocol.

Parameters:

protocol_id (str) – Id of the protocol to delete.

parse_file(file: str | Path | IO[bytes]) → ParsedProtocol[source]

Parse a vendor protocol file into UCP.

Accepts a cycler’s own protocol file (Maccor, Arbin, Neware, Novonix, BioLogic, …) and returns the equivalent UCP. Nothing is saved — pass the result’s ucp to create() to store it.

Parameters:

file (str, Path, or file object) – Path to the protocol file, or an already-open binary file object.

Returns:

The UCP text plus a human-readable rendering and the names of any drive cycles or subroutines the file referenced but did not carry.

Return type:

ParsedProtocol

validate(protocol: str) → dict[str, Any][source]

Validate a UCP protocol string.

Parameters:

protocol (str) – The protocol YAML string to validate.

Returns:

Validation result with valid (bool) and optionally error (str) keys.

Return type:

dict[str, Any]

find_input_references(protocol: str) → list[str][source]

Find input references (external parameter placeholders) in a protocol.

Parameters:

protocol (str) – The protocol YAML string to inspect.

Returns:

List of input reference names found in the protocol.

Return type:

list[str]

convert(protocol: str | dict, target: Literal['maccor', 'arbin', 'arbin_sdx', 'neware', 'biologic_bttest', 'novonix'], drive_cycles: dict[str, Any] | None = None, filename_stem: str = 'protocol', nominal_capacity_ah: float | None = None, verify: bool = False) → ConvertResult[source]

Convert a UCP to a vendor-native protocol file.

Returns the artifact as raw bytes. Use ConvertResult.text() to decode as a string, or ConvertResult.save() to write to disk.

Parameters:
  • protocol (str or dict) – UCP as a YAML string or dict.

  • target (str) – One of maccor, arbin, neware, biologic_bttest, novonix.

  • drive_cycles (dict, optional) – Mapping of drive cycle name → samples, required when the protocol references DriveCycle steps.

  • filename_stem (str, optional) – Stem for the returned primary filename. Defaults to protocol.

  • nominal_capacity_ah (float, optional) – Rated cell capacity in amp-hours. Required for neware and biologic_bttest when the protocol uses C-rate steps or cutoffs: both export them as absolute currents, so the rate cannot be resolved without it. Other targets express C-rate natively and ignore it.

  • verify (bool, optional) – Ask the server to round-trip the emitted file back through its parser and reject the conversion if the protocol no longer means the same thing – a dropped loop count, goto, or safety bound. Defaults to False, matching the server: the checker still reports several benign artifacts of the Maccor, Arbin and Neware writers as differences, so enabling it can refuse a valid conversion to those targets. Reliable today for biologic_bttest and novonix.

Returns:

Holds the primary artifact bytes plus any side assets.

Return type:

ConvertResult

Protocol groups

Use client.protocol_group to create, list, rename, and delete project-scoped families. Assign protocols on create/update and filter their list by group:

group = client.protocol_group.create("Schedule family")
protocol = client.protocol.create(
    "Simulation r1", protocol_yaml, protocol_group_id=group.id
)
members = client.protocol.list(protocol_group_id=group.id)
client.protocol.update(protocol.id, protocol_group_id=None)  # ungroup

Omitting protocol_group_id on update preserves membership. Deleting a group ungroups its protocols without deleting them. Inline simulation submissions do not inherit membership automatically; assign the saved protocol after the run.

Manage project-scoped families of related protocols.

class ionworks.api.protocol_group.ProtocolGroupClient(client: Any)[source]

Bases: object

Manage groups; assign members with client.protocol.create/update.

Groups belong to one project and have unique names within that project. They organize protocols without changing their immutable bodies or ids.

list(project_id: str | None = None, limit: int | None = None, offset: int | None = None, *, name: str | None = None, created_by_email: str | None = None, created_after: str | None = None, created_before: str | None = None, updated_after: str | None = None, updated_before: str | None = None, order_by: str | None = None, order: str | None = None) → PaginatedList[ProtocolGroup][source]

List groups with member counts, pagination, and server-side filters.

project_id defaults to the client’s project. name and created_by_email are case-insensitive substring filters. Date bounds are ISO datetimes; order by name, created_at, or updated_at.

get(group_id: str) → ProtocolGroup[source]

Fetch a group by id.

create(name: str, project_id: str | None = None, *, description: str | None = None) → ProtocolGroup[source]

Create a group; an existing name in this project raises a conflict.

Search existing groups before creating a family for debugging variants. project_id defaults to the client’s project.

update(group_id: str, *, name: str | None = None, description: str | None | Unset = Unset.TOKEN) → ProtocolGroup[source]

Rename or describe a group without changing membership.

Omit description to preserve it; pass None to clear it.

delete(group_id: str) → None[source]

Delete the group, ungrouping its protocols without deleting them.