How to Use the SmartEM API Client#

This guide explains how to use the unified SmartEM API client to communicate with the SmartEM Core API.

Overview#

The SmartEM API Client provides a unified interface to interact with the SmartEM Core API. It supports both synchronous and asynchronous operations, making it flexible for different usage scenarios. The client also includes data conversion utilities to convert between EPU data models and API request/response models.

Installation#

The SmartEM API Client is included with the SmartEM Decisions package, so no additional installation is required.

Basic Usage#

Importing the Client#

from smartem_backend.api_client import SmartEMAPIClient

Creating a Client Instance#

# Create a client with default timeout (10 seconds)
client = SmartEMAPIClient("http://localhost:8000")

# Create a client with custom timeout
client = SmartEMAPIClient("http://localhost:8000", timeout=30.0)

# Create a client with custom logger
import logging
logger = logging.getLogger("my_logger")
client = SmartEMAPIClient("http://localhost:8000", logger=logger)

Using the Client with Context Managers#

The client supports context managers to ensure proper resource cleanup:

# Synchronous context manager
with SmartEMAPIClient("http://localhost:8000") as client:
    # Use the client...
    status = client.get_status()
    print(status)

# Asynchronous context manager
async with SmartEMAPIClient("http://localhost:8000") as client:
    # Use the client asynchronously...
    status = await client.aget_status()
    print(status)

Synchronous vs Asynchronous Methods#

The client provides both synchronous and asynchronous methods for all operations. The asynchronous methods are prefixed with a (e.g., aget_status vs get_status).

# Synchronous method
acquisitions = client.get_acquisitions()

# Asynchronous method
acquisitions = await client.aget_acquisitions()

Working with Acquisitions#

Creating an Acquisition#

from smartem_agent.model.schemas import EpuSessionData
from datetime import datetime

# Create an acquisition from EPU session data
session = EpuSessionData(
    id="my-acquisition-id",
    name="My Acquisition",
    start_time=datetime.now(),
    storage_path="/path/to/storage"
)
response = client.create_acquisition(session)
print(f"Created acquisition with ID: {response.id}")

# Alternatively, create directly with an API request model
from smartem_backend.model.http_request import AcquisitionCreateRequest

request = AcquisitionCreateRequest(
    id="my-acquisition-id",
    name="My Acquisition"
)
response = client.create_acquisition(request)

Retrieving Acquisitions#

# Get all acquisitions
acquisitions = client.get_acquisitions()
for acq in acquisitions:
    print(f"Acquisition: {acq.id} - {acq.name}")

# Get a specific acquisition
acquisition = client.get_acquisition("acquisition-id")
print(f"Acquisition details: {acquisition.name}, Status: {acquisition.status}")

Updating an Acquisition#

from smartem_backend.model.http_request import AcquisitionUpdateRequest

update = AcquisitionUpdateRequest(
    name="Updated Acquisition Name",
    status="completed"
)
updated = client.update_acquisition("acquisition-id", update)

Deleting an Acquisition#

client.delete_acquisition("acquisition-id")

Working with Hierarchical Data#

The client supports the full hierarchy of entities:

  1. Acquisition

  2. Grid

  3. Grid Square

  4. Foil Hole

  5. Micrograph

Here’s an example of creating entities at each level:

# Create an acquisition
acquisition = client.create_acquisition(EpuSessionData(id="acq-1", name="Test Acquisition"))

# Create a grid for the acquisition
from smartem_agent.model.schemas import GridData

grid = GridData(data_dir="/path/to/grid")
grid_response = client.create_acquisition_grid(acquisition.id, grid)

# Create a grid square for the grid
from smartem_agent.model.schemas import GridSquareData

gridsquare = GridSquareData(id="gs-1", data_dir="/path/to/gridsquare")
gridsquare_response = client.create_grid_gridsquare(grid_response.id, gridsquare)

# Create a foil hole for the grid square
from smartem_agent.model.schemas import FoilHoleData

foilhole = FoilHoleData(id="fh-1", gridsquare_id=gridsquare.id)
foilhole_response = client.create_gridsquare_foilhole(gridsquare_response.id, foilhole)

# Create a micrograph for the foil hole
from smartem_agent.model.schemas import MicrographData, MicrographManifest

manifest = MicrographManifest(
    unique_id="mic-1",
    acquisition_datetime=datetime.now(),
    detector_name="K3",
    energy_filter=True,
    phase_plate=False,
    binning_x=1,
    binning_y=1
)
micrograph = MicrographData(
    id="mic-1",
    gridsquare_id=gridsquare.id,
    foilhole_id=foilhole.id,
    location_id="loc-1",
    high_res_path="/path/to/mic.mrc",
    manifest_file="/path/to/manifest.xml",
    manifest=manifest
)
micrograph_response = client.create_foilhole_micrograph(foilhole_response.id, micrograph)

EntityStore Compatibility#

The client maintains compatibility with the existing EntityStore API for seamless integration:

# Create an entity through the EntityStore compatibility API
success = client.create("acquisition", "acq-id", session_data, parent=None)

# Create a grid with parent relationship
success = client.create("grid", "grid-id", grid_data, parent=("acquisition", "acq-id"))

Error Handling#

The client includes comprehensive error handling:

try:
    response = client.get_acquisition("non-existent-id")
except httpx.HTTPStatusError as e:
    print(f"HTTP error {e.response.status_code}: {e}")
except Exception as e:
    print(f"Error: {e}")

Logging#

The client includes detailed logging. You can configure the log level to control verbosity:

import logging
logging.basicConfig(level=logging.INFO)  # For normal operation
logging.basicConfig(level=logging.DEBUG)  # For more detailed logs including request/response data

Advanced Usage#

Working with Raw API Responses#

If you need to work with the raw API responses rather than parsed models:

raw_response = await client._request("get", "status")
print(raw_response)

Managing the ID Mapping Cache#

The client maintains a cache of entity IDs to database IDs:

# Get a mapped database ID
db_id = client._get_db_id("acquisition", "entity-id")

# Store a mapping manually (normally done automatically)
client._store_entity_id_mapping("acquisition", "entity-id", "db-id")

Closing the Client#

Always close the client when you’re done to free up resources:

# Explicitly close
client.close()

# Or use context managers (recommended)
with SmartEMAPIClient("http://localhost:8000") as client:
    # Client will be automatically closed when exiting the context
    pass