Job result openers
client.get_job_results(job_id) retrieves result values and references.
client.open_job_result(job_id) waits for completion and opens a selected
output using a registered opener. Cuiman includes Pillow, xarray, pandas, and
GeoPandas openers; each requires its corresponding optional library. The Pillow
image opener takes precedence over the dataset openers for supported images,
including PNG and JPEG, and returns a PIL.Image.Image. Custom openers extend
or specialize this behavior.
The following example uses simulate_scene from the
local test service. Run the Python blocks in
order from the repository root in the Pixi environment. The maintained source
is openers.py.
Prepare a small scene
This request creates two variables on a 4 × 4 grid for two dates. The generated values are zero; the example demonstrates data access, not a scientific product.
{
"inputs": {
"var_names": "a, b",
"bbox": [10, 40, 12, 42],
"resolution": 0.5,
"start_date": "2025-01-01",
"end_date": "2025-01-03",
"periodicity": 1,
"output_path": "guide-scene.zarr"
}
}
output_path is relative to the server's working directory. The process
replaces existing data at that path, so choose an unused path. The example
assumes server and client run on the same machine and can access the same
filesystem. A file:// link from a remote server is not automatically accessible
to your client. Omitting the path uses server-process memory, which a separate
client process cannot read.
Submit once
from pathlib import Path
from urllib.parse import urlsplit
from urllib.request import url2pathname
import xarray as xr
from cuiman import Client
from cuiman.api.opener import JobResultOpenContext, JobResultOpener
from gavicore.models import ProcessRequest
client = Client(api_url="http://127.0.0.1:8008", auth={"auth_type": "none"})
def submit_scene(client: Client, request_path: Path) -> str:
"""Load a scene request and return the newly submitted job's ID."""
request = ProcessRequest.model_validate_json(
request_path.read_text(encoding="utf-8")
)
job = client.execute_process("simulate_scene", request=request)
print(job.model_dump_json(indent=2))
return job.jobID
request_path = Path("examples/guides/cuiman/simulate-scene-request.json")
job_id = submit_scene(client, request_path)
Retain the returned job_id. Once its status is successful,
client.get_job_results(job_id) shows a link to the dataset with media type
application/zarr.
Open with a built-in opener
def open_scene(client: Client, job_id: str) -> xr.Dataset:
"""Wait up to 30 seconds and use the built-in xarray opener."""
return client.open_job_result(
job_id,
output_name="return_value",
data_type=xr.Dataset,
engine="zarr",
timeout=30,
poll_interval=0.1,
)
dataset = open_scene(client, job_id)
try:
print(dataset)
print(dict(dataset.sizes))
finally:
dataset.close()
data_type=xr.Dataset selects a compatible opener, output_name selects the
process output, and engine="zarr" is forwarded to xarray. The printed sizes
are lat: 4, lon: 4, and time: 2.
The helper waits up to 30 seconds for completion. A running job that exceeds
this deadline raises TimeoutError; failed or dismissed jobs raise
JobResultStatusError. Inspect the job before retrying. Opening an existing
job's output does not submit a new job. Always close datasets after use.
Add a custom opener
A custom opener decides whether it can handle the requested output, then opens it. This example specializes in local Zarr links and converts file URIs to native paths, including on Windows. It also handles escaped spaces in paths, which the built-in reader's current file-URI handling may not resolve. Use paths without spaces for the built-in example, or this custom opener:
class LocalZarrOpener(JobResultOpener):
"""Open local Zarr links as datasets, converting file URIs to native paths."""
async def accept_job_result(self, ctx: JobResultOpenContext) -> bool:
"""Accept the selected output only if it is a local Zarr dataset."""
link = ctx.output_link
if link is None or ctx.data_type not in (None, xr.Dataset):
return False
url = urlsplit(link.href)
return (
ctx.output_media_type == "application/zarr"
and url.scheme == "file"
and url.netloc in ("", "localhost")
)
async def open_job_result(self, ctx: JobResultOpenContext) -> xr.Dataset:
"""Pass the native filesystem path and reader options to xarray."""
link = ctx.output_link
assert link is not None
path = url2pathname(urlsplit(link.href).path)
return xr.open_zarr(path, **ctx.options)
ctx.output_link resolves the requested output name; it can be None.
The acceptance check also respects the requested data type and media type.
The example imports xarray directly because it is required by this guide;
reusable plugins can implement is_usable() to detect optional dependencies.
Register the opener temporarily and use the same completed job:
def open_with_custom_opener(client: Client, job_id: str) -> xr.Dataset:
"""Temporarily prefer the custom opener, restoring registration afterward."""
unregister = client.config.register_job_result_opener(LocalZarrOpener)
try:
return client.open_job_result(
job_id, output_name="return_value", data_type=xr.Dataset, timeout=30
)
finally:
unregister()
dataset = open_with_custom_opener(client, job_id)
try:
print(dataset)
finally:
dataset.close()
New registrations take precedence over built-ins. Registration belongs to the
client's configuration class, so it affects clients sharing that class.
The returned callback removes the registration, including when opening fails.
For an application's permanent extensions, prefer a ClientConfig subclass
with extra_job_result_openers; see Customization.
Close the client
client.close()
The complete script uses try/finally for client and dataset cleanup. Run it
to submit one scene and open it with the built-in opener:
python -m examples.guides.cuiman.openers
The original opener notebook remains available as a historical example. Its assumption that no openers are registered by default no longer applies.