Skip to main content

Spring-inspired bootstrap package for Python

Project description

alt-python-boot-lib

Language Python License: MIT

Application bootstrap for the alt-python framework. Provides the one-call Boot.boot() entry point, the startup banner, and the MiddlewarePipeline used by all HTTP and serverless adapters.

Inspired by Spring Boot's SpringApplication.run() auto-configuration and application context lifecycle.

Part of the alt-python/boot monorepo.

Install

uv add alt-python-boot-lib   # or: pip install alt-python-boot-lib

Requires Python 3.12+, alt-python-config, alt-python-logger, and alt-python-cdi.

Quick Start

from boot import Boot
from cdi import Context, Singleton


class GreetingService:
    def __init__(self):
        self.config = None   # CDI-autowired

    def greet(self, name: str) -> str:
        return f"Hello, {name}!"


class Application:
    def __init__(self):
        self.greeting_service = None  # CDI-autowired

    def run(self):
        print(self.greeting_service.greet("world"))


Boot.boot({
    'contexts': [
        Context([Singleton(GreetingService), Singleton(Application)])
    ]
})

Boot.boot() resolves config from the current directory, prints the startup banner, and calls application.run() on the Application bean.

API

from boot import Boot, MiddlewarePipeline, RequestLoggerMiddleware, ErrorHandlerMiddleware, NotFoundMiddleware

Boot

Boot.boot(options=None)

Bootstrap the application. Loads config, wires the CDI container, prints the banner, and calls run() on the Application bean.

Boot.boot({
    'contexts': [Context([Singleton(MyService), Singleton(Application)])]
})

Options:

Key Type Description
contexts list[Context] CDI contexts to wire
config config-like Config source (default: auto-discovered via ConfigFactory)

Returns the ApplicationContext after start() completes.

Boot.test(options=None)

Test bootstrap. Suppresses the startup banner and captures log output in-memory. Accepts the same options as Boot.boot().

def test_my_service():
    ctx = Boot.test({
        'contexts': [Context([Singleton(MyService)])]
    })
    svc = ctx.get('my_service')
    assert svc.greet("world") == "Hello, world!"

Boot.root(name, default=None)

Read a value from the global boot context.

config = Boot.root('config')
logger_factory = Boot.root('logger_factory')

MiddlewarePipeline

The CDI middleware pipeline — the Python equivalent of Spring Security's filter chain, applied uniformly across all HTTP and serverless adapters.

MiddlewarePipeline.compose(middlewares, final_handler)

Composes an ordered list of middleware instances and a final handler into a single async callable.

pipeline = MiddlewarePipeline.compose(middlewares, dispatch)
response = await pipeline(request)

compose() is called once per request. Middleware run in order (lowest order value first). Each middleware calls await next_fn(request) to pass control down the chain; returning without calling next_fn short-circuits the pipeline.

MiddlewarePipeline.collect(ctx)

Collects all CDI components that declare __middleware__ = {"order": N} from an ApplicationContext, filters out uninstantiated entries, and returns them sorted by order.

middlewares = MiddlewarePipeline.collect(app_ctx)

Writing Middleware

A middleware component is a plain CDI class with __middleware__ = {"order": N} as a class attribute and an async def handle(self, request, next_fn) method. Lower order values run first (outermost).

class AuthMiddleware:
    __middleware__ = {"order": 5}

    def __init__(self):
        self.logger = None  # CDI-autowired

    async def handle(self, request, next_fn):
        token = request.get("headers", {}).get("authorization", "").removeprefix("Bearer ")
        if not token:
            return {"statusCode": 401, "body": {"error": "Unauthorized"}}
        return await next_fn({**request, "user": {"token": token}})

Register it in the CDI context — no extra wiring needed:

from boot_aws_lambda import lambda_starter
from cdi import Context, Singleton

context = Context([
    *lambda_starter(),
    Singleton(AuthMiddleware),   # auto-detected via __middleware__
    Singleton(TodoController),
])

Normalised Request Shape

All adapters present the same request dict to middleware:

{
    "method":  "GET",
    "path":    "/todos/42",
    "params":  {"id": "42"},
    "query":   {"page": "1"},
    "headers": {...},
    "body":    {...},
    "ctx":     application_context,
}

Middleware written against this shape works identically across Lambda, Azure Functions, GCP Cloud Functions, FastAPI, and Flask adapters.


Built-in Middleware

Every *_starter() function registers these three middleware components:

Class Order Behaviour
RequestLoggerMiddleware 10 Logs METHOD /path → status (Xms) at verbose level
ErrorHandlerMiddleware 20 Converts unhandled exceptions to JSON error responses
NotFoundMiddleware 30 Returns 404 when no route matches

print_banner(config=None, logger=None)

Prints the startup banner to stdout. Called automatically by Boot.boot(). Pass config with boot.banner-mode: off to suppress:

from boot import print_banner

print_banner()          # prints the banner
print_banner(config)    # suppressed if boot.banner-mode == "off"

Testing

Use Boot.test() to suppress the banner and capture logs during tests:

from boot import Boot
from cdi import Context, Singleton


def test_greet():
    ctx = Boot.test({
        'contexts': [Context([Singleton(GreetingService)])]
    })
    assert ctx.get('greeting_service').greet("world") == "Hello, world!"

Boot.test() wraps the config in a PropertySourceChain with boot.banner-mode: off prepended, and uses CachingLoggerFactory so log output is captured in-memory rather than printed.

Spring Attribution

The Boot lifecycle maps to Spring Boot's SpringApplication.run():

Spring alt-python-boot
SpringApplication.run() Boot.boot()
ApplicationContext.refresh() + start() ApplicationContext.start()
@SpringBootApplication banner print_banner()
Spring Security FilterChain MiddlewarePipeline
Filter.doFilter(req, res, chain) handle(request, next_fn)

License

MIT

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

alt_python_boot_lib-1.1.1.tar.gz (6.6 kB view details)

Uploaded Source

Built Distribution

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

alt_python_boot_lib-1.1.1-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

Details for the file alt_python_boot_lib-1.1.1.tar.gz.

File metadata

  • Download URL: alt_python_boot_lib-1.1.1.tar.gz
  • Upload date:
  • Size: 6.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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 alt_python_boot_lib-1.1.1.tar.gz
Algorithm Hash digest
SHA256 d2204b43ce2dba35d0b578ee36d01a4c9cc3ab75151b8cb4e99ab991b6c325b1
MD5 9c5d1b302d26e232e562f66cc322497a
BLAKE2b-256 2d4af05663acdb8f93add863a0219cce2b7b955e3e7eb1f9d918220ebca9d050

See more details on using hashes here.

File details

Details for the file alt_python_boot_lib-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: alt_python_boot_lib-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 8.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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 alt_python_boot_lib-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5e852201c14370622e290507f75b7eb2c071258427dbca11ef4f9ee5eba5f988
MD5 7ed16017ba16ce3de2ecdffee833b4d1
BLAKE2b-256 c395c99b02a9f64b06637a49868fa402c1888c67dc70a406885e6ffe3184d0b2

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