airbus-client 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.5
2
+ Name: airbus-client
3
+ Version: 0.1.0
4
+ Summary: Python client for the Airbus JSON-RPC event bus
5
+ Project-URL: Homepage, https://github.com/ghhwer/airbus
6
+ Project-URL: Repository, https://github.com/ghhwer/airbus
7
+ Author: Airbus contributors
8
+ License: MIT
9
+ Requires-Python: >=3.13
10
+ Requires-Dist: jsonschema>=4.26.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # airbus-client
14
+
15
+ Python SDK for the [Airbus](https://github.com/ghhwer/airbus) JSON-RPC event bus.
16
+
17
+ ```bash
18
+ pip install airbus-client
19
+ ```
20
+
21
+ ```python
22
+ from airbus_client import RpcClient, Queue
23
+
24
+ client = RpcClient() # AIRBUS_URL / AIRBUS_HOST:AIRBUS_PORT
25
+ assert client.ping() == "pong"
26
+ ```
27
+
28
+ The daemon is distributed separately (container image or `cargo`/`make build`).
29
+ See the [repository README](https://github.com/ghhwer/airbus) for run instructions.
@@ -0,0 +1,17 @@
1
+ # airbus-client
2
+
3
+ Python SDK for the [Airbus](https://github.com/ghhwer/airbus) JSON-RPC event bus.
4
+
5
+ ```bash
6
+ pip install airbus-client
7
+ ```
8
+
9
+ ```python
10
+ from airbus_client import RpcClient, Queue
11
+
12
+ client = RpcClient() # AIRBUS_URL / AIRBUS_HOST:AIRBUS_PORT
13
+ assert client.ping() == "pong"
14
+ ```
15
+
16
+ The daemon is distributed separately (container image or `cargo`/`make build`).
17
+ See the [repository README](https://github.com/ghhwer/airbus) for run instructions.
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "airbus-client"
3
+ version = "0.1.0"
4
+ description = "Python client for the Airbus JSON-RPC event bus"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = { text = "MIT" }
8
+ dependencies = ["jsonschema>=4.26.0"]
9
+ authors = [{ name = "Airbus contributors" }]
10
+ urls = { Homepage = "https://github.com/ghhwer/airbus", Repository = "https://github.com/ghhwer/airbus" }
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ packages = ["src/airbus_client"]
18
+
19
+ [dependency-groups]
20
+ dev = [
21
+ "datamodel-code-generator==0.78.0",
22
+ "pytest>=9.1.1",
23
+ ]
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
@@ -0,0 +1,34 @@
1
+ """Public airbus_client API.
2
+
3
+ Import from this package root for application use. Wire ``*Params`` / ``*Result``
4
+ types live under ``airbus_client.payloads`` for raw RPC. ``protocol``, ``contracts``,
5
+ ``binary``, and ``endpoint`` are operational internals.
6
+
7
+ Endpoint defaults: ``AIRBUS_URL`` or ``AIRBUS_HOST``/``AIRBUS_PORT`` (else
8
+ ``127.0.0.1:9097``). ``RpcClient()`` and ``Queue(name)`` use that resolution;
9
+ pass ``client=`` for ephemeral test servers.
10
+ """
11
+
12
+ from airbus_client.binary import run_airbus
13
+ from airbus_client.payloads import (
14
+ DispatchStrategy,
15
+ DuplexSide,
16
+ ListenerEventParams,
17
+ QueueMode,
18
+ )
19
+ from airbus_client.queue import Queue
20
+ from airbus_client.rpc import EventListener, RpcClient, RpcError
21
+ from airbus_client.server import start_server
22
+
23
+ __all__ = [
24
+ "DispatchStrategy",
25
+ "DuplexSide",
26
+ "EventListener",
27
+ "ListenerEventParams",
28
+ "Queue",
29
+ "QueueMode",
30
+ "RpcClient",
31
+ "RpcError",
32
+ "run_airbus",
33
+ "start_server",
34
+ ]
@@ -0,0 +1,10 @@
1
+ from airbus_client.server import start_server
2
+
3
+
4
+ def main() -> None:
5
+ with start_server("127.0.0.1:0") as rpc:
6
+ print(rpc.ping())
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()
@@ -0,0 +1,20 @@
1
+ import os
2
+ import subprocess
3
+ from pathlib import Path
4
+
5
+
6
+ def airbus_bin() -> Path:
7
+ env = os.environ.get("AIRBUS_BIN")
8
+ if env:
9
+ return Path(env)
10
+ return Path(__file__).resolve().parents[3] / "out" / "airbus"
11
+
12
+
13
+ def run_airbus(*args: str, input_text: str | None = None) -> subprocess.CompletedProcess[str]:
14
+ return subprocess.run(
15
+ [str(airbus_bin()), *args],
16
+ input=input_text,
17
+ capture_output=True,
18
+ text=True,
19
+ check=True,
20
+ )
@@ -0,0 +1,5 @@
1
+ # @generated by scripts/generate_payloads.py — do not edit by hand.
2
+ import json
3
+
4
+ SCHEMAS = json.loads('{"add_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "items": {"type": "number"}, "maxItems": 2, "minItems": 2, "title": "AddParams", "type": "array"}, "add_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "title": "AddResult", "type": "number"}, "attach_listener_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"exhaustion_timeout_ms": {"minimum": 0, "type": "integer"}, "host": {"type": "string"}, "max_retries": {"minimum": 0, "type": "integer"}, "port": {"maximum": 65535, "minimum": 1, "type": "integer"}, "queue": {"$ref": "#/$defs/QueueName"}, "side": {"$ref": "#/$defs/DuplexSide", "description": "Listener side for full-duplex queues; required when mode is full-duplex, rejected otherwise"}}, "required": ["queue", "port"], "title": "AttachListenerParams", "type": "object"}, "attach_listener_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"listener_id": {"minLength": 1, "type": "string"}, "queue": {"$ref": "#/$defs/QueueName"}, "status": {"type": "string"}}, "required": ["listener_id", "queue", "status"], "title": "AttachListenerResult", "type": "object"}, "create_queue_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"dispatch_strategy": {"$ref": "#/$defs/DispatchStrategy", "description": "Fifo only; rejected for broadcast and full-duplex. Default when omitted on fifo: round_robin"}, "mode": {"$ref": "#/$defs/QueueMode"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue"], "title": "CreateQueueParams", "type": "object"}, "create_queue_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"created": {"type": "boolean"}, "mode": {"$ref": "#/$defs/QueueMode"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue", "mode", "created"], "title": "CreateQueueResult", "type": "object"}, "detach_listener_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"listener_id": {"minLength": 1, "type": "string"}}, "required": ["listener_id"], "title": "DetachListenerParams", "type": "object"}, "detach_listener_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"detached": {"type": "boolean"}, "listener_id": {"minLength": 1, "type": "string"}}, "required": ["listener_id", "detached"], "title": "DetachListenerResult", "type": "object"}, "list_listeners_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"queue": {"$ref": "#/$defs/QueueName"}}, "title": "ListListenersParams", "type": "object"}, "list_listeners_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"listeners": {"items": {"additionalProperties": false, "properties": {"active": {"type": "boolean"}, "failure_count": {"minimum": 0, "type": "integer"}, "host": {"type": "string"}, "id": {"minLength": 1, "type": "string"}, "mode": {"$ref": "#/$defs/QueueMode"}, "port": {"maximum": 65535, "minimum": 1, "type": "integer"}, "queue": {"$ref": "#/$defs/QueueName"}, "side": {"$ref": "#/$defs/DuplexSide", "description": "Present for full-duplex listeners"}}, "required": ["id", "queue", "host", "port", "mode", "failure_count", "active"], "type": "object"}, "type": "array"}}, "required": ["listeners"], "title": "ListListenersResult", "type": "object"}, "list_queues_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"queues": {"items": {"$ref": "#/$defs/QueueInfo"}, "type": "array"}}, "required": ["queues"], "title": "ListQueuesResult", "type": "object"}, "listener_event_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"event": {"$ref": "#/$defs/EventObject"}, "id": {"minLength": 1, "type": "string"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue", "id", "event"], "title": "ListenerEventParams", "type": "object"}, "listener_event_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"status": {"enum": ["ok"], "type": "string"}}, "required": ["status"], "title": "ListenerEventResult", "type": "object"}, "peek_events_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"count": {"default": 1, "type": "integer"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue"], "title": "PeekEventsParams", "type": "object"}, "peek_events_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"events": {"items": {"additionalProperties": false, "properties": {"event": {"$ref": "#/$defs/EventObject"}, "id": {"minLength": 1, "type": "string"}}, "required": ["id", "event"], "type": "object"}, "type": "array"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue", "events"], "title": "PeekEventsResult", "type": "object"}, "ping_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "title": "PingResult", "type": "string"}, "post_event_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"event": {"$ref": "#/$defs/EventObject"}, "queue": {"$ref": "#/$defs/QueueName"}, "side": {"$ref": "#/$defs/DuplexSide", "description": "Publisher side for full-duplex queues; required when mode is full-duplex, rejected otherwise"}}, "required": ["queue", "event"], "title": "PostEventParams", "type": "object"}, "post_event_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"id": {"minLength": 1, "type": "string"}, "queue": {"$ref": "#/$defs/QueueName"}}, "required": ["id", "queue"], "title": "PostEventResult", "type": "object"}, "queue_ready_params.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"queue": {"$ref": "#/$defs/QueueName"}}, "required": ["queue"], "title": "QueueReadyParams", "type": "object"}, "queue_ready_result.schema.json": {"$defs": {"DispatchStrategy": {"enum": ["round_robin", "single_node"], "title": "DispatchStrategy", "type": "string"}, "DuplexSide": {"enum": ["host", "device"], "title": "DuplexSide", "type": "string"}, "EventObject": {"additionalProperties": true, "title": "EventObject", "type": "object"}, "QueueInfo": {"additionalProperties": false, "properties": {"depth": {"minimum": 0, "type": "integer"}, "listener_count": {"minimum": 0, "type": "integer"}, "mode": {"$ref": "#/$defs/QueueMode"}, "name": {"$ref": "#/$defs/QueueName"}}, "required": ["name", "depth", "mode", "listener_count"], "title": "QueueInfo", "type": "object"}, "QueueMode": {"enum": ["broadcast", "fifo", "full-duplex"], "title": "QueueMode", "type": "string"}, "QueueName": {"minLength": 1, "title": "QueueName", "type": "string"}}, "additionalProperties": false, "properties": {"queue": {"$ref": "#/$defs/QueueName"}, "ready": {"description": "Mode-aware readiness: exists for fifo/broadcast; both duplex sides attached for full-duplex", "type": "boolean"}}, "required": ["queue", "ready"], "title": "QueueReadyResult", "type": "object"}}')
5
+ METHODS = json.loads('{"add": {"params": "add_params.schema.json", "params_required": true, "receiver": "server", "result": "add_result.schema.json"}, "attach_listener": {"params": "attach_listener_params.schema.json", "params_required": true, "receiver": "server", "result": "attach_listener_result.schema.json"}, "create_queue": {"params": "create_queue_params.schema.json", "params_required": true, "receiver": "server", "result": "create_queue_result.schema.json"}, "detach_listener": {"params": "detach_listener_params.schema.json", "params_required": true, "receiver": "server", "result": "detach_listener_result.schema.json"}, "list_listeners": {"params": "list_listeners_params.schema.json", "params_required": false, "receiver": "server", "result": "list_listeners_result.schema.json"}, "list_queues": {"params": null, "params_required": false, "receiver": "server", "result": "list_queues_result.schema.json"}, "on_event": {"params": "listener_event_params.schema.json", "params_required": true, "receiver": "listener", "result": "listener_event_result.schema.json"}, "peek_events": {"params": "peek_events_params.schema.json", "params_required": true, "receiver": "server", "result": "peek_events_result.schema.json"}, "ping": {"params": null, "params_required": false, "receiver": "server", "result": "ping_result.schema.json"}, "post_event": {"params": "post_event_params.schema.json", "params_required": true, "receiver": "server", "result": "post_event_result.schema.json"}, "queue_ready": {"params": "queue_ready_params.schema.json", "params_required": true, "receiver": "server", "result": "queue_ready_result.schema.json"}}')
@@ -0,0 +1,36 @@
1
+ """Resolve the Airbus TCP endpoint from the environment.
2
+
3
+ Precedence: ``AIRBUS_URL`` (``host:port``), else ``AIRBUS_HOST`` / ``AIRBUS_PORT``,
4
+ else ``127.0.0.1:9097``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+
11
+ _DEFAULT_HOST = "127.0.0.1"
12
+ _DEFAULT_PORT = 9097
13
+
14
+
15
+ def parse_endpoint(url: str) -> tuple[str, int]:
16
+ """Parse ``host:port`` into a host/port pair."""
17
+ host, _, port_s = url.rpartition(":")
18
+ if not host or not port_s.isdigit():
19
+ raise ValueError(f"Airbus endpoint must be host:port (got {url!r}); example: 127.0.0.1:9097")
20
+ return host, int(port_s)
21
+
22
+
23
+ def airbus_endpoint() -> tuple[str, int]:
24
+ """Resolve host/port from ``AIRBUS_URL`` or ``AIRBUS_HOST`` / ``AIRBUS_PORT``."""
25
+ url = os.environ.get("AIRBUS_URL", "").strip()
26
+ if url:
27
+ return parse_endpoint(url)
28
+ host = os.environ.get("AIRBUS_HOST", _DEFAULT_HOST)
29
+ port = int(os.environ.get("AIRBUS_PORT", str(_DEFAULT_PORT)))
30
+ return host, port
31
+
32
+
33
+ def airbus_url() -> str:
34
+ """Return ``host:port`` for the resolved endpoint."""
35
+ host, port = airbus_endpoint()
36
+ return f"{host}:{port}"
@@ -0,0 +1,164 @@
1
+ # @generated by scripts/generate_payloads.py — do not edit by hand.
2
+ # Source of truth: schema/payloads/*.schema.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from enum import StrEnum
8
+ from typing import Any
9
+
10
+ type PingResult = str
11
+ type AddResult = float
12
+
13
+ class QueueMode(StrEnum):
14
+ broadcast = 'broadcast'
15
+ fifo = 'fifo'
16
+ full_duplex = 'full-duplex'
17
+
18
+
19
+ class DispatchStrategy(StrEnum):
20
+ round_robin = 'round_robin'
21
+ single_node = 'single_node'
22
+
23
+
24
+ class DuplexSide(StrEnum):
25
+ host = 'host'
26
+ device = 'device'
27
+
28
+
29
+ @dataclass
30
+ class QueueInfo:
31
+ name: str
32
+ depth: int
33
+ mode: QueueMode
34
+ listener_count: int
35
+
36
+
37
+ type AddParams = list[float]
38
+
39
+
40
+ @dataclass
41
+ class PostEventParams:
42
+ queue: str
43
+ event: dict[str, Any]
44
+ side: DuplexSide | None = None
45
+
46
+
47
+ @dataclass
48
+ class PostEventResult:
49
+ id: str
50
+ queue: str
51
+
52
+
53
+ @dataclass
54
+ class ListQueuesResult:
55
+ queues: list[QueueInfo]
56
+
57
+
58
+ @dataclass
59
+ class PeekEventsParams:
60
+ queue: str
61
+ count: int | None = 1
62
+
63
+
64
+ @dataclass
65
+ class Event:
66
+ id: str
67
+ event: dict[str, Any]
68
+
69
+
70
+ @dataclass
71
+ class PeekEventsResult:
72
+ queue: str
73
+ events: list[Event]
74
+
75
+
76
+ @dataclass
77
+ class CreateQueueParams:
78
+ queue: str
79
+ mode: QueueMode | None = None
80
+ dispatch_strategy: DispatchStrategy | None = None
81
+
82
+
83
+ @dataclass
84
+ class CreateQueueResult:
85
+ queue: str
86
+ mode: QueueMode
87
+ created: bool
88
+
89
+
90
+ @dataclass
91
+ class AttachListenerParams:
92
+ queue: str
93
+ port: int
94
+ host: str | None = None
95
+ exhaustion_timeout_ms: int | None = None
96
+ max_retries: int | None = None
97
+ side: DuplexSide | None = None
98
+
99
+
100
+ @dataclass
101
+ class AttachListenerResult:
102
+ listener_id: str
103
+ queue: str
104
+ status: str
105
+
106
+
107
+ @dataclass
108
+ class DetachListenerParams:
109
+ listener_id: str
110
+
111
+
112
+ @dataclass
113
+ class DetachListenerResult:
114
+ listener_id: str
115
+ detached: bool
116
+
117
+
118
+ @dataclass
119
+ class ListListenersParams:
120
+ queue: str | None = None
121
+
122
+
123
+ @dataclass
124
+ class Listener:
125
+ id: str
126
+ queue: str
127
+ host: str
128
+ port: int
129
+ mode: QueueMode
130
+ failure_count: int
131
+ active: bool
132
+ side: DuplexSide | None = None
133
+
134
+
135
+ @dataclass
136
+ class ListListenersResult:
137
+ listeners: list[Listener]
138
+
139
+
140
+ @dataclass
141
+ class QueueReadyParams:
142
+ queue: str
143
+
144
+
145
+ @dataclass
146
+ class QueueReadyResult:
147
+ queue: str
148
+ ready: bool
149
+
150
+
151
+ @dataclass
152
+ class ListenerEventParams:
153
+ queue: str
154
+ id: str
155
+ event: dict[str, Any]
156
+
157
+
158
+ class Status(StrEnum):
159
+ ok = 'ok'
160
+
161
+
162
+ @dataclass
163
+ class ListenerEventResult:
164
+ status: Status
@@ -0,0 +1,185 @@
1
+ """JSON-RPC envelopes and schema-backed payload codecs; no sockets or threads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import types
7
+ from collections.abc import Callable
8
+ from dataclasses import fields, is_dataclass
9
+ from enum import Enum
10
+ from typing import Any, get_args, get_origin, get_type_hints
11
+
12
+ from jsonschema import Draft202012Validator, ValidationError
13
+
14
+ from airbus_client import payloads
15
+ from airbus_client.contracts import METHODS, SCHEMAS
16
+
17
+ VERSION = "2.0"
18
+ _ABSENT = object()
19
+ _VALIDATORS = {name: Draft202012Validator(schema) for name, schema in SCHEMAS.items()}
20
+
21
+
22
+ class RpcError(Exception):
23
+ def __init__(self, code: int, message: str, data: Any = None, *, id: int | str | None = None):
24
+ super().__init__(f"{code}: {message}")
25
+ self.code, self.message, self.data, self.id = code, message, data, id
26
+
27
+
28
+ def to_wire(value: Any) -> Any:
29
+ if is_dataclass(value) and not isinstance(value, type):
30
+ # Omit absent optional fields, but preserve nulls inside user event dictionaries.
31
+ return {
32
+ field.name: to_wire(item)
33
+ for field in fields(value)
34
+ if (item := getattr(value, field.name)) is not None
35
+ }
36
+ if isinstance(value, Enum):
37
+ return value.value
38
+ if isinstance(value, dict):
39
+ return {key: to_wire(item) for key, item in value.items()}
40
+ if isinstance(value, list):
41
+ return [to_wire(item) for item in value]
42
+ return value
43
+
44
+
45
+ def validate_payload(method: str, kind: str, value: Any) -> None:
46
+ if kind == "params" and value is None and not METHODS[method]["params_required"]:
47
+ return
48
+ schema = METHODS[method][kind]
49
+ if schema is None:
50
+ if value not in (None, {}):
51
+ raise ValueError(f"{method} takes no parameters")
52
+ return
53
+ try:
54
+ _VALIDATORS[schema].validate(value)
55
+ except ValidationError as error:
56
+ raise ValueError(f"{method} {kind}: {error.message}") from error
57
+
58
+
59
+ def _decode(model: Any, value: Any) -> Any:
60
+ if value is None:
61
+ return None
62
+ origin, args = get_origin(model), get_args(model)
63
+ if origin is types.UnionType:
64
+ model = next(choice for choice in args if choice is not type(None))
65
+ return _decode(model, value)
66
+ if origin is list:
67
+ return [_decode(args[0], item) for item in value]
68
+ if is_dataclass(model):
69
+ hints = get_type_hints(model)
70
+ return model(
71
+ **{
72
+ field.name: _decode(hints[field.name], value[field.name])
73
+ for field in fields(model)
74
+ if field.name in value
75
+ }
76
+ )
77
+ if isinstance(model, type) and issubclass(model, Enum):
78
+ return model(value)
79
+ if model is float:
80
+ return float(value)
81
+ return value
82
+
83
+
84
+ def decode_result(method: str, model: Any, value: Any) -> Any:
85
+ validate_payload(method, "result", value)
86
+ return _decode(model, value)
87
+
88
+
89
+ def request(method: str, params: Any = None, id: Any = _ABSENT) -> dict[str, Any]:
90
+ document = {"jsonrpc": VERSION, "method": method}
91
+ if id is not _ABSENT:
92
+ document["id"] = id
93
+ if params is not None:
94
+ document["params"] = to_wire(params)
95
+ return document
96
+
97
+
98
+ def _valid_id(value: Any) -> bool:
99
+ return value is None or type(value) in (int, str)
100
+
101
+
102
+ def validate_response(response: Any, expected_id: Any) -> dict[str, Any]:
103
+ if not isinstance(response, dict) or response.get("jsonrpc") != VERSION:
104
+ raise ValueError("invalid JSON-RPC response version")
105
+ if "id" not in response or not _valid_id(response["id"]):
106
+ raise ValueError("invalid JSON-RPC response id")
107
+ if type(response["id"]) is not type(expected_id) or response["id"] != expected_id:
108
+ raise ValueError("JSON-RPC response id does not match request")
109
+ if ("result" in response) == ("error" in response):
110
+ raise ValueError("response must contain exactly one of result or error")
111
+ if "error" in response:
112
+ error = response["error"]
113
+ if (
114
+ not isinstance(error, dict)
115
+ or type(error.get("code")) is not int
116
+ or not isinstance(error.get("message"), str)
117
+ ):
118
+ raise ValueError("invalid JSON-RPC error object")
119
+ return response
120
+
121
+
122
+ def response_result(response: dict[str, Any]) -> Any:
123
+ if "error" in response:
124
+ error = response["error"]
125
+ raise RpcError(error["code"], error["message"], error.get("data"), id=response["id"])
126
+ return response["result"]
127
+
128
+
129
+ def batch_responses(requests: list[dict], response: Any) -> list[dict | None]:
130
+ expected = {item["id"] for item in requests if "id" in item}
131
+ if not expected:
132
+ if response is not None:
133
+ raise ValueError("notifications must not receive a response")
134
+ return [None] * len(requests)
135
+ if not isinstance(response, list):
136
+ raise ValueError("batch response must be an array")
137
+ by_id = {}
138
+ for item in response:
139
+ if not isinstance(item, dict) or not _valid_id(item.get("id")):
140
+ raise ValueError("invalid batch response")
141
+ id = item.get("id")
142
+ if id not in expected or id in by_id:
143
+ raise ValueError("unexpected or duplicate batch response id")
144
+ by_id[id] = validate_response(item, next(value for value in expected if value == id))
145
+ if set(by_id) != expected:
146
+ raise ValueError("missing batch response")
147
+ return [by_id[item["id"]] if "id" in item else None for item in requests]
148
+
149
+
150
+ def _error_response(id: Any, code: int, message: str) -> dict[str, Any]:
151
+ return {"jsonrpc": VERSION, "id": id, "error": {"code": code, "message": message}}
152
+
153
+
154
+ def handle_event(body: str, callback: Callable[[dict[str, Any]], Any]) -> dict[str, Any] | None:
155
+ try:
156
+ document = json.loads(body)
157
+ except ValueError:
158
+ return _error_response(None, -32700, "invalid JSON")
159
+ if not isinstance(document, dict):
160
+ return _error_response(None, -32600, "invalid request")
161
+ id = document.get("id")
162
+ if (
163
+ document.get("jsonrpc") != VERSION
164
+ or not _valid_id(id)
165
+ or not isinstance(document.get("method"), str)
166
+ ):
167
+ return _error_response(id if _valid_id(id) else None, -32600, "invalid request")
168
+ notification = "id" not in document
169
+ if document["method"] != "on_event":
170
+ return None if notification else _error_response(id, -32601, "method not found")
171
+ try:
172
+ validate_payload("on_event", "params", document.get("params"))
173
+ event = _decode(payloads.ListenerEventParams, document["params"])
174
+ except ValueError as error:
175
+ return None if notification else _error_response(id, -32602, str(error))
176
+ try:
177
+ callback(to_wire(event))
178
+ except Exception as error:
179
+ return None if notification else _error_response(id, -32603, str(error))
180
+ if notification:
181
+ return None
182
+ acknowledgment = payloads.ListenerEventResult(status=payloads.Status.ok)
183
+ result = to_wire(acknowledgment)
184
+ validate_payload("on_event", "result", result)
185
+ return {"jsonrpc": VERSION, "id": id, "result": result}