A framework for lightweight services
Project description
babbelfish
A lightweight async service framework used by every sensor service in this
workspace. Provides two base classes plus convention-driven YAML loading on
top of heisskleber transports.
The goal is to let a service implementation be a single file: one
@dataclass for configuration, one subclass of Service with an async
runner(), and nothing else. Everything else — signal handling, sender
wiring, config-file parsing — is handled here.
Quick tour
from dataclasses import dataclass
from babbelfish import Service, ServiceConf
@dataclass
class MyServiceConf(ServiceConf):
topic: str = "my-topic"
poll_interval: float = 1.0
class MyService(Service):
def __init__(self, config: MyServiceConf) -> None:
self.config = config
self.senders = list(config.senders.values())
super().__init__(config)
async def runner(self) -> None:
while True:
payload = {"epoch": ..., "value": 42}
await asyncio.gather(
*[s.send(payload, topic=self.config.topic) for s in self.senders]
)
await asyncio.sleep(self.config.poll_interval)
if __name__ == "__main__":
config = MyServiceConf.from_file("config.yaml")
service = MyService(config)
service.start()
asyncio.run(service.task)
Example config.yaml:
name: "my-service"
topic: "/my-topic"
poll_interval: 0.5
output:
mqtt:
host: localhost
port: 1883
zmq:
host: 127.0.0.1
port: 5555
file:
rollover: 3600
name_fmt: "mine_%Y%m%dT%H%M%S.csv"
directory: "/tmp"
format: "csv"
ServiceConf
Dataclass base for service configuration. Subclass it, add your own fields,
and call from_file(path) to load a YAML config.
Built-in fields
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
str |
— | Service name. Also used as the logging.getLogger key. |
precision |
int |
6 |
Rounding precision for float payload fields (services apply this at publish time). |
senders |
dict[str, Sender[Any]] |
{} |
Populated automatically from the output: section of the YAML. |
receivers |
dict[str, Receiver[Any]] |
{} |
Populated automatically from the input: section of the YAML. |
Class methods
from_file(path)— load a YAML file and delegate tofrom_dict.from_dict(data)— popoutput:/input:sections, instantiate the matchingheissklebertransports via their registry, drop unknown top-level keys, and return an instance of the subclass.
output: / input: sections
The first word of each key (before the first _) selects which
heisskleber transport is used. Multiple instances of the same protocol
are supported by suffixing:
output:
mqtt: # first MQTT sender
host: primary
mqtt_backup: # second MQTT sender
host: secondary
zmq:
host: 127.0.0.1
port: 5555
The sub-dict under each key is passed to the corresponding
heisskleber config class (MqttConf, ZmqConf, FileConf, …).
Unknown protocols raise ValueError at load time.
After loading, config.senders is a dict[str, Sender] keyed by the
original label ("mqtt", "mqtt_backup", "zmq", …). Services typically
access them as a plain list: list(config.senders.values()).
Subclassing for nested structures
ServiceConf.from_dict silently drops unknown keys. If your config has
nested structure (lists of dicts, typed sub-objects, …), override
from_dict in your subclass — see msb_can.service.CanConf for an
example that parses a channels: list into typed ChannelConf instances.
Service
Abstract base class for async services. Handles signal registration, logger setup, and task lifecycle.
Required override
@abstractmethod
async def runner(self) -> None:
...
runner is expected to be an infinite loop that produces and publishes
data. It runs as an asyncio.Task created by start().
Override hooks (all optional)
| Hook | Called when | Default |
|---|---|---|
start_hook() |
Before runner() starts |
Logs Starting <name> service |
stop_hook() |
On shutdown (SIGINT / SIGTERM) | Logs Stopping <name> service |
exit_hook() |
On SIGINT | Logs Exiting <name> service |
exception_hook() |
On unhandled exception in runner() |
Logs the exception |
stop_hook() is where hardware teardown belongs — close serial ports,
disconnect I²C, stop driver threads, etc.
Signal handling
Service.__init__ installs handlers for SIGINT and SIGTERM that:
- Cancel every other task on the event loop.
- Gather them to surface any exceptions.
- Log
Program crashed successfully.and stop the loop.
Derived services must not install their own signal handlers — the base class owns the loop-level handlers.
Logger
self.logger is logging.getLogger(config.name). Pair this with a
logging.yaml that configures a logger whose key matches config.name:
loggers:
my-service: # matches config.name
level: INFO
handlers: [console]
propagate: no
Installation
Within the workspace:
[tool.uv.sources]
babbelfish = { workspace = true }
[project]
dependencies = [
"babbelfish>=0.1.3",
# …
]
Outside the workspace, install from PyPI:
uv pip install babbelfish
Or pin to a specific version:
uv pip install "babbelfish>=0.1.4"
Dependencies
heisskleber— provides theSender/Receivertransport abstractions and their registries. All services in this workspace publish viaheisskleberonly;paho-mqtt/pyzmqare never imported directly from a service.
Running the tests
uv run --no-sync pytest packages/babbelfish/tests/
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 Distribution
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 babbelfish-0.1.4.tar.gz.
File metadata
- Download URL: babbelfish-0.1.4.tar.gz
- Upload date:
- Size: 6.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4f0784e24320035e8a837dd593814ca13847c3280e6d72f828b922cee0191cf
|
|
| MD5 |
3f704145efdd0dd4b0c097d6273729cb
|
|
| BLAKE2b-256 |
969dab42c1b064a3f6ee46e0b4209c49281c1a0bf10e64e2705305a9481fadfd
|
File details
Details for the file babbelfish-0.1.4-py3-none-any.whl.
File metadata
- Download URL: babbelfish-0.1.4-py3-none-any.whl
- Upload date:
- Size: 7.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"43","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88a8ea968e323e8dbc71a47fa9aeb9c2335851fd80872e9d72357b97c42df428
|
|
| MD5 |
b905e9ee894d2d8728c6bb676975976e
|
|
| BLAKE2b-256 |
2b7698d409bf81e894873358949f03d1503c9ff874d102edbc9e811125e35f78
|