Skip to main content

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 as hannah.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
  • Bearer tokens, 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 through shipping.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 the HANNAH_LOG_INSTANCE environment variable, else the hostname. In a container whose hostname is just its ID (a new one with every recreate), it uses container instead. Running the same component in several containers? Set HANNAH_LOG_INSTANCE, or hostname: 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_address is used while none is announced.
  • Never blocks the component: sending runs on its own threads. Connection problems are logged once to the hannah_grpc.logging logger (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, GetSatellites on the hannah.v2 path decides. If Core answers UNIMPLEMENTED, all calls use hannah.v1 and a warning is logged once (hannah_grpc.client logger). 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 write hannah.v2 code only, and on the hannah.v1 path the library translates (hannah_grpc.translate). Requests are strict: a field set on a hannah.v2 message that hannah.v1 doesn't have is an error, nothing is dropped silently. GetDevices/ControlDevice map 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: use resolve() and hannah.v1 messages there.
  • Headers: the interceptors attach x-proto-version and x-compat-version. For grpc.aio streams, pass metadata=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)

Source distribution for hannah-grpc-lib 0.8.1
File Size Uploaded Provenance
hannah_grpc_lib-0.8.1.tar.gz 40.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hannah-grpc-lib 0.8.1
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page