Python API#

orphan:

True

How the Python surface is organized — the generated symbol-by-symbol reference follows below; for the layered architecture, see How BattINFO is built.

Where to start#

Goal

Surface to use

Turn lab data into published, linked records

battinfo.workspace(...) — the one object for the whole journey

Describe a battery product (a datasheet as data)

The CellSpec record class + the publish shortcut

Author research-grade composition (materials, electrodes, electrolyte)

battinfo.authoring — see Tutorial 5

Load, query, save, or resolver-publish canonical records

battinfo.api helpers

Turn a folder of photos and CSVs into a linked submission

battinfo.ingest — one-command folder intake

Read or write another format (BPX, Battery Data Commons, battdat, protocol files)

battinfo.interop importers and exporters

Script validation, saving, and publishing from the shell

The battinfo CLI — see the CLI reference

If you are new here, start with the tutorials — six notebooks that walk the whole story end to end.

The curated top-level namespace (import battinfo) exposes the record classes, the workspace, publishing, and validation — everything else lives in its home module (battinfo.api, battinfo.authoring, battinfo.materials, …) and is documented there.

The workspace#

battinfo.workspace(root) is the data-first surface: convert raw cycler files, register cells, link tests and data, save validated records with stable IRIs, and publish — to the registry review queue and, with zenodo=True, to a citable DOI.

import battinfo

ws = battinfo.workspace(".")

ws.convert()                              # raw cycler files → tidy BDF tables
spec = ws.search("samsung inr21700 50e")[0]   # reuse the registry's identity
ws.add("cell", spec=spec, serial_numbers=["MYLAB-50E-001", "MYLAB-50E-002"])   # serials only your lab uses
ws.add("test", type="cycling", cell="MYLAB-50E-001", data="bdf/MYLAB-50E-001.bdf.csv")
ws.save()
ws.publish(note="My cycling campaign, 2026")

ws.quickstart() prints the full recipe, including login and funding attribution (ws.project(...), ws.contributor(...)). Tutorial 3 builds the chain step by step; Tutorial 6 runs it from a raw cycler export.

Drafts and templates: ws.template("cell-spec", ...) / ws.template("test-spec", ...) write skeleton draft files; ws.load(path) authors them into the session. ws.load(ws.search(...)[0]) references an existing registry record instead (reused, never re-published).

Record classes#

CellSpec, Cell, TestSpec, Test, and Dataset are the record types as pydantic classes, written by hand in bundle.py. You author records with them, and every save validates the record against the JSON Schema, which is the save-time contract. For a standalone cell-spec record, publish is the shortcut:

from battinfo import CellSpec, publish

cell_spec = CellSpec(
    manufacturer="Google",
    model="G20M7",
    format="pouch",
    chemistry="Li-ion",
)

local_result = publish(cell_spec, destination="local")
registry_result = publish(
    cell_spec,
    destination="registry",
    registry_base_url="https://registry.example.org",
    api_key="...",
    workspace_id="hello-world",
    publisher_id="demo-lab",
)
  • publish takes a CellSpec only. A Cell, TestSpec, Test or Dataset is saved with save_cell_instance, save_test_spec, save_test or save_dataset, which mint its IRI; the “Query and save” section below shows one.

  • destination="local" writes the canonical BattINFO record and returns its path in debug_paths.

  • destination="registry" also generates the submission package and submits it to battinfo-registry.

  • destination="battery-genome" additionally returns the expected Battery Genome page URL when platform_base_url is configured.

  • root= is the folder the record is built in, by default .battinfo/publish/<manufacturer>-<model> under the current folder. force=True deletes that folder first and builds it again. It deletes only a folder BattINFO created, which holds a .battinfo-generated file, or one built by an earlier version that holds only what BattINFO writes there, down to the file names in examples/, dist/ and its other record and report folders. For any other folder, such as one that also holds a file of yours (a JSON file included), for the .battinfo folder where battinfo.workspace() keeps a project’s records, and for the home or working folder, it raises a ValueError (UnsafeDeleteError) and deletes nothing. The error names what is in the way.

Publication package (record-classes path)#

When you hold the four core record objects in Python and want the local publication artifacts without a workspace, build the package directly:

from pathlib import Path

from battinfo import Cell, CellSpec, Dataset, Test, build_publication_package, load_publication_package

dataset_dir = Path("data/cr2032-run")
dataset_dir.mkdir(parents=True, exist_ok=True)
(dataset_dir / "capacity.csv").write_text("cycle,capacity_ah\n1,0.225\n")

cell_spec = CellSpec(manufacturer="Energizer", model="CR2032", format="coin", chemistry="li-mno2", rechargeable=False)
cell = Cell(cell_spec=cell_spec, serial_number="energizer-cr2032-202602-dtjrga")
test = Test(cell=cell, kind="capacity_check", protocol="constant current discharging", status="completed")
dataset = Dataset(path=str(dataset_dir), cell=cell, test=test, name="Energizer CR2032 dataset")

report = build_publication_package(
    cell_spec=cell_spec, cell_instance=cell, test=test, dataset=dataset,
)
bundle = load_publication_package(dataset_dir / "battinfo.publish.jsonld")

Notes:

  • battinfo.publish.jsonld is the foundational Schema.org JSON-LD artifact.

  • ro-crate-metadata.json and datacite-metadata.json are emitted alongside it.

  • battinfo.dcat.jsonld is available as an optional export.

  • load_publication_package reads a package back into the record objects. Units come back as they were written. Of the cell spec’s properties it reads the dimensions, mass, nominal capacity and nominal voltage, and it gives the chemistry as unknown.

Query and save (battinfo.api)#

Canonical-record helpers for scripted workflows:

from battinfo import query_cell_specs, save_cell_instance
from battinfo.api import template_cell_instance

rows = query_cell_specs(manufacturer="A123", chemistry="LFP", limit=5)

draft = template_cell_instance(cell_spec_id="https://w3id.org/battinfo/spec/3m6k-9t2p-7x4h-9nq8")
draft["cell_instance"]["serial_number"] = "LAB-001"

result = save_cell_instance(draft, source_root="examples", resolve_references=False)

Draft inputs omit canonical fields like id, short_id, and identifier — saving canonizes them and fills default provenance.

Index and resolver publishing (battinfo.api)#

from battinfo.api import build_index, index_stats, publish_record

publish_record(
    "examples/cell-instance/cell-3m6k-9t2p-7x4h-9nq8.json",
    target_root=".battinfo/resolver-site",
)

index = build_index(source_root="examples", out_path=".battinfo/index.json")
stats = index_stats(".battinfo/index.json")

Ingest-first intake (battinfo.ingest)#

One-command registration for a typed resource instance plus its evidence files (photos become photo datasets; CSVs become a Test plus a Dataset):

from battinfo.ingest import build_ingest_workspace, publish_ingest_workspace, write_ingest_manifest

The folder-local battinfo.ingest.json manifest carries stable ingest metadata; its shape is defined by the ingest manifest contract and validated against assets/schemas/ingest-manifest.schema.json. resource_type="cell-instance" is the currently implemented subject.

Other formats (battinfo.interop)#

Importers and exporters for formats other tools write: BPX parameter files (from_bpx_parameters, to_bpx, upgrade_bpx, check_bpx), Battery Data Commons records (import_bdc_record, to_bdc_record), battdat datasets (from_battdat), BattInfoConverter JSON-LD (import_converter_jsonld), Discovery spreadsheets (import_discovery_xlsx), the solid-state battery database (from_solid_state_db_row), and the aurora-unicycler, bmgen, PyBaMM and UCP protocol formats (import_aurora_unicycler, import_bmgen_jsonld, import_pybamm_experiment, import_ucp). An importer returns BattINFO records, or a result object that holds them with warnings naming what it could not read; nothing is saved or published until you do it.

A BPX file becomes one parameter-set record, and to_bpx rebuilds the file from it:

from battinfo.interop import from_bpx_parameters, to_bpx

bpx = {
    "Header": {"BPX": "1.1.0", "Title": "Example cell", "Model": "Partial"},
    "Parameterisation": {
        "Cell": {"Nominal cell capacity [A.h]": 5.0, "Electrode area [m2]": 0.1},
        "Negative electrode": {"Particle radius [m]": 5.86e-6, "Thickness [m]": 8.52e-5},
    },
}
result = from_bpx_parameters(bpx)
record = result.to_parameter_set(virtual_cell=True, materials={"negative": "graphite"})
print([part["part"] for part in record["parameter_set"]["parts"]])
print(to_bpx(record).bpx == bpx)  # True

Publish a BPX parameter file takes a file from disk to a published record, and the interop scorecard measures how completely BattInfoConverter and Discovery JSON-LD exports reach canonical records.

Advanced: the internal engine#

battinfo.workspace(...) and the record classes are facades over an internal authoring engine (battinfo._workspace.Workspace) that also powers submission-package export and release workflows. New code should not build on the engine directly — its surface is large, uncurated, and free to change; everything the tutorials and this page show goes through the stable facades. If you maintain older code that imports Workspace from battinfo, you will see a deprecation message pointing at the replacement for each call.

Guidance#

  • Prefer battinfo.workspace(...) for anything that ends in publishing.

  • Prefer opaque BattINFO IRIs under https://w3id.org/battinfo/.

  • For validation policy and machine-readable issue output, see the validation contract.

  • For submission-envelope internals, see the infrastructure contract.

Generated reference#

Everything below is generated from the source docstrings and field descriptions — it cannot drift from the code. The authoring workspace has its own page at Workspace authoring.

The record classes#

The five record classes (CellSpec, Cell, TestSpec, Test and Dataset) are the authoring input: construct them with the flat field names you know from the datasheet, then save each with its save_* function (save_cell_spec, save_cell_instance, save_test_spec, save_test or save_dataset). publish is a shortcut for a CellSpec only. ProvenanceInfo, documented with them, is the provenance block every record embeds. Every field is documented below; quantity keys and unit symbols are enumerated in Properties and units.

pydantic model battinfo.CellSpec#
Fields:
  • bibliography (dict[str, Any])

  • cell_configuration (battinfo.bundle.CellConfiguration | None)

  • cell_specification_id (str | None)

  • chemistry (str)

  • comment (list[str])

  • construction (dict[str, Any])

  • counter_electrode (battinfo.bundle.Electrode | None)

  • counter_electrode_spec_id (str | None)

  • country_of_origin (str | None)

  • datasheet_revision (str | None)

  • electrolyte (battinfo.bundle.Electrolyte | None)

  • electrolyte_spec_id (str | None)

  • format (str)

  • handle (str | None)

  • housing (battinfo.bundle.Housing | None)

  • housing_spec_id (str | None)

  • id (str | None)

  • iec_code (str | None)

  • kind (str)

  • manufacturer (str)

  • manufacturer_id (str | None)

  • model (str)

  • name (str | None)

  • negative_electrode (battinfo.bundle.Electrode | None)

  • negative_electrode_basis (str | None)

  • negative_electrode_spec_id (str | None)

  • positive_electrode (battinfo.bundle.Electrode | None)

  • positive_electrode_basis (str | None)

  • positive_electrode_spec_id (str | None)

  • product_type (battinfo.bundle.CellProductType | None)

  • properties (dict[str, Any])

  • rechargeable (bool | None)

  • reference_electrode (battinfo.bundle.Electrode | str | None)

  • reference_electrode_spec_id (str | None)

  • separator (battinfo.bundle.Separator | None)

  • separator_spec_id (str | None)

  • size_code (str | None)

  • source (battinfo.bundle.ProvenanceInfo)

  • specification_comment (list[str])

  • uid (str | None)

  • working_electrode (battinfo.bundle.Electrode | None)

  • working_electrode_spec_id (str | None)

  • year (int | None)

pydantic model battinfo.Cell#
Fields:
  • batch_id (str | None)

  • cell_spec (battinfo.bundle.CellSpec | None)

  • cell_spec_id (str | None)

  • comment (list[str])

  • conformance (battinfo.bundle.Conformance | None)

  • counter_electrode_id (str | None)

  • dataset_ids (list[str])

  • expires_at (int | str | None)

  • grade (str | None)

  • handle (str | None)

  • id (str | None)

  • kind (str)

  • manufactured_at (int | str | None)

  • measured (dict[str, Any])

  • name (str | None)

  • serial_number (str | None)

  • source (battinfo.bundle.ProvenanceInfo)

  • uid (str | None)

  • working_electrode_id (str | None)

pydantic model battinfo.TestSpec#

A test spec: a reusable test procedure, saved as a test-protocol record.

Give the steps as PyBaMM-style strings with experiment= (["Charge at C/3 until 4.2 V", "Discharge at C/10 until 2.5 V"], plus cycles=) or as structured method steps, and the test type with kind=. Each run of the procedure is a Test that points at it: Test(protocol=spec, ...) or ws.add("test", spec=spec, ...). Save it with battinfo.save_test_spec. A misspelled argument raises TypeError with a did-you-mean; the fields below say what each holds.

Fields:
  • artifacts (list[battinfo.bundle.Artifact])

  • comment (list[str])

  • conditions (dict[str, battinfo.testmethod.Quantity])

  • description (str | None)

  • handle (str | None)

  • id (str | None)

  • kind (str)

  • method (list[battinfo.testmethod.Step])

  • name (str | None)

  • protocol (battinfo.bundle.ProtocolInfo)

  • record (dict[str, Any])

  • safety (dict[str, Any])

  • source (battinfo.bundle.ProvenanceInfo)

  • test_type (battinfo.bundle.BatteryTestType)

  • uid (str | None)

  • version (str | None)

pydantic model battinfo.Test#

A test: one run of a procedure on one physical cell, saved as a test record.

Name the cell (cell= a Cell, or cell_id= its IRI), the test type (kind=) and, when there is one, the TestSpec it followed (protocol=). As-run details go in conditions, instrument, started_at and ended_at. The data the run produced is a Dataset that points back at the test. A misspelled argument raises TypeError with a did-you-mean; the fields below say what each holds.

Fields:
  • artifacts (list[battinfo.bundle.Artifact])

  • cell (battinfo.bundle.Cell | None)

  • cell_instance_id (str | None)

  • channel_id (str | None)

  • comment (list[str])

  • conditions (dict[str, Any])

  • conformance (battinfo.bundle.Conformance | None)

  • dataset_ids (list[str])

  • description (str | None)

  • ended_at (int | str | None)

  • equipment_id (str | None)

  • handle (str | None)

  • id (str | None)

  • instrument (str | None)

  • kind (str)

  • name (str | None)

  • protocol (battinfo.bundle.ProtocolInfo)

  • protocol_entity (battinfo.bundle.TestSpec | None)

  • protocol_id (str | None)

  • source (battinfo.bundle.ProvenanceInfo)

  • started_at (int | str | None)

  • status (str | None)

  • test_type (battinfo.bundle.BatteryTestType)

  • uid (str | None)

pydantic model battinfo.Dataset#

A dataset: one data artifact and where its files are, saved as a dataset record.

Point it at the cell and test the data is about (cell=, test= or their IRIs) and at the data: a local file (path) or files already archived elsewhere (access_url and distributions, each with a checksum). A collection of datasets is a Dataset with additional_type=["DatasetSeries"] and no cell, test or files; its members name it in series_id. Save it with battinfo.save_dataset. A misspelled argument raises TypeError with a did-you-mean; the fields below say what each holds.

Fields:
  • access_url (str | None)

  • additional_type (list[str])

  • cell (battinfo.bundle.Cell | None)

  • cell_instance_id (str | None)

  • checksum (battinfo.bundle.ChecksumInfo)

  • citations (list[dict[str, Any]])

  • comment (list[str])

  • conditions_of_access (str | None)

  • created_at (int | str | None)

  • creators (list[dict[str, Any]])

  • data_format (str | None)

  • dataset_path (str | None)

  • description (str | None)

  • distributions (list[dict[str, Any]])

  • download_url (str | None)

  • funders (list[dict[str, Any]])

  • handle (str | None)

  • id (str | None)

  • identifier (Any)

  • in_language (str | None)

  • included_in_data_catalog (str | dict[str, Any] | None)

  • is_accessible_for_free (bool | None)

  • is_based_on (list[str])

  • keywords (list[str])

  • kind (str)

  • license (str | None)

  • main_entity (list[dict[str, Any]])

  • measurement_methods (list[str])

  • measurement_techniques (list[str])

  • modified_at (int | str | None)

  • name (str | None)

  • published_at (int | str | None)

  • publisher (dict[str, Any] | None)

  • related_cell_ids (list[str])

  • related_test_ids (list[str])

  • same_as (list[str])

  • series_id (str | None)

  • source (battinfo.bundle.ProvenanceInfo)

  • spatial_coverage (str | None)

  • temporal_coverage (str | None)

  • test (battinfo.bundle.Test | None)

  • test_id (str | None)

  • uid (str | None)

  • variable_measured (list[dict[str, Any]])

  • version (str | None)

pydantic model battinfo.ProvenanceInfo#
Fields:
  • battinfo_version (str | None)

  • citation (str | None)

  • comment (str | None)

  • curated_by (str | None)

  • file (str | None)

  • file_hash (str | None)

  • name (str | None)

  • retrieved_at (int | str | None)

  • type (str | None)

  • url (str | None)

  • workflow_version (str | None)

Publish and save#

battinfo.publish(obj: Any = None, destination: str | None = None, *, root: str | Path | None = None, force: bool = False, validation_policy: str | None = None, registry_base_url: str | None = None, api_key: str | None = None, api_key_header: str | None = None, platform_base_url: str | None = None, workspace_id: str | None = None, publisher_id: str | None = None, source_version: str | None = None, base: str | None = None, **legacy: Any) → PublishResult | dict[str, Any]#

Save one cell spec, and submit it when the destination is a registry.

publish(spec, destination="local") saves the record in a folder; "registry", "battery-genome", "staging" and "production" also submit it.

publish() takes a CellSpec only. The other record models are saved with their own function, which also mints the IRI: save_cell_instance for a Cell, save_test_spec for a TestSpec, save_test for a Test and save_dataset for a Dataset. To save and publish linked cells, tests and datasets, use battinfo.workspace().

Parameters:
  • obj – The CellSpec to publish. Anything else raises TypeError, and for another record model the message names the function that saves it.

  • destination – "local" (the default) saves the record only; debug_paths gives its path. "registry" also submits it to the registry at registry_base_url. "battery-genome", "staging" and "production" submit it too and return the record’s Battery Genome page URL, so they also need platform_base_url. Values not passed are read from environment variables such as BATTINFO_REGISTRY_URL and BATTINFO_API_KEY (BATTINFO_STAGING_* and BATTINFO_PRODUCTION_* for those two destinations).

  • root – The workspace folder. Defaults to .battinfo/publish/<manufacturer>-<model> in the current folder.

  • force – Delete root first and build it again. Refused (UnsafeDeleteError, a ValueError) for a folder BattINFO did not create, for the .battinfo folder where battinfo.workspace() keeps a project’s records, and for the home or working folder.

  • validation_policy – Validation policy for the saved record. Defaults to "strict".

  • registry_base_url – Override the destination’s configured values.

  • api_key – Override the destination’s configured values.

  • api_key_header – Override the destination’s configured values.

  • platform_base_url – Override the destination’s configured values.

  • workspace_id – Override the destination’s configured values.

  • publisher_id – Override the destination’s configured values.

  • source_version – Override the destination’s configured values.

  • base – The base a cell spec without an IRI mints under. Left out, it is https://w3id.org/battinfo/id/1001/; pass "https://w3id.org/battinfo/" for the old form. A spec already saved in root keeps the IRI it has.

Returns:

The record’s IRI and, after a submission, the registry’s answer and the URLs of the registry entry and the Battery Genome page.

Return type:

PublishResult

Notes

The deprecated form publish(cell_spec=..., cell_instance=..., test=..., dataset=...) still builds a dataset publication package, with a DeprecationWarning, and accepts only the keywords of battinfo.publish_publication_package(). It will be removed in 0.9.0.

battinfo.save_cell_spec(draft: CellSpec | dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save a cell-spec from either draft payload or canonical record.

Drafts without id/uid mint deterministically from the natural key (manufacturer :: model :: format :: chemistry :: size_code) — an identical re-save is a no-op; a revised datasheet updates the record in place under mode="upsert". See save_record for the full minting policy.

battinfo.save_cell_instance(draft: Cell | dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save a cell-instance from either draft payload or canonical record.

Drafts without id/uid mint deterministically from the natural key (cell_spec_id :: serial_number :: batch_id :: label), where the label is the serial when there is one, otherwise the name, otherwise the batch, so renaming a cell with a serial keeps its IRI. Instances with no serial, batch, or name mint randomly so distinct anonymous cells never dedup. See save_record for the full minting policy.

battinfo.save_test_spec(draft: TestSpec | dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save a test protocol from either draft payload or canonical record.

battinfo.save_test(draft: Test | dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save a test from either draft payload or canonical record.

battinfo.save_dataset(draft: Dataset | dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save a dataset record into a records folder and return what was written.

draft is a battinfo.Dataset, a dict of its fields, a canonical record ({"schema_version": ..., "dataset": {...}}) or the path of a JSON file holding either. A draft without an id gets an IRI minted from its identity (see save_record()), so saving the same dataset again lands on the same IRI.

Parameters:
  • source_root – The records folder to save into, for a workspace <root>/.battinfo/records. References are resolved against it.

  • mode – "create_only" (default) refuses a record whose IRI is already saved there; "upsert" updates it in place, and an unchanged re-save writes nothing and reports content_changed: False.

  • duplicate_policy – What "create_only" does with a record that already exists: "error" (default) raises ValueError; "return_existing" returns the saved record’s id and path with status: "exists".

  • resolve_references – True (default) requires the cells and tests in the dataset’s about list to be saved under source_root, and raises ValueError naming each one that is not. False skips that check, for a dataset saved before the records it points at. series_id is not checked here.

  • validate – Validate the record before writing it, under the named policy ("default", "strict", "publisher" or "ingest"); an error raises ValueError and nothing is written.

  • validation_policy – Validate the record before writing it, under the named policy ("default", "strict", "publisher" or "ingest"); an error raises ValueError and nothing is written.

  • publish – Also write resolver artifacts for the record under publish_root.

  • publish_root – Also write resolver artifacts for the record under publish_root.

  • build_jsonld – Also write resolver artifacts for the record under publish_root.

  • build_html – Also write resolver artifacts for the record under publish_root.

  • dry_run – Report what would be written without writing.

  • stamp – A function applied to the record before it is compared and written (the workspace uses it for funding and contributor stamps).

  • status (and published. By)

  • id

  • entity_type

  • path

  • mode

  • status

  • "created" (-) – False; it only describes a record that was already there.

  • record) (- "updated" (mode="upsert" over a saved) – content_changed is True when the record differs from the saved one and False when it does not, in which case nothing is rewritten unless the saved copy holds text in a quantity’s value, which is read as value_text for the comparison and then rewritten, with a warning that names each text value moved (a numeric string is read as the number without one). The comparison ignores the record’s own id, uid and the created_at, modified_at, retrieved_at, generated_at and imported_at timestamps, but not published_at: a Dataset saved without one gets the save time, so its re-save compares as changed.

  • with (- "exists" (mode="create_only") – duplicate_policy="return_existing"): nothing was written; path is the saved record and there is no content_changed key.

  • "dry-run" (-) – be for the real save.

  • publish=True (With)

  • also (a "created" or "updated" result)

  • otherwise (carries publish_result and published is True;)

  • False. (published is)

battinfo.save_record(record: dict[str, Any] | str | Path, *, source_root: str | Path = PosixPath('examples'), mode: str = 'create_only', duplicate_policy: str = 'error', resolve_references: bool = True, publish: bool = False, publish_root: str | Path = '.battinfo/resolver-site', build_jsonld: bool = True, build_html: bool = True, validate: bool = True, validation_policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error'), dry_run: bool = False, stamp: Callable[[dict[str, Any]], None] | None = None, base: str = 'https://w3id.org/battinfo/id/1001/') → dict[str, Any]#

Save one canonical BattINFO resource into local source storage and optional resolver artifacts.

Minting policy: records that arrive without an id/uid are minted deterministically from their natural identity key (see _identity_minted_uid), so re-running an identical ingest lands on the existing records — use mode="upsert" (no-op re-save reports content_changed: False) or duplicate_policy="return_existing" for idempotent pipelines. Records with no distinguishing identity fall back to a random uid and never silently dedup. A canonical record saved without an id (a filled-in template) is minted the same way (_with_minted_id).

A minted IRI takes base: https://w3id.org/battinfo/id/1001/ unless another is given, such as "https://w3id.org/battinfo/" for the old form. A record that source_root already holds keeps the IRI it is saved under, in either form. An /id/1001/ IRI that the folder holds in the old form, the record’s own or one the record names, is saved in the old form, such as when an importer runs again into a folder written before the switch.

battinfo.bulk_save_session(source_root: Path | str) → Iterator[RecordLocationCache]#

Speed up a batch of save_* calls against one source root.

Loads the id→path map once (per entity type, lazily) instead of rescanning the source root on every save:

with battinfo.bulk_save_session("examples"):
    for draft in drafts:
        battinfo.save_cell_instance(draft, source_root="examples")

Nested sessions for the same root reuse the outer cache. The session assumes single-writer access to source_root for its duration.

Query and validate#

battinfo.query_cell_specs(*, id: str | None = None, manufacturer: str | None = None, chemistry: str | None = None, format: str | None = None, model_name_contains: str | None = None, nominal_capacity_min: float | None = None, nominal_capacity_max: float | None = None, nominal_voltage_min: float | None = None, nominal_voltage_max: float | None = None, spec_filters: Mapping[str, tuple[float | None, float | None]] | None = None, source_root: str | Path | None = None, cell_specs_dir: str | Path | None = None, include_packaged_examples: bool = False, limit: int = 50, offset: int = 0) → list[dict[str, Any]]#

Query cell types using practical metadata/property filters.

Searches YOUR records under source_root (default: ./examples, the same root save_cell_spec writes to). BattINFO’s bundled demo fleet is only searched with include_packaged_examples=True; those hits carry origin="packaged-example". cell_specs_dir= is a deprecated alias pointing directly at one record directory.

battinfo.validate_record_report(doc: dict[str, Any], *, source_root: str | Path | None = None, policy: ValidationPolicy | str = ValidationPolicy(name='default', schema='error', references='error', semantic='warn', publication='error')) → ValidationReport#

Validate one canonical record and return every issue found.

doc is a canonical record as saved on disk ({"schema_version": ..., "cell_spec": {...}} and so on, for any record type). The checks are the record’s JSON Schema, a refusal of the all-zero template IRI, the semantic rules (controlled values, units, plausible ranges) and, for cell specs, the SHACL shapes. With source_root (a records folder) every IRI the record points at must also resolve to a saved record of the right type.

policy is a ValidationPolicy or one of the names "default", "strict", "publisher" and "ingest"; it decides which checks run and whether their findings are errors or warnings. The function never raises over a finding: read report.ok, report.errors and report.warnings, or report.to_dict(). It raises ValueError for an unknown policy name or a document that is not a record of any known type.

battinfo.record_to_jsonld(record: dict, record_type: str, *, context: str = 'url') → dict#

Transform a BattINFO plain-JSON record to a JSON-LD document.

Parameters:
  • record – The plain-JSON record dict (as loaded from .battinfo/records/).

  • record_type – One of "cell-spec", "cell-instance", "test", "test-protocol", "dataset", "material-spec"/"material", or a component spec/instance type.

  • context – "url" (default) references the hosted context (_CONTEXT_URL) with a one-line @context — the hosted document carries the full vocabulary, so the record a reader sees stays simple. Resolved offline by the bundled copy. "inline" embeds the full records @context so the document is self-contained — use it for archival exports that must validate with no network (Zenodo packages inline). Both expand to the same graph. Records that emit a domain-battery (EMMO) context — materials and components — are unaffected by "url".

Returns:

A JSON-LD document with @context, @id, @type, and semantically typed properties using EMMO/schema.org IRIs.

Return type:

dict

Raises:
  • ValueError – For an unknown record_type, or when the document would carry a bare @type that its own @context does not define as a class (jsonld.type_term_unknown), for every record type.

  • Example:: – import json from battinfo.jsonld import record_to_jsonld raw = json.loads(Path(“cell-spec-xyz.json”).read_text()) ld = record_to_jsonld(raw, “cell-spec”) print(json.dumps(ld, indent=2))

Record titles and handles#

battinfo.naming builds a record’s title (its name) and its handle, the short slug a registry workspace shows as <workspace>/<handle>, from structured parts. The how-to is Name your records: titles and handles.

battinfo.naming.handle_for(kind: str, *, group: str | None = None, subject: str | None = None, variant: str | None = None, sample: str | int | None = None, method: str | None = None, manufacturer: object = None, model: str | None = None) → str#

Build a record handle from structured parts.

Parameters:
  • kind – The record type: an entity type ("material"), a record key ("cell_instance"), a kind word ("material-lot"), or "collection" for a dataset collection.

  • group – The handle of the collection (or other grouping source) the record belongs to, e.g. "flores-ocv". It may itself contain /. A collection’s handle is the bare group.

  • subject – The material kind, as a vocabulary key or alias ("graphite", "Si-Gr"). Vocabulary entries become their key with _ replaced by -; other text is slugged as given.

  • variant – The source’s own design or batch label ("Gr-AQ-1"). A leading prefix naming the subject’s material is dropped, so Gr-AQ-1 becomes aq-1.

  • sample – The source’s sample id for a physical item or its results.

  • method – The test method for tests and datasets ("gitt", "p-OCV hold").

  • manufacturer – For a product (a spec kind in PRODUCT_KINDS), its manufacturer or supplier: a name, the organization reference the record carries, or the organization record. The group is the organization’s handle when it has one, else its slugged name.

  • model – For a product, its model or product name. A leading repeat of the manufacturer’s name is dropped.

A product’s handle reads <manufacturer>/<model>-<kind>, e.g. samsung-sdi/inr18650-35e-cell-spec. An organization’s handle, like a collection’s, is its bare group (see organization_handle()).

Every part is normalised the same way: lowercased, transliterated to ASCII, and runs of anything other than letters and digits collapsed to one hyphen. The kind word always comes last.

Raises:

ValueError – when a part has no letters or digits, the kind is unknown, a collection is given anything but a group, or the handle would be longer than HANDLE_MAX_LENGTH.

>>> handle_for("collection", group="flores-ocv")
'flores-ocv'
>>> handle_for("cell-spec", manufacturer="Samsung SDI", model="INR18650-35E")
'samsung-sdi/inr18650-35e-cell-spec'
>>> handle_for("test", group="flores-ocv", subject="graphite",
...            variant="Gr-AQ-1", sample="063b77", method="GITT")
'flores-ocv/graphite-aq-1-063b77-gitt-test'
battinfo.naming.title_for(kind: str, *, subject: str | None = None, variant: str | None = None, sample: str | int | None = None, method: str | None = None, tested: str | None = 'cell', label: str | None = None, manufacturer: object = None, model: str | None = None, source: str | None = None) → str#

Build a readable record title (the name) from structured parts.

The title mirrors the handle in plain English:

[<Source>] <Subject> [<VARIANT>] <kind words> [<sample>] [<METHOD>]

A research record’s title should lead with its source (source="Flores 2026"): a registry holds many graphite material specs, and only the source tells them apart in a list or a search result. Products carry their manufacturer instead (see manufacturer=).

Tests and datasets name the item they are about first, then the method, then their own kind (Graphite AQ-1 cell 063b77 GITT test), and a test spec puts its method before its kind (GITT test spec).

Parameters:
  • kind – The record type, as for handle_for().

  • subject – The material kind. Vocabulary entries use a short label (Graphite, Silicon-graphite, Silicon, LNMO, LFP, NMC111, NMC532); other text is used as given.

  • variant – The source’s design or batch label. The material prefix is dropped as in handle_for() and the rest is upper case (Gr-AQ-1 -> AQ-1).

  • sample – The source’s sample id, kept as written.

  • method – The test method. Text the source already capitalises is kept as written (p-OCV hold); an all-lowercase slug is upper-cased (gitt -> GITT).

  • tested – For tests and datasets, the kind of item tested (default "cell"). Pass None to leave it out. Other kinds ignore it.

  • label – A collection’s descriptive name; " collection" is appended unless it already ends that way. Only collections take it.

  • source – The source the record comes from, as author and year ("Flores 2026"). It leads the title, and a subject that is a common word then loses its capital (Flores 2026 graphite material spec) while an abbreviation keeps it (Flores 2026 LNMO NMP-1 electrode spec). Not used for products or collections; a collection’s label names its source itself.

  • manufacturer – For a product (PRODUCT_KINDS), its manufacturer: a name, a reference or an organization record with a name.

  • model – For a product, its model. The title is the manufacturer’s name and the model (Samsung SDI INR18650-35E), with no kind words: a product’s name is what people search for and cite, and the record type is shown beside the title.

Raises:

ValueError – when the kind is unknown, a collection has no label, or a non-collection is given a label.

>>> title_for("electrode", subject="graphite", variant="Gr-AQ-1", sample="063b77")
'Graphite AQ-1 electrode 063b77'
>>> title_for("test-spec", method="gitt")
'GITT test spec'
>>> title_for("test-spec", method="p-OCV hold")
'p-OCV hold test spec'
>>> title_for("cell-spec", manufacturer="Samsung SDI", model="Samsung SDI INR18650-35E")
'Samsung SDI INR18650-35E'
>>> title_for("electrode", source="Flores 2026", subject="graphite", variant="Gr-AQ-1", sample="063b77")
'Flores 2026 graphite AQ-1 electrode 063b77'
battinfo.naming.is_valid_handle(value: object) → bool#

True when value follows the handle grammar and length limit.

>>> is_valid_handle("flores-ocv/graphite-material-spec")
True
>>> is_valid_handle("Flores OCV")
False
battinfo.naming.kind_word(kind: str) → str | None#

The word that ends a handle for kind, or None for a collection.

kind is an entity type ("material"), a record key ("cell_instance"), or a kind word ("material-lot", "test-spec").

>>> kind_word("material")
'material-lot'
>>> kind_word("test_spec")
'test-spec'

The workspace object#

class battinfo.AuthoringWorkspace(root: str | Path = '.', records_repo: str | Path | None = None, registry_url: str | None = None, *, base: str | None = None)#

The blessed authoring surface: a simplified workspace for BattINFO records.

Create one with battinfo.workspace() and call quickstart() for a copy-pasteable end-to-end example. Wraps the lower-level object-graph engine battinfo.Workspace (defined in battinfo._workspace) with a concise API designed for interactive / notebook use.

Every method is documented in Workspace authoring; run ws.commands() for the live cheat sheet. (The internal object-graph engine in battinfo._workspace is an implementation detail — the deprecated top-level Workspace name points you back here.)