Skip to main content

A decorator-based library for handling Google Cloud Pub/Sub messages with FastAPI integration, inspired by Micronaut's @PubSubListener

Project description

GCP PubSub Events

CI codecov Python 3.8+ License: MIT

A decorator-based Python library for handling Google Cloud Pub/Sub messages, inspired by Micronaut's @PubSubListener pattern.

๐Ÿš€ Features

  • Decorator-based: Clean, annotation-style API similar to Micronaut
  • Context Manager Support: Proper lifecycle management with with and async with syntax
  • FastAPI Integration: Seamless integration with FastAPI using lifespan events
  • Automatic registration: Classes marked with @pubsub_listener are automatically registered
  • Type-safe event handling: Support for Pydantic models with automatic validation
  • Async support: Handlers can be async or sync
  • Error handling: Proper ack/nack based on handler success/failure
  • Thread management: Automatic background thread handling with graceful shutdown
  • Modular design: Well-organized package structure for maintainability

๐Ÿ“ฆ Installation

pip install gcp-pubsub-events

Or install from source:

git clone <repository-url>
cd gcp-pubsub-events
pip install -e .

๐Ÿ—๏ธ Project Structure

gcp_pubsub_events/
โ”œโ”€โ”€ __init__.py              # Main package exports
โ”œโ”€โ”€ core/                    # Core functionality
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ acknowledgement.py   # Message acknowledgment handling
โ”‚   โ”œโ”€โ”€ client.py           # Main PubSub client
โ”‚   โ””โ”€โ”€ registry.py         # Listener registry management
โ”œโ”€โ”€ decorators/             # Decorator implementations
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ listener.py         # @pubsub_listener decorator
โ”‚   โ””โ”€โ”€ subscription.py     # @subscription decorator
โ”œโ”€โ”€ utils/                  # Utility functions
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ””โ”€โ”€ serialization.py    # Event serialization utilities
โ””โ”€โ”€ exceptions/             # Custom exceptions
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ base.py
    โ”œโ”€โ”€ serialization.py
    โ””โ”€โ”€ subscription.py

๐Ÿš€ Quick Start

1. Define Event Classes

from pydantic import BaseModel, Field, field_validator

class RegistrationEvent(BaseModel):
    email: str = Field(..., description="User's email address")
    user_id: str = Field(..., description="Unique user identifier")
    timestamp: datetime = Field(default_factory=datetime.now)
    
    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email format')
        return v.lower()

2. Create a Listener Service

from gcp_pubsub_events import pubsub_listener, subscription, Acknowledgement

@pubsub_listener
class UserEventService:
    def __init__(self, user_service):
        self.user_service = user_service
    
    @subscription("user.registered", RegistrationEvent)
    async def on_user_registered(self, event: RegistrationEvent, acknowledgement: Acknowledgement):
        try:
            await self.user_service.create_profile(event.email, event.user_id)
            acknowledgement.ack()
            print(f"User registered: {event.email}")
        except Exception as error:
            acknowledgement.nack()
            print(f"Error: {error}")

3. Start Listening

Option A: Context Manager (Recommended)

from gcp_pubsub_events import pubsub_manager

# Initialize your services
user_service = UserService()
user_event_service = UserEventService(user_service)

# Use context manager for automatic cleanup
with pubsub_manager("your-gcp-project-id") as manager:
    print("PubSub listener started. Press Ctrl+C to stop.")
    # Your application runs here
    # Cleanup happens automatically when exiting the context

Option B: FastAPI Integration

from contextlib import asynccontextmanager
from fastapi import FastAPI
from gcp_pubsub_events import async_pubsub_manager

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: Initialize PubSub
    async with async_pubsub_manager("your-gcp-project-id") as manager:
        app.state.pubsub = manager
        yield
    # Shutdown: Automatic cleanup

app = FastAPI(lifespan=lifespan)

@app.get("/")
def read_root():
    return {"status": "running"}

Option C: Manual Management

from gcp_pubsub_events import create_pubsub_app

# Initialize your services
user_service = UserService()
user_event_service = UserEventService(user_service)

# Manual management (not recommended for production)
client = create_pubsub_app("your-gcp-project-id")
try:
    client.start_listening()
except KeyboardInterrupt:
    client.stop_listening()

๐Ÿ“š API Reference

Core Components

@pubsub_listener

Class decorator that marks a class as a PubSub listener. Instances are automatically registered.

@subscription(subscription_name, event_type=None)

Method decorator that marks a method as a subscription handler.

Parameters:

  • subscription_name: The GCP Pub/Sub subscription name
  • event_type: Optional event class for automatic deserialization

Acknowledgement

Handles message acknowledgement.

Methods:

  • ack(): Acknowledge successful processing
  • nack(): Negative acknowledge (mark as failed)
  • acknowledged (property): Check if message was acknowledged

PubSubClient

Main client for managing subscriptions.

Methods:

  • start_listening(timeout=None): Start listening to all registered subscriptions
  • stop_listening(): Stop listening

PubSubManager (Recommended)

Enhanced manager with context manager support for proper lifecycle management.

Methods:

  • start(): Start the PubSub listener in a background thread
  • stop(timeout=10.0): Stop listening with optional timeout
  • is_running (property): Check if manager is currently running

Context Manager Support:

# Sync context manager
with PubSubManager("project-id") as manager:
    # Your code here
    pass

# Async context manager  
async with PubSubManager("project-id") as manager:
    # Your async code here
    pass

Factory Functions

create_pubsub_app(project_id, max_workers=10, max_messages=100)

Create and configure a PubSub application (legacy approach).

pubsub_manager(project_id, max_workers=5, max_messages=100, **flow_control_settings)

Context manager for PubSub operations.

async_pubsub_manager(project_id, max_workers=5, max_messages=100, **flow_control_settings)

Async context manager for PubSub operations (ideal for FastAPI).

๐Ÿ”ง Advanced Usage

Custom Event Validation

from pydantic import BaseModel, Field, field_validator

class PaymentEvent(BaseModel):
    amount: float = Field(..., gt=0)
    currency: str = Field(..., pattern="^[A-Z]{3}$")
    user_id: str
    
    @field_validator('amount')
    @classmethod
    def validate_amount(cls, v):
        return round(v, 2)  # Round to 2 decimal places

Error Handling

The library automatically handles acknowledgements based on handler success:

  • If handler completes without exception: ack() is called
  • If handler raises exception: nack() is called
  • Manual acknowledgement is also supported

Sync and Async Handlers

@pubsub_listener
class EventService:
    @subscription("sync.topic")
    def sync_handler(self, event, acknowledgement):
        # Synchronous processing
        pass

    @subscription("async.topic")
    async def async_handler(self, event, acknowledgement):
        # Asynchronous processing
        await some_async_operation()

๐Ÿงช Testing

The library includes comprehensive testing support with the PubSub emulator:

# Install development dependencies
pip install -e .[dev]

# Start the emulator
gcloud beta emulators pubsub start --host-port=localhost:8085

# Set environment variable
export PUBSUB_EMULATOR_HOST=localhost:8085

# Run tests
pytest tests/ -v

# Run tests with coverage
pytest tests/ --cov=gcp_pubsub_events --cov-report=html

Test Coverage

We maintain high test coverage with comprehensive unit, integration, and end-to-end tests:

  • Unit tests: Fast, isolated component tests
  • Integration tests: Real PubSub emulator integration
  • E2E tests: Complete workflow scenarios
  • Performance tests: Throughput and latency benchmarks

Coverage reports are automatically generated and uploaded to Codecov.

๐Ÿ“– Examples

  • Basic Example: examples/basic_example.py - Simple usage with Pydantic models
  • Advanced Example: examples/advanced_example.py - Complex validation and multiple event types

๐Ÿ› ๏ธ Development

Setup Development Environment

git clone <repository-url>
cd gcp-pubsub-events
python -m venv venv
source venv/bin/activate  # or `venv\Scripts\activate` on Windows
pip install -e ".[dev]"

Running Tests

# Install test dependencies
pip install pytest pytest-asyncio

# Run tests
pytest

๐Ÿ“‹ Requirements

  • Python 3.7+
  • google-cloud-pubsub>=2.0.0
  • pydantic>=2.0.0

๐Ÿค Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

๐Ÿ“„ License

MIT License - see LICENSE file for details.

๐Ÿ”— Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gcp_pubsub_events-1.0.1.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gcp_pubsub_events-1.0.1-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

Details for the file gcp_pubsub_events-1.0.1.tar.gz.

File metadata

  • Download URL: gcp_pubsub_events-1.0.1.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.17

File hashes

Hashes for gcp_pubsub_events-1.0.1.tar.gz
Algorithm Hash digest
SHA256 8feb40b88703910bba7e298d9425cc465a4b7198c1ddedfa0aa70e21f7ba87d0
MD5 2e490433647b520e4d655f73c4572809
BLAKE2b-256 5623193cb6a7451c28b8bf3962302584423887874b4e4954f3a574e88cbfa45e

See more details on using hashes here.

File details

Details for the file gcp_pubsub_events-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for gcp_pubsub_events-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 36f3f01cf53d266d93b7af3854995e8e5ef37e0595cb685d7cd58c6053c75c61
MD5 834b962f687956fcfc9aaccea6503df3
BLAKE2b-256 17ebfed3b176705607381e3828c8282b3f37c61e94d988308625207e6f5cd806

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page