Docs · Context Transfer Engine
The clio_cte_core_ext module.
clio_cte_core_ext is the Python binding of the CTE client. It ships in the iowarp-core wheel and is built with nanobind from context-transfer-engine/wrapper/python/core_bindings.cc. Payloads are bytes. Every blocking call releases the GIL. The higher-level clio_cee module is documented in the Context Exploration Engine reference.
- Installed by
- pip install iowarp-core
- Attach with
- CLIO_WITH_RUNTIME=0
- Keyword search needs
- CLIO_CTE_POOL=564.0
Connecting
Set environment variables before the import; the client reads them once when it connects.
import os
os.environ["CLIO_WITH_RUNTIME"] = "0" # attach to a running clio_run
os.environ["CLIO_CTE_POOL"] = "564.0" # only needed for SemanticSearch
import clio_cte_core_ext as cte
cte.clio_init(cte.RuntimeMode.kClient, False)
cte.initialize_cte("", cte.PoolQuery.Dynamic())
client = cte.get_cte_client()clio_init(mode, default_with_runtime=False, is_restart=False) -> bool.CLIO_WITH_RUNTIMEoverrides the second argument. Waits up toCLIO_WAIT_SERVERseconds (default 30) for the runtime.initialize_cte(config_path, pool_query) -> bool. Both arguments are required; pass""andPoolQuery.Dynamic()for the defaults.get_cte_client() -> Client: a copy of the shared client.Client()andClient(pool_id)construct one directly.
Tag
| Call | Returns |
|---|---|
Tag(tag_name) | Opens the tag, creating it if needed (GetOrCreateTag). Tag(tag_id) wraps an existing id without an RPC. |
.PutBlob(blob_name, data: bytes, off=0) | None. Writes at offset off; a new blob lands at score 1.0. Raises on a non-zero return code. |
.GetBlob(blob_name, data_size, off=0) | bytes of length data_size. Raises RuntimeError on failure. |
.GetBlobSize(blob_name) | int, bytes |
.GetBlobScore(blob_name) | float, 0.0 to 1.0 |
.GetContainedBlobs() | list[str] |
.ReorganizeBlob(blob_name, new_score) | None. Moves the blob to the tier the score selects. |
.GetTagId() | UniqueId |
Client: blocking calls
| Call | Returns |
|---|---|
TagQuery(tag_regex, max_tags=0, pool_query=PoolQuery.Broadcast()) | list[str] of tag names |
BlobQuery(tag_regex, blob_regex, max_blobs=0, pool_query=Broadcast()) | list[tuple[str, str]] of (tag_name, blob_name) |
SemanticSearch(tag_regex, blob_regex, query_text, k=10, pool_query=Broadcast()) | list[SemanticSearchResult], best first; k=0 means no cap |
TemporalSearch(tag_regex, blob_regex, time_begin=0, time_end=0, max_entries=0, pool_query=Broadcast()) | list[TemporalSearchResult], ascending by last_modified |
ReorganizeBlob(tag_id, blob_name, new_score) | True on success |
DelBlob(tag_id, blob_name) | True on success |
PollTelemetryLog(minimum_logical_time) | list[CteTelemetry] |
RegisterTarget(target_name, bdev_type, total_size) or RegisterTarget(target_name, bdev_type, total_size, target_query, bdev_id) | int, 0 on success |
Patterns must match the whole name (std::regex_match), so use .*part.* for a substring. SemanticSearch ranks every blob matching both patterns, including blobs with no query word (score 0). For files in a mount, use "[0-9]+" as the blob pattern to search pages and skip each file's attribute blob.
last_modified comes from a steady clock, although the docstrings say epoch nanoseconds. A time_begin computed from time.time() matches nothing. Treat last_modified as an ordering key within one run of the runtime. This is a suspected bug in the source.
Client: asynchronous calls
Each returns a Future. Submission runs without the GIL and may block while shared memory is allocated.
| Call | result() |
|---|---|
AsyncGetOrCreateTag(tag_name, pool_query=Dynamic()) | UniqueId |
AsyncPutBlob(tag_id, blob_name, data, off=0) | None; wait() gives the return code |
AsyncPutBlobIfAbsent(tag_id, blob_name, data, off=0) | {"code", "version"}; a non-zero code means another writer won |
AsyncPutBlobIfVersion(tag_id, blob_name, data, version, off=0) | {"code", "version"}; absent counts as version 0 |
AsyncGetBlob(tag_id, blob_name, data_size, off=0) | bytes |
AsyncGetBlobVersioned(tag_id, blob_name, data_size, off=0) | {"code", "version", "data"}; data is None on failure |
AsyncGetBlobDefer(tag_id, blob_name, data_size, off=0) | bytes, after any pending deferred put to the blob completes |
AsyncGetBlobSize(tag_id, blob_name, pool_query=Dynamic()) | int |
AsyncGetContainedBlobs(tag_id, pool_query=Broadcast()) | list[str] |
AsyncDelBlob(tag_id, blob_name) | None; wait() gives the return code |
AsyncReorganizeBlob(tag_id, blob_name, new_score) | None |
AsyncTagQuery(tag_regex, max_tags=0, pool_query=Broadcast()) | list[str] |
AsyncBlobQuery(tag_regex, blob_regex, max_blobs=0, pool_query=Broadcast()) | list[tuple[str, str]] |
AsyncSemanticSearch(tag_regex, blob_regex, query_text, k=10, pool_query=Broadcast()) | list[SemanticSearchResult] |
AsyncTemporalSearch(tag_regex, blob_regex, time_begin=0, time_end=0, max_entries=0, pool_query=Broadcast()) | list[TemporalSearchResult] |
AsyncPollTelemetryLog(minimum_logical_time) | list[CteTelemetry] |
AsyncRegisterTarget(target_name, bdev_type, total_size, target_query=Local(), bdev_id=UniqueId.GetNull(), pool_query=Dynamic(), attach_existing=0, growth_unit=1<<30) | None; wait() gives the return code |
AsyncGetCapacity(pool_query=Local()) | CapacityInfo; raises RuntimeError on a failed query |
AsyncListTargets(pool_query=Local()) | list[str]; Broadcast appends names from replicas without deduplication |
AsyncStatTargets(pool_query=Local()) | None; requests a one-shot statistics refresh |
AsyncGetTargetInfo(target_name, pool_query=Local()) | TargetInfo; raises RuntimeError on a failed query or a missing target |
Future
wait(max_sec=-1): blocks until done; returns the return code, orNoneifmax_sec(when positive) elapses first.done():Truewhen the task has completed.result(max_sec=-1): blocks, then returns the value in the table above. RaisesTimeoutErrorifmax_secelapses.
Deferred puts
AsyncPutBlobDefer(tag_id, blob_name, data, off=0, max_inflight_bytes=0) -> int: stages a copy ofdatain shared memory and returns 0 when submitted.datais free to reuse as soon as the call returns. When shared memory is exhausted the call waits for the oldest puts;max_inflight_bytesadds an explicit ceiling (0 means none).AwaitPutsUntilSpace(max_inflight_bytes) -> int: waits until at most that many payload bytes are in flight (0 drains all) and returns the bytes still in flight.DeferErrorCount() -> int: deferred puts that completed with a non-zero return code. The count is sticky.
Supporting types
UniqueId(major, minor): the tag id, withmajor_,minor_,IsNull(),ToU64()and the staticGetNull().PoolQuery.Broadcast(),PoolQuery.Dynamic(),PoolQuery.Local(parallelism=32).SemanticSearchResult:tag_id,blob_name,score.TemporalSearchResult:tag_id,blob_name,last_modified.CteTelemetry:op_(aCteOp),off_,size_,tag_id_,mod_time_,read_time_,logical_time_.CapacityInfo:total_capacity,remaining_capacity.TargetInfo:target_score,remaining_space,bytes_read,bytes_written,ops_read,ops_written.- Enums:
RuntimeMode(kClient,kServer,kRuntime),BdevType(kFile,kRam),CteOp(kPutBlob,kGetBlob,kDelBlob,kGetOrCreateTag,kDelTag,kGetTagSize).
Example
tag = cte.Tag("experiment/run1")
tag.PutBlob("note0", b"Arctic sea ice extent fell to a record low.", 0)
tag.PutBlob("note1", b"Atmospheric CO2 passed 420 ppm.", 0)
for tag_name, blob_name in client.BlobQuery("experiment/.*", "note.*"):
print(tag_name, blob_name)
for hit in client.SemanticSearch("experiment/.*", ".*", "sea ice", k=5):
print(hit.blob_name, hit.score)
tag.ReorganizeBlob("note1", 0.0)
futs = [client.AsyncGetBlob(tag.GetTagId(), n, tag.GetBlobSize(n), 0) for n in ("note0", "note1")]
payloads = [f.result() for f in futs]
client.DelBlob(tag.GetTagId(), "note0")