Docs / Contact / sdks/python/api-reference
Python SDK: API Reference
Complete method reference for the tendrl.Client class.
Client defines __slots__, so the public surface below is the whole of it. An unrecognized constructor keyword raises TypeError rather than being ignored, and there is no way to attach extra attributes to an instance.
| Method | Purpose |
|---|---|
Client(...) |
Construct a client |
client.start() |
Start the background sender thread |
client.stop() |
Flush, stop the thread, close connections |
client.publish(...) |
Publish a message |
@client.tether(...) |
Publish a function's return value |
client.check_msg() |
Poll for inbound messages once |
client.check_connection_state() |
Probe the server |
client.process_offline_messages() |
Replay stored messages |
Lifecycle#
Client(**kwargs)#
Create a new client instance. See Configuration for all parameters.
from tendrl import Client
client = Client(api_key="your_key")
client.start()#
Start the background sender thread, which batches queued messages, probes connectivity every 30 seconds, and (when a callback was passed) polls for inbound messages. That is all it does: it performs no network call of its own, so it does not validate the API key. A bad key surfaces later, as a failed send reported on the tendrl logger.
No-op in headless mode, where there is no thread to start.
client.stop()#
Stop the client. Signals the sender thread, waits up to 10 seconds for it to flush the batch it is holding, then closes the HTTP client (or agent socket) and the offline storage database. It sends nothing to Contact — there is no entity-status call in this SDK — so the entity goes offline on the server's own timeout. If the thread does not finish in time, a warning is logged on the tendrl logger saying up to one batch may not have been sent.
Publishing#
client.publish(data, tags=None, entity="", wait_response=False, timeout=5)#
Publish a message to Contact. data must be a dict or a str; anything else raises ValueError.
| Parameter | Type | Default | Description | |
|---|---|---|---|---|
data |
dict \ | str | — | Message payload. A string is wrapped as {"data": "..."}. |
tags |
list[str] | None |
Tags for routing to flows (max 10) | |
entity |
str | "" |
Target entity identifier. Empty sends to self. | |
wait_response |
bool | False |
True = send immediately and return the response. False = queue for batching. |
|
timeout |
int | 5 |
Request timeout in seconds, used only when the message is sent immediately |
Returns: the server's response when the message is sent immediately (wait_response=True, or any publish in headless mode), otherwise the empty string "". The empty string means "queued", not "delivered" — see undelivered-message warnings.
# Queued (non-blocking); returns "" immediately
client.publish({"temp": 23.5}, tags=["sensor"])
# Immediate with response
resp = client.publish({"alert": "high"}, wait_response=True)
If the queue is full — the sender cannot drain as fast as you publish, usually because the network is down — publish() waits one second for room and then hands the message to the undelivered path (offline storage if enabled, otherwise a dropped-message warning). It never blocks indefinitely.
client.check_msg()#
Poll Contact once for inbound messages and pass each to the callback. The sender thread already does this every check_msg_rate seconds when a callback is set; call it yourself to poll on your own schedule. Returns None; messages reach you only through the callback.
client.check_connection_state()#
Probe the server (a HEAD request in API mode, a socket connect in agent mode) and return True if it answered. The sender thread calls this every 30 seconds and stops attempting sends while it returns False.
client.process_offline_messages()#
Replay messages held in SQLite, in batches of 50, deleting only the ones that actually arrive. Called automatically when the connectivity probe recovers; call it yourself to force a flush. No-op when offline_storage is off.
@client.tether(tags=None, write_offline=False, db_ttl=3600)#
Decorator that publishes the return value of a function.
| Parameter | Type | Default | Description |
|---|---|---|---|
tags |
list[str] | None |
Tags applied to the published message |
write_offline |
bool | False |
Also store locally for offline resilience |
db_ttl |
int | 3600 |
Offline storage TTL in seconds |
@client.tether(tags=["metrics"], write_offline=True)
def collect():
return {"cpu": 42.5}
collect() # Publishes {"cpu": 42.5} with tags ["metrics"]
Message Receiving#
There is exactly one way to receive messages: pass a callback to the constructor. The SDK has no routing decorators — @client.on(), @client.on_default and @client.on_state() do not exist and raise AttributeError.
def handle(message):
if "ai-response" in (message.get("tags") or []):
print(message.get("data"))
client = Client(api_key="your_key", callback=handle)
client.start()
Constructor arguments that control receiving:
| Argument | Default | Description |
|---|---|---|
callback |
None |
Called with every inbound message. Polling only runs when this is set. |
check_msg_rate |
3.0 |
Seconds between polls |
check_msg_limit |
1 |
Max messages retrieved per poll |
The callback must be callable or the constructor raises TypeError. Exceptions it raises are caught and logged on the tendrl logger, so a bad inbound message cannot kill the sender thread.
The handler receives a dict shaped like:
{
"msg_type": "publish",
"source": "account:region:entity:name",
"timestamp": "2025-01-15T10:30:00Z",
"data": { ... },
"tags": ["tag1"]
}
File Transfer#
The Python SDK has no file-transfer methods. send_file, check_files, download_file and rescan_file are Go and MicroPython features. To move files from Python, call the /entities/files REST endpoints directly — see File Transfer.
Heartbeat#
The Python SDK does not send heartbeats and has no heartbeat method. send_heartbeat and heartbeat_interval are not valid constructor arguments and raise TypeError. If you need heartbeats, use the Go SDK, the MicroPython client, or the Nano Agent.
State Table#
The Python SDK doesn't wrap the state table in either direction: there is no on_state, check_state, get_state, update_state or replace_state, and state_callback is not a valid constructor argument. Call the REST endpoints directly:
| Method | Path | Purpose |
|---|---|---|
GET |
/api/entities/status-table |
Read current state |
PATCH |
/api/entities/status-table |
Merge into existing state |
PUT |
/api/entities/status-table |
Replace state entirely |
Authenticate with the entity's API key. See REST protocol → State Table for full examples.
Message Structure#
Messages sent to Contact follow this structure:
{
"msg_type": "publish",
"data": { "temperature": 23.5 },
"timestamp": "2025-01-15T10:30:00Z",
"context": {
"tags": ["sensor"],
"wait": false
}
}
String data is automatically wrapped: "hello" becomes {"data": "hello"}.
Tendrl