Skip to main content

Add your description here

Project description

erns-shared

Shared Python utilities for AWS + AI projects. Provides reusable Domain-Driven Design (DDD) primitives, AWS service wrappers, and Lambda infrastructure patterns used across Lab products and Domenxa.

Only code that has been reused in 2+ real projects enters this library.

Installation

uv add erns-shared
# or
pip install erns-shared

Optional extras

# SSE streaming for FastAPI
uv add erns-shared[http]

# AI providers — pick what you need
uv add erns-shared[ai-anthropic]
uv add erns-shared[ai-openai]
uv add erns-shared[ai-google]
uv add erns-shared[ai-ollama]

# All AI providers at once
uv add erns-shared[ai]

Modules

Module Status Description
erns_shared.ddd Available DDD primitives, DynamoDB UoW, EventBridge publisher
erns_shared.aws Available S3, DynamoDB query helpers, SSM Parameter Store, Lambda Powertools
erns_shared.ai Available Multi-provider AI client with cost tracking and error handling
erns_shared.http Available SSE streaming response for FastAPI
erns_shared.parsers Coming soon PDF extraction, CSV parsing

erns_shared.ddd

Battle-tested building blocks for DDD-style Python services on AWS.

Define a domain model

from erns_shared.ddd import (
    EntityId, DomainAggregate, DomainEvent, Command,
    ValueObject, EpochTime, update_last_update_date,
)

class OrderId(EntityId):
    value: str

class OrderPlaced(DomainEvent):
    domain_name: str = "orders"
    order_id: str
    total: float

class Order(DomainAggregate):
    id: OrderId
    total: float
    status: str = "pending"

    @update_last_update_date
    def place(self) -> None:
        self.status = "placed"
        self.add_event(OrderPlaced(order_id=self.id._key(), total=self.total))

Persist with DynamoDB (single-table) + publish to EventBridge

from erns_shared.ddd.adapters.unit_of_work import DynamoDbUnitOfWork
from erns_shared.ddd.adapters.persistence.dynamodb_repository import DynamoDbRepository

uow = DynamoDbUnitOfWork()
repo = DynamoDbRepository(session=uow.session, table_name="my-table", entity_type=Order)

order = Order(id=OrderId(value="ord-123"), total=99.99)
order.place()

with uow.transaction():
    repo.put(order)
    uow.publish_events(order.pull_events())

Structured Lambda logger

from erns_shared.aws import get_lambda_logger

logger = get_lambda_logger()
logger.info("Order placed")

Set LOG_LEVEL=DEBUG env var to enable debug logging.


erns_shared.aws

S3Client

from erns_shared.aws import S3Client

s3 = S3Client()

s3.upload("my-bucket", "docs/report.pdf", body=pdf_bytes, content_type="application/pdf")
data = s3.download("my-bucket", "docs/report.pdf")

if s3.key_exists("my-bucket", "docs/report.pdf"):
    url = s3.presigned_get_url("my-bucket", "docs/report.pdf", expiration=3600)

for key in s3.list_keys("my-bucket", prefix="docs/"):
    print(key)

DynamoDBTable

High-level query helpers for the single-table pattern. Works with plain dicts — complements the DDD persistence layer.

from boto3.dynamodb.conditions import Attr, Key
from erns_shared.aws import DynamoDBTable

table = DynamoDBTable("my-table")

# query — flexible sort key conditions
orders = table.query_by_pk("pk", "user#1", sk_condition=Key("sk").begins_with("order#"))
recent  = table.query_by_pk("pk", "user#1", sk_condition=Key("sk").between("order#2024", "order#2025"))

# query + filter on non-key attributes
shipped = table.query_by_pk(
    "pk", "user#1",
    sk_condition=Key("sk").begins_with("order#"),
    filter_expression=Attr("status").eq("shipped"),
)

# convenience prefix helper
orders = table.query_by_pk_sk_prefix("pk", "user#1", "sk", "order#")

# scan the full table
active = table.scan(filter_expression=Attr("active").eq(True))

# batch read (auto-chunks at 100)
items = table.batch_get(keys=[{"pk": "user#1", "sk": "profile"}])

# single-item writes with optional condition + return previous value
old = table.put_item(item, return_values="ALL_OLD")
table.put_item(item, condition=Attr("version").eq(3))
table.delete_item(key, condition=Attr("status").eq("pending"))

Batch writer — not atomic, auto-chunks at 25 per call

with table.batch_writer() as w:
    w.put({"pk": "user#1", "sk": "profile", "name": "Alice"})
    w.delete({"pk": "user#old", "sk": "profile"})
# flushes on exit, clears ops on exception

Transaction writer — fully atomic, max 100 operations

with table.transaction_writer() as w:
    w.put({"pk": "order#1", "sk": "meta", "status": "placed"})
    w.put({"pk": "order#1", "sk": "meta"}, condition=Attr("version").eq(2))
    w.delete({"pk": "draft#1", "sk": "meta"})
# all land or none do

SSMClient

from erns_shared.aws import SSMClient

ssm = SSMClient()

# single parameter — cached by default
db_url = ssm.get_parameter("/app/db_url")

# load an entire path into cache in one call
params = ssm.get_parameters_by_path("/app/prod")

# write
ssm.put_parameter("/app/feature_flag", "true", param_type="String", overwrite=True)

# cache management
ssm.invalidate_cache("/app/db_url")  # single key
ssm.invalidate_cache()               # full flush

Lambda Powertools

from erns_shared.aws import build_powertools, LambdaContext

logger, tracer, metrics = build_powertools(service="order-service", namespace="MyApp")
# falls back to POWERTOOLS_SERVICE_NAME / POWERTOOLS_METRICS_NAMESPACE env vars

@tracer.capture_lambda_handler
@logger.inject_lambda_context
def handler(event: dict, context: LambdaContext) -> dict:
    logger.info("Handling event")
    return {"statusCode": 200}

erns_shared.ai

Multi-provider AI client with a unified interface, cost tracking, and provider-agnostic error handling.

Requires: uv add erns-shared[ai-anthropic] (or whichever provider you use).

One-shot completion

from erns_shared.ai import get_ai_client, ProviderError

client = get_ai_client(provider="anthropic", model="claude-sonnet-4-6")
# API key falls back to ANTHROPIC_API_KEY env var if not passed explicitly

try:
    response = client.complete(
        system="You are a helpful assistant.",
        user="Summarise this document in 3 bullet points.",
        max_tokens=1024,
    )
    print(response.text)
    print(f"Tokens: {response.input_tokens} in / {response.output_tokens} out")
    print(f"Cost: ${response.estimated_cost_usd:.6f}")
except ProviderError as e:
    # e.status_code, e.retryable, e.public_message
    print(e.public_message)

Streaming (SSE)

stream() is an async generator that yields text chunks as they arrive — designed to feed directly into sse_stream():

from erns_shared.ai import get_ai_client
from erns_shared.http import SSEEvent, sse_stream
from fastapi import FastAPI

app = FastAPI()
client = get_ai_client(provider="anthropic", model="claude-sonnet-4-6")

@app.post("/chat")
async def chat(prompt: str):
    async def generate():
        async for chunk in client.stream(system="You are helpful.", user=prompt):
            yield SSEEvent(data={"text": chunk}, event="delta")
        yield SSEEvent(data="[DONE]", event="done")

    return sse_stream(generate())

Supported providers: anthropic, openai, google, ollama.


erns_shared.http

SSE streaming for FastAPI

Requires: uv add erns-shared[http].

from fastapi import FastAPI
from erns_shared.http import SSEEvent, sse_stream

app = FastAPI()

@app.post("/chat")
async def chat(prompt: str):
    async def generate():
        async for chunk in some_ai_stream(prompt):
            yield SSEEvent(data={"text": chunk}, event="delta")
        yield SSEEvent(data="[DONE]", event="done")

    return sse_stream(generate())

SSEEvent fields: data (auto-serialized to JSON for dicts/lists), event, id, retry.


Requirements

  • Python 3.11+
  • pydantic >= 2.0
  • pydantic-settings >= 2.0
  • boto3 >= 1.34
  • backoff >= 2.0
  • aws-lambda-powertools >= 2.0

Contributing

See GUIDE.md for setup instructions, project structure, conventions, and the release workflow.

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

erns_shared-0.1.3.tar.gz (19.6 kB view details)

Uploaded Source

Built Distribution

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

erns_shared-0.1.3-py3-none-any.whl (27.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: erns_shared-0.1.3.tar.gz
  • Upload date:
  • Size: 19.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for erns_shared-0.1.3.tar.gz
Algorithm Hash digest
SHA256 ac7ea77cf33b766606167af3d4cfa46425345d8731dab63a8968a496b5f9c545
MD5 28897895384a43ee3ebe4d6ab5ce80f7
BLAKE2b-256 cb95ff38015b1e8d554bef02cfebbf30ea7ff3fc4f65350c035b6aa3c2fe2085

See more details on using hashes here.

File details

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

File metadata

  • Download URL: erns_shared-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 27.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for erns_shared-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7ddaaf8ad11108e7625d3ef6a7f836c3cf3bf61858f7bcb8e994bfa7a6b97a7d
MD5 dd8b3d66443c5d31a55bc83981ceb302
BLAKE2b-256 42dc67144017e8883d449295a7b402f0107cecc107562578c6f580fe1eed1a20

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