Overview of Faust Stream Processing
masterasyncio and features static typing to facilitate robust stream processing applications.repository·master·Indexed 23 days ago
https://github.com/faust-streaming/faustA fork of the Faust project, faust-streaming is an asyncio-based Python library for stream processing. It provides a framework for building scalable event processing pipelines featuring an in-memory durable K/V store, distributed state via Faust Tables (backed by RocksDB or Aerospike), and integration with Kafka topics using records and agents.
asyncio and features static typing to facilitate robust stream processing applications.Faust is organized into several core modules that handle different aspects of stream processing:
faust.app: Defines the Faust application, including configuration and message production.faust.cli: The command-line interface.faust.exceptions: Custom exceptions.faust.models: Defines how message keys and values are serialized/deserialized.faust.sensors: Records statistics from a running application.faust.serializers: Handles JSON serialization and encoding codecs.faust.stores: Manages table storage (e.g., in-memory, RocksDB).faust.streams: Implementation of Streams and Tables.faust.topics: Tools for creating and managing topic descriptions.faust.transport: Message transport implementations (e.g., aiokafka).faust.types: Public interface for static typing.faust.utils: General utilities.faust.web: Web abstractions and the Faust web server.faust.windows: Windowing strategies.faust.worker: Deployment helpers for signal handling and graceful shutdown.An App is the central instance of the Faust library. It serves as the core API provider and manages the lifecycle of stream processors (agents), topics, channels, web views, and CLI commands.
Key characteristics:
To create an application, you must provide a unique name (ID), a message broker URL, and optionally a storage driver for tables.
import faust
app = faust.App('example', broker='kafka://', store='rocksdb://')LiveCheck is an end-to-end testing tool designed for production or staging environments in microservice architectures or any asynchronous system (e.g., a monolith sending Celery tasks).
It acts as a passive observer that tracks requests as they travel through your system and allows you to define contracts that must be met at every step. Because it is a passive observer, it can detect and alert when subsystems are down or when anomalies occur (e.g., checking if an account debt exceeds a threshold after a specific change).
Key features:
tables to store and manage stateful data during the test lifecycle.An agent is a distributed system processing events in a stream. Each event is a key/value pair, typically described using faust.Record for type safety and serialization.
To create an agent, use the @app.agent decorator on an async function. The function must take a stream as its argument and iterate over it using async for.
Partitioning Behavior:
key=None), messages are distributed evenly across available workers. This is ideal for distributing work in a cluster.Fault Tolerance: If a worker fails, Kafka moves the partition to an online worker. Faust uses "standby tables" and a custom partition manager to optimize startup time and availability.
import faust
app = faust.App('example', broker='kafka://localhost:9092')
@app.agent()
async def myagent(stream):
async for event in stream:
... # process eventPolymorphic fields allow a field to hold different types of models at runtime. To enable this, set polymorphic_fields=True on the parent model.
Faust handles this by adding a __faust metadata field to the serialized payload, which contains the namespace (ns) of the model. During deserialization, Faust uses this metadata to reconstruct the correct specific subclass.
Important: You must import the specific model classes before attempting to deserialize them, otherwise Faust will not find them in its internal index.
import faust
from typing import List
class Asset(faust.Record):
url: str
type: str
class ImageAsset(Asset):
type = 'image'
class VideoAsset(Asset):
runtime_seconds: float
type = 'video'
class Article(faust.Record, polymorphic_fields=True):
assets: List[Asset]Faust follows strict coding standards:
mypy. To keep imports lightweight, interfaces for classes are defined in faust/types/. For example, faust.App has a corresponding faust.types.app.AppT.from xxx import * is prohibited.def method(self, arg: str) -> None:
"""Short description.
More details.
"""A Service is a component that can be started and stopped. Faust is composed of many services, including App, Stream, Agent, Table, and TableManager.
Services are built using the mode.Service class. You can register a service class with your app using the @app.service decorator. Services can implement on_start and on_stop lifecycle methods and can run background tasks using the @Service.task decorator.
import faust
from mode import Service
app = faust.App('service-example')
@app.service
class MyService(Service):
async def on_start(self):
print('MYSERVICE IS STARTING')
async def on_stop(self):
print('MYSERVICE IS STOPPING')
@Service.task
async def _background_task(self):
while not self.should_stop:
print('BACKGROUND TASK WAKE UP')
await self.sleep(1.0)
if __name__ == '__main__':
app.main()Faust uses a layered abstraction model to decouple message processing from the underlying transport mechanism. This allows agents to work with different types of communication backends.
aiokafka) that handles the actual network communication.Abstraction Hierarchy:
Agent <--> Stream <--> ChannelAgent <--> Stream <--> Topic <--> Transport <--> aiokafkaBecause Topics are highly Kafka-specific, you should subclass Channels if you need to implement different communication means like RabbitMQ, MQTT, or ZeroMQ.
Faust supports a Blueprint pattern for defining reusable web components. A web.Blueprint can contain routes and views, which are then registered to a Faust app with an optional url_prefix.
from faust import web
blueprint = web.Blueprint('user')
@blueprint.route('/', name='list')
class UserListView(web.View):
async def get(self, request: web.Request) -> web.Response:
return self.json({'hello': 'world'})
@blueprint.route('/{username}/', name='detail')
class UserDetailView(web.View):
async def get(self, request: web.Request) -> web.Response:
name = request.match_info['username']
return self.json({'hello': name})
async def post(self, request: web.Request) -> web.Response:
...
async def delete(self, request: web.Request) -> web.Response:
...
# Register the blueprint to an app
blueprint.register(app, url_prefix='/users/')Faust uses an Observer design pattern via Signals. Signals allow you to react to lifecycle events or data events.
Important Rules for Signal Handlers:
**kwargs to ensure backward compatibility when new arguments are added to the signal.on_produce_message, on_configured), use def (not async def).on_partitions_revoked), use async def.app.signal.connect applies to that specific app instance. Connecting to faust.App.signal.connect applies to all app instances.