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 |
|
Describe a battery product (a datasheet as data) |
The |
Author research-grade composition (materials, electrodes, electrolyte) |
|
Load, query, save, or resolver-publish canonical records |
|
Turn a folder of photos and CSVs into a linked submission |
|
Read or write another format (BPX, Battery Data Commons, battdat, protocol files) |
|
Script validation, saving, and publishing from the shell |
The |
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",
)
publishtakes aCellSpeconly. ACell,TestSpec,TestorDatasetis saved withsave_cell_instance,save_test_spec,save_testorsave_dataset, which mint its IRI; the “Query and save” section below shows one.destination="local"writes the canonical BattINFO record and returns its path indebug_paths.destination="registry"also generates the submission package and submits it tobattinfo-registry.destination="battery-genome"additionally returns the expected Battery Genome page URL whenplatform_base_urlis configured.root=is the folder the record is built in, by default.battinfo/publish/<manufacturer>-<model>under the current folder.force=Truedeletes that folder first and builds it again. It deletes only a folder BattINFO created, which holds a.battinfo-generatedfile, or one built by an earlier version that holds only what BattINFO writes there, down to the file names inexamples/,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.battinfofolder wherebattinfo.workspace()keeps a project’s records, and for the home or working folder, it raises aValueError(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.jsonldis the foundational Schema.org JSON-LD artifact.ro-crate-metadata.jsonanddatacite-metadata.jsonare emitted alongside it.battinfo.dcat.jsonldis available as an optional export.load_publication_packagereads 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 asunknown.
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-protocolrecord.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"], pluscycles=) or as structuredmethodsteps, and the test type withkind=. Each run of the procedure is aTestthat points at it:Test(protocol=spec, ...)orws.add("test", spec=spec, ...). Save it withbattinfo.save_test_spec. A misspelled argument raisesTypeErrorwith 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
testrecord.Name the cell (
cell=aCell, orcell_id=its IRI), the test type (kind=) and, when there is one, theTestSpecit followed (protocol=). As-run details go inconditions,instrument,started_atandended_at. The data the run produced is aDatasetthat points back at the test. A misspelled argument raisesTypeErrorwith 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
datasetrecord.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_urlanddistributions, each with a checksum). A collection of datasets is aDatasetwithadditional_type=["DatasetSeries"]and no cell, test or files; its members name it inseries_id. Save it withbattinfo.save_dataset. A misspelled argument raisesTypeErrorwith 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 aCellSpeconly. The other record models are saved with their own function, which also mints the IRI:save_cell_instancefor aCell,save_test_specfor aTestSpec,save_testfor aTestandsave_datasetfor aDataset. To save and publish linked cells, tests and datasets, usebattinfo.workspace().- Parameters:
obj – The
CellSpecto publish. Anything else raisesTypeError, and for another record model the message names the function that saves it.destination –
"local"(the default) saves the record only;debug_pathsgives its path."registry"also submits it to the registry atregistry_base_url."battery-genome","staging"and"production"submit it too and return the record’s Battery Genome page URL, so they also needplatform_base_url. Values not passed are read from environment variables such asBATTINFO_REGISTRY_URLandBATTINFO_API_KEY(BATTINFO_STAGING_*andBATTINFO_PRODUCTION_*for those two destinations).root – The workspace folder. Defaults to
.battinfo/publish/<manufacturer>-<model>in the current folder.force – Delete
rootfirst and build it again. Refused (UnsafeDeleteError, aValueError) for a folder BattINFO did not create, for the.battinfofolder wherebattinfo.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 inrootkeeps 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 aDeprecationWarning, and accepts only the keywords ofbattinfo.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/uidmint 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 undermode="upsert". Seesave_recordfor 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/uidmint 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. Seesave_recordfor 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 anidgets an IRI minted from its identity (seesave_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 reportscontent_changed: False.duplicate_policy – What
"create_only"does with a record that already exists:"error"(default) raisesValueError;"return_existing"returns the saved record’s id and path withstatus: "exists".resolve_references –
True(default) requires the cells and tests in the dataset’saboutlist to be saved under source_root, and raisesValueErrornaming each one that is not.Falseskips that check, for a dataset saved before the records it points at.series_idis not checked here.validate – Validate the record before writing it, under the named policy (
"default","strict","publisher"or"ingest"); an error raisesValueErrorand nothing is written.validation_policy – Validate the record before writing it, under the named policy (
"default","strict","publisher"or"ingest"); an error raisesValueErrorand 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_changedisTruewhen the record differs from the saved one andFalsewhen it does not, in which case nothing is rewritten unless the saved copy holds text in a quantity’svalue, which is read asvalue_textfor 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 ownid,uidand thecreated_at,modified_at,retrieved_at,generated_atandimported_attimestamps, but notpublished_at: aDatasetsaved 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;pathis the saved record and there is nocontent_changedkey."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/uidare minted deterministically from their natural identity key (see_identity_minted_uid), so re-running an identical ingest lands on the existing records — usemode="upsert"(no-op re-save reportscontent_changed: False) orduplicate_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 anid(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 thatsource_rootalready 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_rootfor 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 rootsave_cell_specwrites to). BattINFO’s bundled demo fleet is only searched withinclude_packaged_examples=True; those hits carryorigin="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
ValidationPolicyor 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: readreport.ok,report.errorsandreport.warnings, orreport.to_dict(). It raisesValueErrorfor 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@contextso 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:
- Raises:
ValueError – For an unknown
record_type, or when the document would carry a bare@typethat its own@contextdoes 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, soGr-AQ-1becomesaq-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 (seeorganization_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 (seemanufacturer=).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"). PassNoneto 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’slabelnames 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
Nonefor 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 callquickstart()for a copy-pasteable end-to-end example. Wraps the lower-level object-graph enginebattinfo.Workspace(defined inbattinfo._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.)