Skip to main content

Python client library for i3X servers

Project description

i3x-client

Python client library for i3X servers, provided by CESMII. Supports the i3X 1.0 release specification.

Connecting to pre-release servers is deprecated: servers without a GET /info endpoint (alpha) are rejected with UnsupportedVersionError, and servers reporting a pre-1.0 specVersion (beta) emit a DeprecationWarning.

Installation

pip install i3x-client

For development:

pip install -e ".[dev]"

Quick Start

import i3x

# Connect to an i3X server
client = i3x.Client("https://api.i3x.dev/v1")   # Replace with your server
client.connect()

# Check server capabilities
info = client.get_info()
print(info.spec_version, info.capabilities)

# Explore the address space
namespaces = client.get_namespaces()
object_types = client.get_object_types()
objects = client.get_objects(root=True)

# Read a value
value = client.get_value("sensor-001")
print(value.value, value.quality, value.timestamp)

# Read historical values
history = client.get_history("sensor-001", start_time="2026-01-01T00:00:00Z")
for vqt in history.values:
    print(vqt.value, vqt.timestamp)

# Write a value (quality defaults to "Good", timestamp to server time)
client.update_value("sensor-001", 72.5)
client.update_value("sensor-001", {"value": 72.5, "quality": "Good", "timestamp": "2026-01-01T00:00:00Z"})

# Write several values in one request
client.update_values({"sensor-001": 72.5, "sensor-002": 18.3})

client.disconnect()

Context Manager

with i3x.Client("https://api.i3x.dev/v1") as client:
    namespaces = client.get_namespaces()

Subscriptions (SSE Streaming)

client = i3x.Client("https://api.i3x.dev/v1")
client.on_value_change = lambda client, change: print(f"{change.element_id}: {change.value} ({change.quality})")
client.connect()

# Creates subscription, registers items, and starts SSE stream in one call
sub = client.subscribe(["sensor-001", "sensor-002"])

# on_value_change fires automatically as values arrive

# Unsubscribe when done
client.unsubscribe(sub)
client.disconnect()

Sync Mode (Polled, Acknowledged Delivery)

sub_id = client.create_subscription()
client.register_items(sub_id, ["sensor-001"])

# First poll — no lastSequenceNumber
batches = client.sync_subscription(sub_id)
last_seq = batches[-1].sequence_number if batches else None

# Subsequent polls — ack previous batches, receive new ones
batches = client.sync_subscription(sub_id, last_sequence_number=last_seq)
for batch in batches:
    for u in batch.updates:
        print(batch.sequence_number, u.element_id, u.value)

Hierarchy Traversal

get_objects() loads the full address space in a single call. Every object carries a parent_id that encodes the complete tree — both the organizational hierarchy (HasChildren) and the internal composition of each node (HasComponent). Group by parent_id to reconstruct the tree in memory without additional round-trips:

client = i3x.Client("https://api.i3x.dev/v1")
client.connect()

objects = client.get_objects()

children_of = {}
for obj in objects:
    children_of.setdefault(obj.parent_id, []).append(obj)

def print_subtree(parent_id=None, depth=0):
    for obj in children_of.get(parent_id, []):
        print("  " * depth + f"{obj.display_name}  [{obj.type_element_id}]")
        print_subtree(obj.element_id, depth + 1)

print_subtree()   # parent_id=None → roots
client.disconnect()

To start from a known root and walk only one branch using relationship queries:

def walk_components(element_id, depth=0):
    obj = client.get_object(element_id)
    print("  " * depth + obj.display_name)
    for rel in client.get_related_objects([element_id], relationship_type="HasComponent"):
        walk_components(rel.object.element_id, depth + 1)

walk_components("pump-101")

Graph Traversal

Cross-branch relationships like SuppliesTo and Monitors connect objects that are unrelated in the hierarchy, making the address space a directed graph. Follow edges with get_related_objects and a specific relationship type:

client = i3x.Client("https://api.i3x.dev/v1")
client.connect()

# Process flow: what does pump-101 feed into?
for rel in client.get_related_objects(["pump-101"], relationship_type="SuppliesTo"):
    print(f"pump-101 → {rel.object.display_name}")
# pump-101 → tank-201

# Instrumentation: which sensors cover tank-201?
for rel in client.get_related_objects(["tank-201"], relationship_type="MonitoredBy"):
    print(f"{rel.object.display_name} monitors tank-201")
# TempSensor-101 monitors tank-201

client.disconnect()

For multi-hop traversal, BFS over arbitrary relationship types:

from collections import deque

def bfs(start_id, rel_types):
    """Yield every object reachable from start_id via rel_types (breadth-first)."""
    seen, queue = set(), deque([start_id])
    while queue:
        eid = queue.popleft()
        if eid in seen:
            continue
        seen.add(eid)
        yield client.get_object(eid)
        for rel_type in rel_types:
            for rel in client.get_related_objects([eid], relationship_type=rel_type):
                queue.append(rel.object.element_id)

# Trace everything downstream of pump-101 through the process chain
for obj in bfs("pump-101", ["SuppliesTo"]):
    print(obj.display_name)
# pump-101
# tank-201

# Walk instrumentation outward from pump-101: what does it supply, and what monitors those targets?
for obj in bfs("pump-101", ["SuppliesTo", "MonitoredBy"]):
    print(obj.display_name)
# pump-101
# tank-201
# TempSensor-101

Authentication

The i3X spec requires authentication, but does not mandate an authentication scheme, so pass whatever your server requires:

# Bearer token (Authorization: Bearer <token>)
client = i3x.Client("https://my-i3x-server/v1", token="my-token")

# HTTP Basic (or any httpx auth object)
client = i3x.Client("https://my-i3x-server/v1", auth=("user", "password"))

# Custom header scheme
client = i3x.Client("https://my-i3x-server/v1", headers={"X-API-Key": "my-key"})

TLS / Self-Signed Certificates

By default the client verifies the server's TLS certificate. Development and test servers often use a self-signed certificate; pass verify to handle that:

# Skip certificate verification entirely (dev/test only)
client = i3x.Client("https://localhost:8443/v1", verify=False)

# Or trust a custom CA bundle instead of disabling verification
client = i3x.Client("https://my-dev-server/v1", verify="/path/to/ca.pem")

Leave verify=True (the default) in production. If verification fails, the client raises ConnectionError with guidance on using verify.

Note: If http:// URLs are used, and redirected to https:// by the server — the client follows redirects.

Custom Client ID

A client_id is auto-generated as a UUID and used to scope subscriptions. You can provide your own:

client = i3x.Client("https://my-i3x-server/v1", client_id="my-app-instance-1")

API Reference

Client

i3x.Client(base_url, auth=None, timeout=30.0, client_id=None, token=None, headers=None, verify=True)

base_url must include the version prefix required by the spec, e.g. https://server.example.com/v1.

Connection

  • connect() — Connect to the server (verifies via GET /info and checks specVersion)
  • disconnect() — Disconnect and stop all subscriptions
  • is_connected — Connection state
  • client_id — The client ID used to scope subscriptions
  • server_infoServerInfo captured during connect()

Server Info

  • get_info()ServerInfo — Server version and capabilities

Exploration

  • get_namespaces()list[Namespace]
  • get_object_types(namespace_uri=None)list[ObjectType]
  • query_object_types(element_ids)list[ObjectType]
  • get_relationship_types(namespace_uri=None)list[RelationshipType]
  • query_relationship_types(element_ids)list[RelationshipType]
  • get_objects(type_element_id=None, include_metadata=False, root=None)list[ObjectInstance]
  • get_object(element_id, include_metadata=False)ObjectInstance
  • list_objects(element_ids, include_metadata=False)list[ObjectInstance]
  • get_related_objects(element_ids, relationship_type=None, include_metadata=False)list[RelatedObject]

Values

  • get_value(element_id, max_depth=1)CurrentValue
  • get_values(element_ids, max_depth=1)dict[str, CurrentValue]
  • get_history(element_id, start_time=None, end_time=None, max_depth=1)HistoricalValue

max_depth controls recursion through HasComponent children: 1 = no recursion (default), N = recurse N levels, 0 = infinite.

Updates

  • update_value(element_id, value, quality=None, timestamp=None) — Write a value (raw or VQT dict)
  • update_values(updates) — Write values for multiple elements ({element_id: value})
  • update_history(element_id, values) — Write historical VQTs (timestamp required); raises NotSupportedError if the server doesn't support it

Subscriptions (High-Level)

  • subscribe(element_ids, max_depth=1, display_name=None)Subscription — Create + register + stream
  • unsubscribe(subscription) — Stop stream and delete subscription
  • sync_subscription(subscription, last_sequence_number=None)list[SyncBatch] — pass -1 to clear the queue

Subscriptions (Low-Level)

  • create_subscription(display_name=None)str — Returns subscription ID
  • register_items(subscription_id, element_ids, max_depth=1)
  • unregister_items(subscription_id, element_ids)
  • get_subscription(subscription_id)Subscription
  • list_subscriptions(subscription_ids)list[Subscription]
  • start_stream(subscription_id) — Start SSE for an existing subscription
  • stop_stream(subscription_id) — Stop SSE without deleting subscription

Callbacks

  • on_connect(client)
  • on_disconnect(client)
  • on_value_change(client, change: ValueChange)
  • on_subscribe(client, subscription: Subscription)
  • on_error(client, error: Exception)

Models

All models are frozen dataclasses.

Model Fields
ServerInfo spec_version, server_version, server_name, capabilities
Namespace uri, display_name
ObjectType element_id, display_name, namespace_uri, source_type_id, version, schema, related
RelationshipType element_id, display_name, namespace_uri, relationship_id, reverse_of
ObjectInstance element_id, display_name, type_element_id, parent_id, is_composition, is_extended, metadata
ObjectInstanceMetadata type_namespace_uri, source_type_id, description, relationships, schema_extensions, system
RelatedObject source_relationship, object
VQT value, quality, timestamp
CurrentValue element_id, is_composition, value, quality, timestamp, components
HistoricalValue element_id, is_composition, values (list of VQT)
ValueChange element_id, value, quality, timestamp
SyncBatch sequence_number, updates (list of ValueChange)
Subscription subscription_id, client_id, display_name, monitored_objects

Errors

All errors inherit from i3x.I3XError:

  • ConnectionError — Failed to connect (including TLS/certificate failures)
  • AuthenticationError — Auth rejected (401/403)
  • NotFoundError — Resource not found (404)
  • NotSupportedError — Optional feature not supported by the server (501)
  • ServerError — Server error (5xx)
  • TimeoutError — Request timed out
  • SubscriptionError — Subscription operation failed
  • StreamError — SSE streaming error
  • UnsupportedVersionErrorGET /info returned 404: a pre-release (alpha) server, or a wrong base_url
  • InvalidServerResponseErrorGET /info responded but not with a valid i3X document (e.g. base_url points at a web page, login portal, or non-i3X service)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

i3x_client-1.0.0.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

i3x_client-1.0.0-py3-none-any.whl (22.6 kB view details)

Uploaded Python 3

File details

Details for the file i3x_client-1.0.0.tar.gz.

File metadata

  • Download URL: i3x_client-1.0.0.tar.gz
  • Upload date:
  • Size: 30.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for i3x_client-1.0.0.tar.gz
Algorithm Hash digest
SHA256 cea3bfb7973f80a2aa6235c862c7a6174c2792f25c219fb0c319874f44b5dd7c
MD5 8dbe64ebdbfb853b499bc9895d7f232b
BLAKE2b-256 4fc8517281111f2c62d2c9e04ab9eb55c0e0aa7a07fe1b9d43bbbf2bc8e0026a

See more details on using hashes here.

File details

Details for the file i3x_client-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: i3x_client-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 22.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for i3x_client-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d71454c36de242c85931e8444566fd99755aeb71971ff8a178957b2a2b30aafe
MD5 acf51aeff970e4f110c2507d79a30960
BLAKE2b-256 7365b23d38605a172f689e55f35296dd87a36b3b90ec7a571f7269f06b5d0d38

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page