hannah-grpc-lib (Python)
Shared gRPC client code for Hannah components. Until 0.3.1 this package was called
hannah-logging (import hannah_logging); that package gets no further updates.
Logging (hannah_grpc.logging)
Ships the logs of a Hannah component to the Hannah log collector, so a user can download one archive of all components' logs from the WebUI.
The library adds one logging.Handler. Your existing handlers (stdout, journald, syslog)
keep working unchanged.
Installation
pip install hannah-grpc-lib
The library relies on the hannah-proto version the component already uses, because
every call to Hannah Core has to carry the matching protocol version.
Usage
import logging
import hannah_grpc.logging as hannah_logging # alias it: `from hannah_grpc import logging` would shadow the stdlib
logging.basicConfig(level=logging.INFO)
# As early as possible: everything logged from here on is buffered.
shipping = hannah_logging.install(
"telegram",
version=__version__,
secrets=[config.bot_token], # masked wherever they appear
logger_categories={"hannah.stt": hannah_logging.TRANSCRIPT},
)
# Once the config is loaded:
shipping.connect(
hannah_address="localhost:50051", # discovery via Hannah Core
collector_address=None, # optional static fallback
)
Setting the collector address directly
A component that knows the collector address itself (Hannah Core, which announces it) passes it in instead of subscribing to discovery:
shipping.set_collector_address("192.168.1.10:50061") # None withdraws it
It starts shipping if connect hasn't been called yet, takes precedence over the static
collector_address, and a new address moves the stream.
Categories
Each line is tagged GENERAL, TRANSCRIPT (speech transcripts, user utterances) or
METADATA (room and device names, presence). Exports can leave out the last two, and
the WebUI does so by default.
- By logger name:
logger_categories={"hannah.stt": TRANSCRIPT}also covers child loggers such ashannah.stt.whisper. - Per call:
log.info("heard: %s", text, extra={hannah_logging.CATEGORY_ATTR: hannah_logging.TRANSCRIPT})
Secrets
Before a line leaves the process, the library masks:
- values of keys such as
password,token,api_key,secret,psk Bearertokens, JWTs, credentials in URLs (mqtt://user:pw@host), PEM private keys- Telegram bot tokens and well-known token prefixes (
glpat-,ghp_, …) - every value passed as
secrets=[…]or later throughshipping.add_secret(…)
Extra regexes go in secret_patterns=[…]. The component's own handlers still see the
unmasked line.
Behaviour
- Buffer: 4 MiB by default (
max_buffer_bytes). When it is full, the oldest lines are dropped and reported to the collector as a gap. While no collector is known, the buffer simply keeps running as a ring. - Timestamps are taken when a line is logged, not when it is sent.
- Instance name: tells several instances of a component apart in the export. Without
instance, the library uses theHANNAH_LOG_INSTANCEenvironment variable, else the hostname. In a container whose hostname is just its ID (a new one with every recreate), it usescontainerinstead. Running the same component in several containers? SetHANNAH_LOG_INSTANCE, orhostname:in Compose. - Discovery: subscribes to Hannah Core's infrastructure announcements and follows the
log collector when it moves. If Core is unreachable, the last known collector is kept.
The static
collector_addressis used while none is announced. - Never blocks the component: sending runs on its own threads. Connection problems
are logged once to the
hannah_grpc.logginglogger (not shipped) and retried with backoff. - Shutdown: at exit, the library tries for up to 2 seconds to send what is still
buffered (
shipping.close(timeout=…)).
Calling Hannah Core (hannah_grpc.client)
Works with hannah.v2 and falls back to hannah.v1 (the previous generation, N−1) when
Hannah Core is too old for hannah.v2, so a component can be updated before Core. The
two generations are never assumed to be wire-identical: the library only picks the path,
each generation uses its own message classes, and it translates nothing. The stub sends
a message of the other generation's class as an error instead of putting it on the wire.
import grpc.aio
from hannah_proto.v1 import hannah_pb2 as hannah_v1
from hannah_proto.v2 import hannah_pb2 as hannah_v2
from hannah_grpc import client as hannah_client
channel = grpc.aio.insecure_channel(address, interceptors=hannah_client.aio_interceptors())
stubs = hannah_client.VersionedStub(channel)
stub = await stubs.resolve()
pb = hannah_v1 if stubs.previous else hannah_v2 # stubs.generation is "v1" or "v2"
await stub.SubmitText(pb.SubmitTextRequest(text="..."))
# after a reconnect (e.g. a stream was lost):
stubs.reset()
Synchronous channels: grpc.intercept_channel(channel, *hannah_client.sync_interceptors())
and SyncVersionedStub.
- Probe: once per connection,
GetSatelliteson thehannah.v2path decides. If Core answersUNIMPLEMENTED, all calls usehannah.v1and a warning is logged once (hannah_grpc.clientlogger). Any other error isn't cached; the next call probes again. A single method Core doesn't know yet does not switch the connection. - Translation: with
resolve_translated()you writehannah.v2code only, and on thehannah.v1path the library translates (hannah_grpc.translate). Requests are strict: a field set on ahannah.v2message thathannah.v1doesn't have is an error, nothing is dropped silently.GetDevices/ControlDevicemap v1 state keys to typed slots and back (the device class is inferred on the way up; slots that v1 can't name, e.g. a humidity next to a temperature, keep their slot ID as the key).AgentConnect, the device sync between the ioBroker adapter and Core, is not translated: useresolve()andhannah.v1messages there. - Headers: the interceptors attach
x-proto-versionandx-compat-version. Forgrpc.aiostreams, passmetadata=hannah_client.stream_metadata(stubs.service, "Method")explicitly; grpc.aio's stream interceptors don't reliably apply metadata.
Metadata
Release files for hannah-grpc-lib 0.8.1
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 | |
|---|---|---|---|---|
| hannah_grpc_lib-0.8.1.tar.gz | 40.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hannah_grpc_lib-0.8.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.1 kB
Release files / hannah_grpc_lib-0.8.1.tar.gz
| Download URL | hannah_grpc_lib-0.8.1.tar.gz |
|---|---|
| Size | 40.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f70dde7c12770e90cdefd32dc316ede056f75d0f2ea28c5c6357bcbf6115894f
|
|
BLAKE2b-256 checksum How to use checksums |
057864e25b797e11ddc2467c5847019db19a131cb955caf3908c17280a284cf3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / hannah_grpc_lib-0.8.1-py3-none-any.whl
| Download URL | hannah_grpc_lib-0.8.1-py3-none-any.whl |
|---|---|
| Size | 29.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b203d0287acb249d0d287b668944673400121cec3a3e160079bdc88a56e46ed
|
|
BLAKE2b-256 checksum How to use checksums |
3f170792dbc0035eb20ed5c7edef147e0831986983fe422bb294a2e20b3d4085
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Security for hannah-grpc-lib 0.8.1
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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.