Skip to main content

Opinionated framework for async web services

Project description

Pepelats

Opinionated framework for async web services in Python — compose on a fluent builder with DI, configuration, observability, and lifecycle built in.

Python Version Status License


Apps compose on WebHostBuilder or FastAPIHostBuilder: register services, mount routes or APIs, run background workers. Starlette is the HTTP layer; FastAPI is optional.

Features

  • Compose and run an async web service from a single builder
  • Inject services and configuration into handlers and constructors
  • Load typed settings from TOML and environment variables
  • Structured logging, tracing, and metrics
  • Run background work alongside the HTTP server
  • Optional FastAPI for REST APIs and OpenAPI (pepelats[fastapi])

Installation

uv add pepelats
uv add "pepelats[fastapi]"

Configuration

Pass a config_dir to the builder. Pepelats loads appsettings.toml from that directory (and merges appsettings.local.toml when present). Set the active environment with the ENVIRONMENT variable (defaults to default).

config/appsettings.toml:

[default]
environment = "local"

[default.service]
service_name = "my-service"
service_version = "1.0.0"

[default.logging]
log_level = "INFO"
sinks = ["console"]

[default.logging.console]
json_logs = false

[default.host]
bind = "127.0.0.1"
port = 8000

[default.observability]
enabled = false
otlp_endpoint = ""

[default.greeting]
punctuation = "!"
shout = false

service, logging, host, and observability are required for host bootstrap. enabled is the OTLP export switch: false runs local-only (console/file logs, no collector); true requires a non-empty otlp_endpoint (an empty one is a config error, not a silent opt-out). export_timeout_seconds (default 3) caps every OTLP export, including the final flush on shutdown — so an unreachable collector can't stall teardown. The flush runs to completion before the process exits, so shutdown leaves no straggling export logs.

App-specific sections bind to Pydantic models. The section name defaults to the model name in snake_case (GreetingConfiggreeting). Register with add_configuration; services receive the config through constructor injection:

from pydantic import BaseModel

from pepelats.configuration import Configuration
from pepelats.dependency_injection import ServiceCollection


class GreetingConfig(BaseModel):
    punctuation: str
    shout: bool


class HealthService:
    def __init__(self, config: GreetingConfig) -> None:
        self._config = config

    def status(self) -> str:
        message = "ok"
        if self._config.shout:
            message = message.upper()
        return message + self._config.punctuation


def register(services: ServiceCollection, configuration: Configuration) -> None:
    services.add_configuration(GreetingConfig)
    services.add_singleton(HealthService)

Examples

Runnable full versions: tests/examples/di_showcase_generic.py (Starlette) and tests/examples/di_showcase.py (FastAPI).

Generic host

from pathlib import Path

from pydantic import BaseModel
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route

from pepelats.configuration import Configuration
from pepelats.dependency_injection import ServiceCollection
from pepelats.hosting import HostPipeline, WebHostBuilder, request_services
from pepelats.hosting.web_host import WebHost


class GreetingConfig(BaseModel):
    punctuation: str
    shout: bool


class HealthService:
    def __init__(self, config: GreetingConfig) -> None:
        self._config = config

    def status(self) -> str:
        message = "ok"
        if self._config.shout:
            message = message.upper()
        return message + self._config.punctuation


async def health(request: Request) -> JSONResponse:
    services = request_services(request)
    service = await services.get(HealthService)
    return JSONResponse({"status": service.status()})


def register(services: ServiceCollection, configuration: Configuration) -> None:
    services.add_configuration(GreetingConfig)
    services.add_singleton(HealthService)


def map_routes(pipeline: HostPipeline) -> None:
    pipeline.map(Route("/health", health))


def build_host(config_dir: Path) -> WebHost:
    return (
        WebHostBuilder.create(config_dir=config_dir)
        .configure_services(register)
        .configure_pipeline(map_routes)
        .build()
    )


if __name__ == "__main__":
    build_host(Path("config")).run()

FastAPI

from pathlib import Path

from fastapi import APIRouter, FastAPI
from pydantic import BaseModel

from pepelats.configuration import Configuration
from pepelats.dependency_injection import Inject, ServiceCollection
from pepelats.hosting.web_host import WebHost
from pepelats.integrations.fastapi import FastAPIHostBuilder, InjectRoute


class GreetingConfig(BaseModel):
    punctuation: str
    shout: bool


class HealthService:
    def __init__(self, config: GreetingConfig) -> None:
        self._config = config

    def status(self) -> str:
        message = "ok"
        if self._config.shout:
            message = message.upper()
        return message + self._config.punctuation


router = APIRouter(route_class=InjectRoute)


@router.get("/health")
async def health(service: Inject[HealthService]) -> dict[str, str]:
    return {"status": service.status()}


def register(services: ServiceCollection, configuration: Configuration) -> None:
    services.add_configuration(GreetingConfig)
    services.add_singleton(HealthService)


def configure_api(app: FastAPI) -> None:
    app.include_router(router)


def build_host(config_dir: Path) -> WebHost:
    return (
        FastAPIHostBuilder.create(config_dir=config_dir)
        .configure_services(register)
        .configure_api(configure_api)
        .build()
    )


if __name__ == "__main__":
    build_host(Path("config")).run()

Dependencies

Pepelats uses:

Optional:

  • FastAPI — API layer (pepelats[fastapi])

License

Apache-2.0 — see 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

pepelats-0.1.0a2.tar.gz (1.7 MB view details)

Uploaded Source

Built Distribution

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

pepelats-0.1.0a2-py3-none-any.whl (42.6 kB view details)

Uploaded Python 3

File details

Details for the file pepelats-0.1.0a2.tar.gz.

File metadata

  • Download URL: pepelats-0.1.0a2.tar.gz
  • Upload date:
  • Size: 1.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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 pepelats-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 af51f4186c13a0b8324a5396aebe0a9ae0adff0f14ef75c428739490f47bb947
MD5 31ade423ad6a1011384d6119073aa9fa
BLAKE2b-256 57ddc50935989b0b0d4f895d6723dc217b53f423e4fb21854db755658db77850

See more details on using hashes here.

File details

Details for the file pepelats-0.1.0a2-py3-none-any.whl.

File metadata

  • Download URL: pepelats-0.1.0a2-py3-none-any.whl
  • Upload date:
  • Size: 42.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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 pepelats-0.1.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 6550e29578e3fcdcc6f4b9c855ef99a8103e5bc7515e46f72010b4020386f726
MD5 eabb373ede897b0b262ccecf7580bc26
BLAKE2b-256 88f858e81f212f25dd034912bc976c6ec4abf6241362295160ed53e9a7164358

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