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/
Metadata
Release files for babbelfish 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| babbelfish-0.1.4.tar.gz | 6.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| babbelfish-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 13.2 kB
Release files / babbelfish-0.1.4.tar.gz
| Download URL | babbelfish-0.1.4.tar.gz |
|---|---|
| Size | 6.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c4f0784e24320035e8a837dd593814ca13847c3280e6d72f828b922cee0191cf
|
|
BLAKE2b-256 checksum How to use checksums |
969dab42c1b064a3f6ee46e0b4209c49281c1a0bf10e64e2705305a9481fadfd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / babbelfish-0.1.4-py3-none-any.whl
| Download URL | babbelfish-0.1.4-py3-none-any.whl |
|---|---|
| Size | 7.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88a8ea968e323e8dbc71a47fa9aeb9c2335851fd80872e9d72357b97c42df428
|
|
BLAKE2b-256 checksum How to use checksums |
2b7698d409bf81e894873358949f03d1503c9ff874d102edbc9e811125e35f78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for babbelfish 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.