Skip to main content

Spring style dependency injection for python

Project description

AppCtx

Spring-style dependency injection for Python

Python Version License PyPI Version

Overview

AppCtx is a lightweight dependency injection (DI) container for Python, inspired by the Spring Framework. It uses decorator-based bean registration and auto-wiring via type annotations to manage dependencies in a clean, testable way.

Python Requirements: 3.8+ | License: MIT

Features

  • 🚀 Decorator-based registration@bean / @component to register, @post_construct for lifecycle hooks
  • 🔄 Auto-wiring — Dependencies resolved from type annotations automatically
  • 🔍 Circular dependency detection — Caught at startup, not runtime
  • 📦 Lightweight — Zero mandatory dependencies beyond stdlib
  • 🐍 Pythonic API — Feels natural in Python, not a Java port
  • 🏗️ Multiple contexts — Isolated containers for different scopes

Installation

pip install appctx

Quick Start

1. Define and register beans

Use @bean on functions that create your objects. Dependencies are declared via type annotations:

from appctx import bean, get_bean, refresh

class DatabaseService:
    def __init__(self, connection_string: str = "sqlite:///default.db"):
        self.connection_string = connection_string

    def connect(self):
        return f"Connected to {self.connection_string}"

class UserService:
    def __init__(self, db: DatabaseService):
        self.db = db

    def get_user(self, user_id: int):
        return f"User {user_id} from {self.db.connect()}"

# Register beans
@bean
def database_service():
    return DatabaseService("postgresql://localhost/myapp")

@bean
def user_service(db: DatabaseService):  # Auto-injected by type
    return UserService(db)

2. Initialize and use

# Initialize the container — resolves all dependencies
refresh()

# Retrieve the root application object (the only place you should call get_bean)
user_svc = get_bean(UserService)
print(user_svc.get_user(123))

Note: get_bean() is only needed at the entry point to bootstrap your application. All other dependencies are injected automatically via constructor or function parameters — just like Spring.

Class-style beans

Decorate classes directly — the container instantiates them with resolved dependencies:

from appctx import bean, get_bean, refresh

@bean
class EmailService:
    def __init__(self):
        self.server = "smtp.example.com"
    
    def send_email(self, to: str, subject: str):
        return f"Email sent to {to} via {self.server}"

@bean
class NotificationService:
    def __init__(self, email: EmailService):
        self.email = email
    
    def notify(self, user: str, message: str):
        return self.email.send_email(user, "Notification", message)

refresh()

# Entry point — the only place you should call get_bean
notification_svc = get_bean(NotificationService)
print(notification_svc.notify("user@example.com", "Hello World!"))

Post-construct lifecycle

Use @post_construct to run initialization logic after all beans are created:

from appctx import post_construct

class DatabaseService:
    def __init__(self):
        self.connection = None

    @post_construct
    def init(self):
        self.connection = create_connection()  # Called automatically after construction

@bean
def database_service():
    return DatabaseService()

Note: @post_construct methods run after all beans are created, so you can safely reference other beans during initialization.

How It Works

  1. Bean Registration: Use the @bean decorator to register functions/classes that create beans
  2. Auto-wiring: Dependencies are resolved from type annotations and injected automatically — no manual get_bean() calls needed
  3. Container Initialization: Call refresh() to instantiate all beans in dependency order
  4. Entry Point: Use get_bean() once at the application root to retrieve the assembled object graph

Core Concepts

Concept Description
Bean An object managed by the container
ApplicationContext The DI container that holds and wires beans
@bean Decorator that registers a function/class as a bean factory (alias: @component)
refresh() Initializes the container, instantiates all beans in dependency order
get_bean(T) Retrieve a bean by type or name (low-level; prefer auto-wiring)
get_beans(T) Retrieve all beans of a given type (low-level; prefer auto-wiring)

Dependency Resolution Rules

  1. Positional arguments → resolved by type annotation
  2. Keyword-only arguments (*, name) → resolved by parameter name, falls back to default
  3. **kwargs → receives all remaining beans not consumed by other parameters

Global Default Context

AppCtx provides a global default context for convenience, similar to Spring's application context. The top-level functions (bean, refresh, get_bean, add) operate on this shared instance:

from appctx import bean, refresh, get_bean

# These all use the same global ApplicationContext behind the scenes
@bean
def my_service():
    return MyService()

refresh()
app = get_bean(MyService)

Custom Context (Isolation)

For libraries or tests that need isolation, create your own ApplicationContext:

from appctx import ApplicationContext

ctx = ApplicationContext()

@ctx.bean
def my_service():
    return MyService()

ctx.refresh()
app = ctx.get_bean(MyService)

Note: @bean on the global context registers beans immediately when the module is imported. For custom contexts, use @ctx.bean to register against that specific instance.

Organizing Beans Across Modules

AppCtx does not perform automatic package scanning. Instead, beans are registered at import time — when Python loads a module, any @bean decorators execute and register with the target context.

To wire beans across multiple modules, simply import them before calling refresh():

# main.py
from appctx import bean, refresh, get_bean

# Import modules so their @bean decorators fire
import myapp.services.user   # registers UserService
import myapp.services.email   # registers EmailService
import myapp.config           # registers ConfigService

refresh()
app = get_bean(UserService)

You can also use add() to register a plain function or class (without the @bean decorator):

from appctx import add

def some_service():
    return SomeService()

add(some_service)  # Equivalent to @bean on some_service

Note: For modules, simply importing them is sufficient — the @bean decorators fire at import time. add(module) is available as a semantic marker but the import alone triggers registration.

Best Practices

  1. Rely on auto-wiring, avoid get_bean — Like Spring, let the container inject dependencies via function/constructor parameters. Reserve get_bean() only for bootstrapping the root application object.
  2. Use type annotations — They are the primary mechanism for dependency resolution. Always annotate positional parameters.
  3. Single responsibility per bean — Each bean should have one clear purpose.
  4. Prefer constructor injection — Dependencies go in __init__ parameters, not hidden inside methods.
  5. Keep beans stateless when possible — Makes testing and reasoning easier.
  6. Use @post_construct for initialization — Not __init__ side-effects. Post-construct runs after all beans exist, allowing cross-bean setup.
  7. Separate configuration from logic — Use dedicated config beans instead of hardcoding values.
  8. Design for testability — Beans that accept dependencies via constructor are trivially mockable in tests.

Documentation

License

MIT — see LICENSE.

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

appctx-0.3.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

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

appctx-0.3-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file appctx-0.3.tar.gz.

File metadata

  • Download URL: appctx-0.3.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for appctx-0.3.tar.gz
Algorithm Hash digest
SHA256 62c56c0f4d7b5ef7d82cdce9df8ea6b5a6aa27879d8f4c9c3585359f04215418
MD5 2679d012d2f158c7dcb49a5b279396aa
BLAKE2b-256 ba907362d290eb91ff51e8b3aaedad481ea7509da93df0b44079c285382de2a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for appctx-0.3.tar.gz:

Publisher: publish.yml on wssccc/appctx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file appctx-0.3-py3-none-any.whl.

File metadata

  • Download URL: appctx-0.3-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for appctx-0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 e473fe68134a8ce766add41857bf6f3a4473ccd02bc9a222e6c363d4f1a95677
MD5 0970c7217ae489d9f1e40e72bc0f05e0
BLAKE2b-256 36b368ecff4b2de0f2dee09333f9ae80c4d7f03e9b4388b95ed1505e6e34eafe

See more details on using hashes here.

Provenance

The following attestation bundles were made for appctx-0.3-py3-none-any.whl:

Publisher: publish.yml on wssccc/appctx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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