Skip to main content

Ergon internal task-oriented project bootstrapper

Project description

Ergon Framework โ€” Python SDK

Python 3.10+ License: MIT Code Style: Ruff

The official Python implementation of the Ergon Frameworkโ€”a transaction-first, observability-native execution engine for building high-throughput automation pipelines.


Installation

pip install ergon-framework-python

For local development, install in editable mode:

pip install -e /path/to/ergon-framework/sdks/python

๐Ÿ“– Full Getting Started Guide โ€” Complete setup, project configuration, and task registration


Quick Start

1. Define a Consumer Task

Create a task by inheriting from ConsumerTask and implementing process_transaction:

from typing import Any
from ergon.task.mixins import ConsumerTask
from ergon.connector import Transaction

class OrderProcessor(ConsumerTask):
    """Consumes order transactions and processes them."""

    def process_transaction(self, transaction: Transaction) -> Any:
        # Pure business logicโ€”no I/O, no retries, no protocol details
        order = transaction.payload
        total = order["quantity"] * order["unit_price"]
        return {"order_id": order["id"], "total": total, "status": "processed"}

    def handle_process_success(self, transaction: Transaction, result: Any):
        # Called after successful processing
        self.output_connector.dispatch_transactions([
            Transaction(id=f"out-{transaction.id}", payload=result, metadata={})
        ])

    def handle_process_exception(self, transaction: Transaction, exc: Exception):
        # Called when processing fails (after all retries exhausted)
        self.dlq_connector.dispatch_transactions([transaction])

2. Create a Connector

Connectors bridge external systems to the transaction interface:

from typing import List
from ergon.connector import Connector, Transaction
import uuid

class RabbitMQConnector(Connector):
    def __init__(self, queue_name: str, connection_url: str):
        self.queue_name = queue_name
        # Initialize your RabbitMQ client here
        self.client = create_rabbitmq_client(connection_url)

    def fetch_transactions(self, batch_size: int, **kwargs) -> List[Transaction]:
        messages = self.client.consume(self.queue_name, max_messages=batch_size)
        return [
            Transaction(
                id=str(uuid.uuid4()),
                payload=msg.body,
                metadata={"delivery_tag": msg.delivery_tag, "routing_key": msg.routing_key}
            )
            for msg in messages
        ]

    def dispatch_transactions(self, transactions: List[Transaction], **kwargs):
        for tx in transactions:
            self.client.publish(self.queue_name, tx.payload)

3. Configure and Run

Use TaskConfig to wire everything together:

from ergon.task import TaskConfig, runner, policies
from ergon.connector import ConnectorConfig

# Configure the consumer policy
consumer_policy = policies.ConsumerPolicy()
consumer_policy.name = "consumer"
consumer_policy.loop.concurrency.value = 5      # Process 5 transactions in parallel
consumer_policy.loop.batch.size = 10            # Fetch 10 at a time
consumer_policy.loop.streaming = True           # Keep polling for new messages
consumer_policy.process.retry.max_attempts = 3  # Retry failed processing 3 times
consumer_policy.process.retry.backoff = 1.0     # Start with 1s backoff
consumer_policy.process.retry.backoff_multiplier = 2.0  # Double each retry

# Create the task configuration
config = TaskConfig(
    name="order-processor",
    task=OrderProcessor,
    max_workers=1,
    connectors={
        "input": ConnectorConfig(
            connector=RabbitMQConnector,
            kwargs={"queue_name": "orders", "connection_url": "amqp://localhost"}
        ),
        "output": ConnectorConfig(
            connector=RabbitMQConnector,
            kwargs={"queue_name": "processed-orders", "connection_url": "amqp://localhost"}
        ),
    },
    policies=[consumer_policy],
)

# Run the task
if __name__ == "__main__":
    runner.run(config)

The framework handles the loop, retry logic, telemetry, and graceful shutdown automatically.


The Transaction Object

In the Python SDK, transactions are implemented as immutable Pydantic models:

from ergon.connector import Transaction

# Transactions are created by Connectors when fetching data
tx = Transaction(
    id="tx-001",                           # Unique identifier for tracing
    payload={"order_id": 123, "amount": 99.99},  # The actual data
    metadata={"source": "rabbitmq", "queue": "orders"}  # Contextual info
)

Transactions are frozen (immutable) once created via Pydantic's frozen=True configuration.


Task Classes

The Python SDK provides ready-to-use task classes:

from ergon.task.mixins import (
    ConsumerTask,      # Sync consumer
    ProducerTask,      # Sync producer
    HybridTask,        # Sync consumer + producer
    AsyncConsumerTask, # Async consumer
    AsyncProducerTask, # Async producer
    AsyncHybridTask,   # Async consumer + producer
)

Synchronous Tasks

Best for CPU-bound processing or legacy libraries:

from ergon.task.mixins import ConsumerTask

class SyncProcessor(ConsumerTask):
    def process_transaction(self, transaction):
        # Runs in ThreadPoolExecutor
        return heavy_computation(transaction.payload)
  • Concurrency controlled by policy.loop.concurrency.value
  • Each transaction processed in its own thread
  • Multi-process scaling via max_workers > 1

Asynchronous Tasks

Best for I/O-bound workloads with high concurrency:

from ergon.task.mixins import AsyncConsumerTask

class AsyncProcessor(AsyncConsumerTask):
    async def process_transaction(self, transaction):
        # Runs in asyncio event loop
        result = await self.http_client.post(transaction.payload)
        return result
  • Concurrency controlled by asyncio.Semaphore
  • Thousands of concurrent operations with minimal overhead
  • Perfect for API calls, database queries, network I/O

Dependency Injection

Tasks receive all dependencies automatically via the TaskMeta metaclass:

class EnrichmentTask(ConsumerTask):
    def process_transaction(self, transaction: Transaction) -> Any:
        # Access connectors as self.{name}_connector
        count = self.input_connector.get_transactions_count()
      
        # Access services as self.{name}_service
        enriched = self.openai_service.complete(transaction.payload["text"])
      
        # Access policies as self.{name}_policy
        timeout = self.consumer_policy.loop.timeout
      
        return {"enriched": enriched, "pending": count}

Configure services in TaskConfig:

from ergon.connector import ServiceConfig

config = TaskConfig(
    name="enrichment-task",
    task=EnrichmentTask,
    connectors={"input": input_connector_config},
    services={
        "openai": ServiceConfig(
            service=OpenAIService,
            kwargs={"api_key": "sk-...", "model": "gpt-4"}
        )
    },
    policies=[consumer_policy],
)

Policy Configuration

Policies are created as instances and configured by setting properties directly:

from ergon.task import policies

# Consumer policy with full configuration
policy = policies.ConsumerPolicy()
policy.name = "my-consumer"

# Loop behavior
policy.loop.concurrency.value = 10      # Parallel transaction processing
policy.loop.batch.size = 50             # Transactions per fetch
policy.loop.streaming = True            # Continuous polling mode
policy.loop.timeout = 3600              # Max loop duration (seconds)
policy.loop.limit = 1000                # Max transactions to process

# Empty queue behavior (for streaming mode)
policy.loop.empty_queue.backoff = 1.0
policy.loop.empty_queue.backoff_multiplier = 2.0
policy.loop.empty_queue.backoff_cap = 30.0

# Per-step retry configuration
policy.fetch.retry.max_attempts = 5
policy.fetch.retry.timeout = 30

policy.process.retry.max_attempts = 3
policy.process.retry.backoff = 1.0
policy.process.retry.backoff_multiplier = 2.0
policy.process.retry.backoff_cap = 60.0

policy.success.retry.max_attempts = 2
policy.exception.retry.max_attempts = 2

Observability

The Python SDK integrates OpenTelemetry natively:

from ergon.telemetry import logging, tracing, metrics

config = TaskConfig(
    name="observed-task",
    task=MyTask,
    connectors={...},
    logging=logging.LoggingConfig(
        level="INFO",
        handlers=[
            logging.ConsoleLogHandler(),
            logging.OTLPLogHandler(endpoint="http://collector:4317"),
        ]
    ),
    tracing=tracing.TracingConfig(
        processors=[
            tracing.SpanProcessor(
                processor=tracing.BatchSpanProcessor,
                exporters=[tracing.OTLPSpanExporter(endpoint="http://collector:4317")]
            )
        ]
    ),
    metrics=metrics.MetricsConfig(
        readers=[
            metrics.MetricReader(
                reader=metrics.PeriodicExportingMetricReader,
                exporters=[metrics.OTLPMetricExporter(endpoint="http://collector:4317")]
            )
        ]
    ),
)

Logging

  • Structured JSON logging via python-json-logger
  • Automatic trace_id and span_id injection
  • Multiple handlers: Console, File, RotatingFile, OTLP

Tracing

  • Hierarchical spans: Task โ†’ Batch โ†’ Transaction โ†’ Attempt
  • Full context propagation across async boundaries
  • Export to Jaeger, Tempo, or any OTLP-compatible backend

Metrics

  • Push-based via PeriodicExportingMetricReader
  • Automatic resource attributes (task name, host, PID, execution ID)
  • Export to Prometheus, OTLP, or console

Scaling

Axis Mechanism Configuration
Process ProcessPoolExecutor TaskConfig.max_workers
Thread ThreadPoolExecutor policy.loop.concurrency.value
Async asyncio.Semaphore policy.loop.concurrency.value
Connector External system partitioning Service-specific (consumer groups, shards)
# Multi-process scaling (sync tasks only)
config = TaskConfig(
    name="scaled-task",
    task=MyTask,
    max_workers=4,  # Spawn 4 isolated worker processes
    connectors={...},
)

Project Structure

After scaffolding with ergon init:

my-project/
โ”œโ”€โ”€ main.py                  # Application entry point
โ”œโ”€โ”€ _observability/          # Telemetry infrastructure configs
โ”œโ”€โ”€ connectors/              # Custom connectors and services
โ”‚   โ””โ”€โ”€ {connector_name}/    # One submodule per connector
โ”‚       โ”œโ”€โ”€ connector.py     # Transaction interface
โ”‚       โ””โ”€โ”€ service.py       # Protocol mechanics
โ””โ”€โ”€ tasks/                   # Task definitions and shared modules
    โ”œโ”€โ”€ settings.py          # Global configs (connectors, services, telemetry)
    โ”œโ”€โ”€ constants.py         # Global constants and enums
    โ”œโ”€โ”€ schemas.py           # Shared Pydantic models
    โ”œโ”€โ”€ exceptions.py        # Shared exception classes
    โ”œโ”€โ”€ helpers.py           # Shared utility functions
    โ””โ”€โ”€ {task_name}/         # Per-task submodule
        โ”œโ”€โ”€ task.py          # Task implementation
        โ”œโ”€โ”€ config.py        # TaskConfig definition
        โ”œโ”€โ”€ schemas.py       # Task-specific models
        โ”œโ”€โ”€ exceptions.py    # Task-specific exceptions
        โ””โ”€โ”€ helpers.py       # Task-specific utilities

๐Ÿ“– Full Project Structure Guide โ€” Detailed documentation on organizing connectors, tasks, and shared modules.


Documentation

Python SDK Guides

Framework Concepts


License

This project is licensed under the MIT License.


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

ergon_framework_python-0.1.3.tar.gz (83.6 kB view details)

Uploaded Source

Built Distribution

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

ergon_framework_python-0.1.3-py3-none-any.whl (108.0 kB view details)

Uploaded Python 3

File details

Details for the file ergon_framework_python-0.1.3.tar.gz.

File metadata

File hashes

Hashes for ergon_framework_python-0.1.3.tar.gz
Algorithm Hash digest
SHA256 4e87dbaf9cb9370d3e76ef81724b2218b6442a83031274137092b1ae8cb48d05
MD5 17c5ca00dd8f61d5e3a24d2da9f9f2ee
BLAKE2b-256 c64a05609fb0c6e6fee1bb225759a4a1273247789696d1f9c1439d796a67799e

See more details on using hashes here.

File details

Details for the file ergon_framework_python-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for ergon_framework_python-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 6a09c100dae1b81ce3b9d6d3ef0167038ec068fc9661de687fbd0719e7407eae
MD5 77fd9e3899aae43c6c6fa3c48821a72d
BLAKE2b-256 7507522702e854efb010e7fa71ca7f5590e536949b31184f8caa86900f042f25

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