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 with package scanning and auto-wiring via type annotations to manage dependencies in a clean, testable way.

Python Requirements: 3.8+ | License: MIT

Features

  • 🏷️ Pure marker decorators@bean for functions, @component for classes — no coupling to container instances
  • 🔍 Package scanningApplicationContext.scan() auto-discovers annotated objects
  • 🔄 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
  • Custom bean names@bean(name="custom") / @component(name="custom")

Installation

pip install appctx

Quick Start

1. Define beans and components

Use @bean on functions (factories) and @component on classes:

# myapp/config.py
from appctx.decorators import bean

@bean
def db_config():
    return {"host": "localhost", "port": 5432}

@bean(name="app_config")  # Custom bean name
def application_config():
    return {"name": "my_app", "debug": False}
# myapp/services.py
from appctx.decorators import component

@component
class UserService:
    def __init__(self, db_config: dict):
        self.db_config = db_config

    def get_user(self, user_id: int):
        return f"User {user_id} from {self.db_config['host']}"

@component(name="notification_svc")
class NotificationService:
    def __init__(self, db_config: dict, app_config: dict):
        self.db_config = db_config
        self.app_config = app_config

2. Scan, refresh, and use

# main.py
from appctx import ApplicationContext

ctx = ApplicationContext()
ctx.scan("myapp").refresh()

# Entry point — the only place you should call get_bean
user_svc = ctx.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.

Post-construct lifecycle

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

from appctx.decorators import component, post_construct

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

    @post_construct
    def init(self):
        self.connection = create_connection()  # Called after all beans are created

Core Concepts

Concept Description
Bean An object managed by the container
ApplicationContext The DI container that holds and wires beans
@bean Pure marker decorator for functions (bean factories)
@component Pure marker decorator for classes (components)
scan() Discovers @bean/@component annotated objects in packages
refresh() Instantiates all beans in dependency order
add() Manually register an object without annotation

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

Scanning

Package scanning

Scan an entire package recursively:

ctx = ApplicationContext()
ctx.scan("myapp").refresh()

Module scanning

Scan a single module:

ctx = ApplicationContext()
ctx.scan("myapp.services").refresh()

Auto-detection

Call scan() without arguments to auto-detect the caller's package:

# Called from within myapp/main.py
ctx = ApplicationContext()
ctx.scan().refresh()  # Auto-detects "myapp" package

Exclude patterns

Exclude packages or modules using fnmatch glob patterns:

ctx = ApplicationContext()
ctx.scan("myapp", exclude=["myapp.tests", "myapp.internal_*"]).refresh()

Decorators

@bean — Function bean factories

from appctx.decorators import bean

@bean
def my_service():
    return MyService()

@bean(name="custom_service")
def another_service():
    return MyService()
  • Only accepts functions — raises TypeError on classes
  • Sets _is_bean = True and _bean_name attributes
  • Use @component for classes

@component — Class components

from appctx.decorators import component

@component
class MyComponent:
    def __init__(self, dependency: SomeDependency):
        self.dependency = dependency

@component(name="my_comp")
class AnotherComponent:
    pass
  • Only accepts classes — raises TypeError on functions
  • Sets _is_component = True and _bean_name attributes
  • Use @bean for functions

@post_construct — Lifecycle hooks

from appctx.decorators import post_construct

class MyService:
    @post_construct
    def init(self):
        self.connection = create_connection()

Organizing Beans Across Modules

Define beans in separate modules, scan to discover them:

# myapp/__init__.py — empty or minimal

# myapp/config.py
from appctx.decorators import bean
@bean
def db_config():
    return {"host": "localhost"}

# myapp/services.py
from appctx.decorators import component
@component
class UserService:
    def __init__(self, db_config: dict):
        self.db_config = db_config

# main.py
from appctx import ApplicationContext
ctx = ApplicationContext()
ctx.scan("myapp").refresh()

You can also manually register objects with add():

ctx = ApplicationContext()
ctx.add(my_factory_func).add(MyComponentClass)
ctx.refresh()

Best Practices

  1. Rely on auto-wiring, avoid get_bean — Let the container inject dependencies via parameters.
  2. Use type annotations — They are the primary mechanism for dependency resolution.
  3. Use @bean for functions, @component for classes — Semantic separation like Spring.
  4. Prefer scan() over add() — Let the container discover beans automatically.
  5. Use @post_construct for initialization — Not __init__ side-effects.
  6. Separate configuration from logic — Use dedicated config beans.
  7. Design for testability — Beans that accept dependencies via constructor are trivially mockable.

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.5.tar.gz (16.0 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.5-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for appctx-0.5.tar.gz
Algorithm Hash digest
SHA256 a3b44b98f3145f24027c2297786ca60a3dd42133ca8f13a8c0a23d36f89de644
MD5 e8f0c539502d98816e79da95cfad8283
BLAKE2b-256 90cb86dc4150e1717b6dcddf920a1fc71e0e7866da52cbeb61b4004b879624b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for appctx-0.5.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.5-py3-none-any.whl.

File metadata

  • Download URL: appctx-0.5-py3-none-any.whl
  • Upload date:
  • Size: 8.7 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 74a17bfe4821989f0b0f71d1bfe6061a5b6ccb1f0e24ce47c25f7ae23a30736a
MD5 1a3fbbf1a3cd3c23fb14cc80462f6472
BLAKE2b-256 1b73d70eb4319c70b9826681b75e632a7e4363656aabf31669c24b1220190787

See more details on using hashes here.

Provenance

The following attestation bundles were made for appctx-0.5-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