Source code for ionworks.cell_specification
"""Cell specification client for managing cell type definitions.
This module provides the :class:`CellSpecificationClient` for creating,
reading, updating, and deleting cell specifications, which define the
properties of battery cell types (manufacturer, chemistry, ratings, etc.).
"""
from __future__ import annotations
from typing import Any
import warnings
from ionworks.errors import IonworksError
from .models import CellSpecification
[docs]
class CellSpecificationClient:
"""Client for managing cell specifications.
Provides methods to create, read, update, and delete cell specifications,
which define the properties of battery cell types (manufacturer, chemistry,
ratings, etc.).
"""
[docs]
def __init__(self, client: Any) -> None:
"""Initialize the CellSpecificationClient.
Parameters
----------
client : Any
The HTTP client instance for making API requests.
"""
self.client = client
[docs]
def get(self, cell_spec_id: str) -> CellSpecification:
"""Get a specific cell specification by ID.
Parameters
----------
cell_spec_id : str
The ID of the cell specification to retrieve.
Returns
-------
CellSpecification
The requested cell specification object.
"""
endpoint = f"/cell_specifications/{cell_spec_id}"
response_data = self.client.get(endpoint)
return CellSpecification(**response_data)
[docs]
def list(
self,
include_components: bool = False,
) -> list[CellSpecification]:
"""List all cell specifications.
Parameters
----------
include_components : bool, optional
If True, returns each specification with its
nested component and material data. Defaults
to False (metadata only).
Returns
-------
list[CellSpecification]
A list of all cell specification objects.
"""
endpoint = "/cell_specifications"
if include_components:
endpoint += "?full=true"
response_data = self.client.get(endpoint)
return [CellSpecification(**item) for item in response_data]
[docs]
def create(self, data: dict[str, Any]) -> CellSpecification:
"""Create a new cell specification.
Parameters
----------
data : dict[str, Any]
Dictionary containing the cell specification data.
Returns
-------
CellSpecification
The newly created cell specification object.
"""
endpoint = "/cell_specifications"
response_data = self.client.post(endpoint, data)
return CellSpecification(**response_data)
[docs]
def create_or_get(self, data: dict[str, Any]) -> CellSpecification:
"""Create a new cell specification or get an existing one.
Creates a new cell specification if it doesn't exist, otherwise returns
the existing one.
Parameters
----------
data : dict[str, Any]
Dictionary containing the cell specification data.
Returns
-------
CellSpecification
The cell specification object (newly created or existing).
"""
try:
return self.create(data)
except IonworksError as e:
if e.error_code == "CONFLICT" or e.status_code == 409:
# Try to get existing spec by ID from error detail
if e.data is not None:
detail = e.data.get("detail", {})
existing_id = (
detail.get("existing_id") if isinstance(detail, dict) else None
)
if existing_id:
return self.get(existing_id)
# Deprecated: legacy error format fallback
legacy_id = e.data.get("existing_cell_specification_id")
if legacy_id:
warnings.warn(
"Received legacy error key "
"'existing_cell_specification_id'. "
"Update the backend to use the "
"standardized error format.",
DeprecationWarning,
stacklevel=2,
)
return self.get(legacy_id)
# Fall back to listing and matching by name
spec_name = data.get("name")
if spec_name:
for spec in self.list():
if spec.name == spec_name:
return spec
raise ValueError(
f"Cell specification '{spec_name}' reported as "
"duplicate but could not be found"
) from e
raise
[docs]
def update(self, cell_spec_id: str, data: dict[str, Any]) -> CellSpecification:
"""Update an existing cell specification.
Parameters
----------
cell_spec_id : str
The ID of the cell specification to update.
data : dict[str, Any]
Dictionary containing the fields to update. Supports nested
component/material data for upsert.
Returns
-------
CellSpecification
The updated cell specification object.
"""
endpoint = f"/cell_specifications/{cell_spec_id}"
response_data = self.client.put(endpoint, data)
return CellSpecification(**response_data)
[docs]
def delete(self, cell_spec_id: str) -> None:
"""Delete a cell specification by ID.
Parameters
----------
cell_spec_id : str
The ID of the cell specification to delete.
"""
endpoint = f"/cell_specifications/{cell_spec_id}"
self.client.delete(endpoint)