benzene.azure

Host Benzene handlers on Azure Functions — HTTP, Service Bus, Event Hub, Queue Storage, Blob Storage, Cosmos DB change feed, Timer, and Event Grid triggers — plus Service Bus, Event Hub, Queue Storage, and Event Grid outbound clients. Distribution: benzene-azure (depends on benzene-core, benzene-http).

pip install benzene-azure   # add [servicebus,eventhub,storage,eventgrid] for the real outbound clients

Overview

One host, one binding per trigger (transport-bindings §1):

Settlement (transport-bindings §1, obligation 8). Every trigger but HTTP is one-way: the trigger function returns normally — acknowledging the message — only when its invocation ended isSuccessful (ignored included); a failure, a missing result, a topic no handler serves or an uncaught error raises MessageHandlingError, and the Functions host's retry, poison-queue and dead-letter configuration decides what happens next.

Trigger Shape Unit of settlement
Service Bus, Queue Storage queue the message
Event Hub, Cosmos DB change feed stream the delivered batch — the first failing item raises, the items after it don't run, and the batch is redelivered from its checkpoint
Event Grid one-shot the invocation's event (or batch, stopping at the first failure)
Blob Storage one-shot the notification
Timer one-shot the tick — nothing to redeliver; the run is recorded as failed for the retry policy

AzureFunctionsApp(..., settlement=Settlement(at_most_once=True)) returns normally whatever the outcome (a failure is logged on benzene.settlement); Settlement(ignore_unmatched_topics=True) acknowledges a topic no handler serves (at any version) as ignored.

Topic for the metadata-carrying transports (Service Bus, Event Hub) comes from the topic application property. The envelope-less / event-shaped triggers (Queue Storage, Blob, Cosmos, Timer, Event Grid) have no native string→string header channel, so their topic comes from an injectable default — or, for Queue Storage and Event Grid, is lifted from the envelope the matching outbound client embedded.

AzureFunctionsApp

from benzene.azure import AzureFunctionsApp

app = AzureFunctionsApp(http_router=router, registry=registry)   # shares one pipeline

An entry-point helper wraps an app as the plain callable each trigger invokes (adapting the azure.functions types lazily) — http_function, service_bus_function, event_hub_function, queue_storage_function, blob_function, cosmos_function, timer_function, event_grid_function — mirroring benzene.gcp.http_function and benzene.aws.to_lambda_handler. The default_topic-taking triggers accept it on the helper too (e.g. queue_storage_function(app, default_topic="orders")). The package and its tests need no Azure SDK; see the example's function_app.py for the v2-model wiring.

benzene.azure also exports the per-trigger decoders for custom wiring — decode_service_bus, decode_event_hub_event, decode_queue_storage, decode_blob_created, decode_cosmos_document, decode_timer, decode_event_grid — plus is_cloud_event (the CloudEvents-vs-native discriminator).

Outbound clients

All four implement benzene.core.MessageSender, import their SDK lazily, and map a send failure to service-unavailable (never raising for a domain outcome).

Testing

benzene.azure.testing provides AzureFunctionsTestHost with one send_* per trigger — send_http, send_service_bus, send_event_hub / send_event_hub_batch, send_queue_storage / send_queue_text, send_blob, send_cosmos, send_timer, send_event_grid — and the native-input fakes behind them (FakeServiceBusMessage, FakeEventHubEvent, FakeQueueMessage, FakeBlobTrigger, FakeTimerRequest). send_queue_text drives the "plain text under default_topic" path and send_queue_storage the embedded-envelope path; send_event_grid accepts either a native or a CloudEvents 1.0 event. Specialize the shared harness with create_test_host(StartUp).build_azure(). See Hosting on Azure Functions and the runnable examples/azure_orders.

See also