Open data-platform SDK over Apache Iggy: typed streaming, declared projections and a query DSL, a key-value store, copy-on-write forks, and an optional agent runtime with the Agent Data Exchange Protocol (AGDX).
Project description
laser-sdk (Python)
The LaserData SDK for Python: an open data-platform SDK over Apache Iggy. Native bindings to the Rust SDK via PyO3, so the wire contract, codecs, and runtime are the same ones the Rust client uses.
One Apache Iggy connection gives you typed streaming, declared projections and a query DSL, a key-value store, copy-on-write forks of the read model, and an optional agent runtime with the Agent Data Exchange Protocol (AGDX): publish, request/reply, and a consumer that drives your async def handler with at-least-once delivery, dedup, retry, and a dead-letter queue.
Apache Iggy is the underlying streaming core. Projections, the query layer, the key-value store, and forks are served by LaserData Cloud over that same connection. Against raw Apache Iggy those calls raise UnsupportedError.
Install
pip install laser-sdk
Wheels ship for Linux (x86_64, aarch64) and macOS (Intel, Apple Silicon), Python 3.10 through 3.13.
Connect
import asyncio
from laser_sdk import Laser
async def main():
laser = await Laser.connect("iggy://iggy:iggy@127.0.0.1:8090", stream="agents")
caps = await laser.capabilities()
print(caps)
asyncio.run(main())
The connection string scheme is optional (iggy:// is assumed). Pin a default stream= so laser.topic(name) is the one-word shortcut against it, or address any topic on any stream explicitly with laser.stream(name).topic(name). The accessors are free and synchronous, IO happens at the verbs (publish, replay, ensure), mirroring the Rust grammar one-to-one.
Publish and consume
await laser.topic("orders").ensure(partitions=4)
await (
laser.topic("orders").publish()
.index("customer_id", "alice")
.index("total", "129")
.inline_payload()
.json({"id": "o-1", "customer": "alice", "amount": 129})
.send()
)
Batch and any payload
A single publish is the simplest call, not the common one. publish_batch accumulates records and sends them in one network round-trip, the largest throughput lever the SDK offers, and reads mirror it: a topic(..).replay() cursor drains every record that arrived since the last poll in one call. Batching on both sides is what makes the path efficient.
The payload is yours, in any format. add_json / add_msgpack (and extend_json for a whole list) are conveniences over add_payload, which takes raw bytes the SDK never inspects, so a compressed blob or your own framing rides unchanged. Schema-first Avro and Protobuf bodies are below.
batch = laser.topic("orders").publish_batch().inline_payload()
batch.extend_json([{"id": "o-1", "amount": 129}, {"id": "o-2", "amount": 80}])
batch.add_payload(b"\x00any-bytes-any-format") # raw bytes, untouched by the SDK
await batch.send() # the whole batch, one round-trip
Typed topics
One handle binds a topic to a class: pass cls= (a dataclass or pydantic model) and the topic encodes on the way in and decodes with the log position attached on the way out. publish(order) encodes the instance as JSON in one call, records(group) is the typed reader over the same caller-owned offsets as replay(): next() yields the next record decoded into the class (None when caught up), and a record that does not decode raises TypedDecodeError naming its exact log position, then the reader moves past it.
from dataclasses import dataclass
@dataclass
class Order:
customer: str
amount: int
orders = laser.topic("orders", cls=Order)
await orders.publish(Order(customer="alice", amount=129)).send()
records = orders.records("billing")
while (record := await records.next()) is not None:
order: Order = record.value # an Order instance, record.position names the log slot
Schema-first bodies (Avro / Protobuf)
Compile a registered writer schema once, then publish raw datums under it. The body is encoded client-side, so a value that stops matching the schema fails before publishing rather than as a managed-side warning you cannot see. The managed plane resolves the schema by id and extracts indexed columns from the binary body.
from laser_sdk import CompiledSchema
source = {"kind": "avro", "schema": fill_avro_schema}
schema_id = await laser.register_schema(source, name="fill")
compiled = CompiledSchema.compile(source, id=schema_id)
batch = laser.topic("trades_avro").publish_batch().inline_payload()
for fill in fills:
batch = batch.add_avro(compiled, schema_id, fill)
await batch.send()
CompiledSchema also offers validate / validate_value / decode, and the single-record builder has .avro(compiled, schema_id, value). For Protobuf or your own framing, encode the body yourself and ship it with .raw_bytes(bytes, "protobuf") (or batch .add_raw_bytes(..)). Writer schemas live on LaserData Cloud, so registration is a managed feature.
Query (managed)
rows = await (
laser.query("orders")
.where_eq("customer_id", "alice")
.filter_gte("total", 100)
.order_desc("total")
.limit(10)
.with_payload()
.fetch_all()
)
for row in rows:
print(row.headers, row.json())
Query, the key-value store, and forks are managed features served by LaserData Cloud. Against raw Apache Iggy they raise UnsupportedError.
Key-value
kv = laser.kv("sessions")
await kv.set("user:42").json({"state": "online"}).ttl(300).send()
state = await kv.get_typed("user:42")
values = await kv.get_many(["user:42", "user:43"]) # one round trip (the mixed-operation batch)
await kv.copy_to("user:42", "user:42:2026", to_namespace="archive") # one backend transaction
await kv.move_to("plan:draft", "plan:current") # copy plus source delete
await kv.delete("user:42")
Agents
from laser_sdk import Laser
async def handle(ctx, message):
text = message.payload.decode()
await ctx.respond(f"echo: {text}".encode())
laser = await Laser.connect("iggy://iggy:iggy@127.0.0.1:8090", stream="agents")
await laser.bootstrap(partitions=4)
handle_agent = laser.spawn_agent(
"echo", "agent.commands", handle, respond_on="agent.responses"
)
await handle_agent.ready()
from laser_sdk import Provenance
reply = await laser.request(
"agent.commands", "agent.responses", b"hello",
Provenance(agent="caller"), timeout_secs=10,
)
print(reply.payload.decode())
await handle_agent.shutdown()
For a human-in-the-loop pause, the typed AGDX producer's request_input publishes a prompt and blocks on the human's correlated reply, which a handler resolves with AgentCtx.respond_input:
decision = await laser.agdx("agent.human_input", "orchestrator", conversation_id).request_input(
"agent.responses", b"approve a $500 refund?", timeout_secs=15
)
Runs (managed)
The managed run registry answers "what happened to that task" without folding topics yourself. Gated on the agent_workflow capability, UnsupportedError elsewhere.
runs = laser.runs()
run = await runs.submit("diagnoser", b'{"incident": "INC-7"}')
info = await runs.status(run.run_id)
page = await runs.list(state="running", limit=25)
await runs.cancel(run.run_id) # records the intent, the engine observes it
wf = laser.workflow("incident-response")
wf.registered() # the run's lifecycle lands in the registry
Change feed (managed)
Await a view's advance instead of polling it blind. A projection binding built with notify makes the plane publish one change record per committed batch; laser.watch() reads that feed. Gated on the watch capability, UnsupportedError elsewhere.
feed = laser.watch(index="orders_v1")
for change in await feed.poll():
print(change.index, change.from_offset, change.to_offset, change.rows)
rows = await laser.query("orders_v1").fetch_all() # the record is a wakeup, the rows come from query
saved = feed.offsets # persist to resume after a restart
Consume and replay
# A resumable reader over a topic. Each poll drains what is new. Persist the
# offsets to resume after a restart.
cursor = laser.topic("orders").replay()
for message in await cursor.poll():
print(message.json())
saved = cursor.offsets
# Replay a conversation's history off the log (agent runtime).
history = await laser.assemble_context(conversation_id, last_n=50)
Memory and state
Agent memory shares one remember / recall / forget surface over four backends, and the kv-backed handle adds the named-item altitude: set(key, value) / fetch(key) / update(key, patch) / remove(key) for working notes addressed by name (UnsupportedError on the other backends). The log-backed default works on raw Apache Iggy. The in-process vector backend ranks recall by semantic similarity, embedding through your own async def embed(text) -> list[float]. The query and key-value backends are managed.
async def embed(text: str) -> list[float]:
... # your model, or a deterministic stand-in
memory = laser.vector_memory(embed)
await memory.remember("checkout latency traces to the database pool", conversation=cid)
hits = await memory.recall(conversation=cid, semantic="why is checkout slow", limit=3)
print([item.text for item in hits])
# A durable key/value seam for agent state, the same vocabulary as the managed store.
store = ls.InMemoryStore() # or ls.FileStore("/var/lib/agent")
await store.set("cursor", saved_bytes)
value = await store.get("cursor")
Edge interop (A2A / MCP / AG-UI)
Reach an agent as an A2A task source or an MCP tool server, and render a conversation as AG-UI events, all over the durable log:
# A2A: submit a task, poll for the result.
a2a = laser.a2a_bridge("a2a-gateway", "agent.commands", "agent.responses")
task = await a2a.submit({"message": {"role": "user", "parts": [{"kind": "text", "text": "hi"}]}})
status = await a2a.task(task["id"])
# MCP: advertise tools, route tools/call to the agent.
mcp = laser.mcp_bridge(
"mcp-gateway", "agent.tool_calls", "agent.tool_results", "laser-mcp",
tools=[{"name": "ask", "input_schema": {"type": "object"}}],
)
tools = mcp.list_tools()
result = await mcp.call_tool("ask", {"q": "what is AGDX?"})
# An agent answers a bridge request from its handler:
async def handle(ctx, message):
await ctx.respond_input("agent.responses", b"the answer")
# AG-UI: snapshot + deltas reconstruct shared state off the log.
await laser.publish_state_snapshot("agent.llm_io", "ui", conversation_id, {"count": 1})
state = await laser.reconstruct_state(conversation_id, "agent.llm_io")
events = await laser.agui_events(conversation_id, "agent.llm_io")
Host the actual HTTP endpoint with your Python web framework over these adapter methods.
Errors
Every failure raises a subclass of LaserError: QueryError, KvError, ForkError, UnsupportedError, InvalidError, CodecError, ProtocolError, TimeoutError, ConfigError, TransportError. Each instance carries code, retryable, unsupported, not_found, version_skew, version_conflict, and stale attributes so you can branch without matching on the type.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file laser_sdk-0.0.1rc7.tar.gz.
File metadata
- Download URL: laser_sdk-0.0.1rc7.tar.gz
- Upload date:
- Size: 525.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0545b789ddc240192b8b31adb4d74ecf79005f26fc8906832fc04a9cc1aa0f0c
|
|
| MD5 |
5445a71674462f6caeeab54b6713a80b
|
|
| BLAKE2b-256 |
6b7d32265cbf2916db6b72408c24826479b7f2c7dfb2b61e21d83e4e4f819f25
|
Provenance
The following attestation bundles were made for laser_sdk-0.0.1rc7.tar.gz:
Publisher:
ci-python.yml on laserdata/laser-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
laser_sdk-0.0.1rc7.tar.gz -
Subject digest:
0545b789ddc240192b8b31adb4d74ecf79005f26fc8906832fc04a9cc1aa0f0c - Sigstore transparency entry: 2065782049
- Sigstore integration time:
-
Permalink:
laserdata/laser-sdk@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Branch / Tag:
refs/tags/py-v0.0.1-rc.7 - Owner: https://github.com/laserdata
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci-python.yml@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Trigger Event:
push
-
Statement type:
File details
Details for the file laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 11.7 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7126eb2e7a111ca7d476339df7dc22836a7f4e34dda0c9703003f89f09703256
|
|
| MD5 |
b7f19a350889a83c13dac6d1a97a4676
|
|
| BLAKE2b-256 |
a6e2646dc820dc3943eab009b21b434f437282ac0f293bb699d43c5288ad3644
|
Provenance
The following attestation bundles were made for laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
ci-python.yml on laserdata/laser-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
7126eb2e7a111ca7d476339df7dc22836a7f4e34dda0c9703003f89f09703256 - Sigstore transparency entry: 2065782138
- Sigstore integration time:
-
Permalink:
laserdata/laser-sdk@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Branch / Tag:
refs/tags/py-v0.0.1-rc.7 - Owner: https://github.com/laserdata
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci-python.yml@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Trigger Event:
push
-
Statement type:
File details
Details for the file laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 11.7 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bd00f377c06f0c56d9d2805f827ee667d50589157c596238a8dbe9d4e6a0a96
|
|
| MD5 |
5de88d5cbfe85d3df62d7c91305f74c0
|
|
| BLAKE2b-256 |
0a62bfe3b8cd1e5dea4ddddd4b044b6e2d5a28ff80bae3a788a4f7fd7dc42a6f
|
Provenance
The following attestation bundles were made for laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
ci-python.yml on laserdata/laser-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
laser_sdk-0.0.1rc7-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
5bd00f377c06f0c56d9d2805f827ee667d50589157c596238a8dbe9d4e6a0a96 - Sigstore transparency entry: 2065782086
- Sigstore integration time:
-
Permalink:
laserdata/laser-sdk@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Branch / Tag:
refs/tags/py-v0.0.1-rc.7 - Owner: https://github.com/laserdata
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci-python.yml@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Trigger Event:
push
-
Statement type:
File details
Details for the file laser_sdk-0.0.1rc7-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.
File metadata
- Download URL: laser_sdk-0.0.1rc7-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 21.8 MB
- Tags: CPython 3.10+, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55c55441394383376bc6ad5ff50089534b0a11c524ca6126a8259ca3e289275d
|
|
| MD5 |
457e93877121a82d4e03afc2a990b479
|
|
| BLAKE2b-256 |
ae012afe6f1890997f2a554d1ca32f925647e0bfaa70af9326f2714f9566a8b3
|
Provenance
The following attestation bundles were made for laser_sdk-0.0.1rc7-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:
Publisher:
ci-python.yml on laserdata/laser-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
laser_sdk-0.0.1rc7-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl -
Subject digest:
55c55441394383376bc6ad5ff50089534b0a11c524ca6126a8259ca3e289275d - Sigstore transparency entry: 2065782188
- Sigstore integration time:
-
Permalink:
laserdata/laser-sdk@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Branch / Tag:
refs/tags/py-v0.0.1-rc.7 - Owner: https://github.com/laserdata
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci-python.yml@86c70c2d5f1aa51f6e1db7ab99ae99cb7ee75991 -
Trigger Event:
push
-
Statement type: