Skip to content

πŸ”—πŸ“š Elegant Python Dependency Injection with Profile-Aware Configuration

License

Notifications You must be signed in to change notification settings

hcleungca/bindry

Repository files navigation

πŸ”—πŸ“š Bindry

Elegant Python Dependency Injection with Profile-Aware Configuration

PyPI version Python Support License: MIT

🌟 Overview

Bindry is a powerful yet intuitive dependency injection framework for Python that supports profile-based configuration, environment variable interpolation, and flexible component lifecycle management. It enables you to write more maintainable and testable applications by managing dependencies through configuration rather than hard-coding them.

🎯 Motivation

As a Spring developer, I was eager to explore the possibilities of dependency injection in Python. While Spring's robust ecosystem has been instrumental in shaping my understanding of modern software development, I found myself craving a framework that could leverage Python's unique strengths and flexibility. Bindry fills this gap by providing an elegant and intuitive solution for managing dependencies in Python applications.

✨ Features

  • πŸ—‚οΈ Profile-based configuration management
  • 🌐 Environment variable support with fallback values
  • πŸ“œ YAML and JSON configuration file support
  • βš™οΈ Constructor, method, and property injection
  • 🧩 Singleton and prototype scoping
  • πŸ” Type-based automatic dependency resolution
  • πŸ› οΈ Support for constructor arguments via configuration
  • 🏷️ Flexible component registration through decorators or configuration files

πŸ“¦ Installation

pip install bindry

πŸš€ Quick Start

1. Define Your Components

from bindry import component, Scope, autowired

# Define an interface
class MessageService:
    def send_message(self, msg: str): pass

# Implement the service
# Register the bean in application-context.yaml
class EmailService(MessageService):
    def send_message(self, msg: str):
        print(f"Sending email: {msg}")

# Use declarator to register the bean
@component(scope=Scope.SINGLETON)
class NotificationManager:
    # MessageService will be injected
    def __init__(self, message_service: MessageService):
        self.message_service = message_service

    def notify(self, message: str):
        self.message_service.send_message(message)

2. Configure Your Application

Create a application-context.yaml file:

profiles:
  default:
    beans:
      MessageService:
        bean_type: "myapp.services.MessageService"
        implementation: "myapp.services.EmailService"
        scope: "singleton"
        constructor_args:
          timeout: 30

  development:
    beans:
      MessageService:
        bean_type: "myapp.services.MessageService"
        implementation: "myapp.services.MockMessageService"
        scope: "singleton"

3. Initialize and Use

from bindry import ApplicationContext

# Initialize the context
context = ApplicationContext.get_instance()
context.load_configuration("application-context.yaml", active_profiles=["development"])

# Get and use components
notification_manager = context.get_bean(NotificationManager)
notification_manager.notify("Hello, World!")

🌍 Environment Variable Support

Bindry supports environment variable interpolation in configuration files:

profiles:
  default:
    beans:
      DatabaseService:
        bean_type: "myapp.services.DatabaseService"
        implementation: "myapp.services.DatabaseService"
        scope: "singleton"
        constructor_args:
          url: "${DATABASE_URL:sqlite:///default.db}"
          timeout: "${DB_TIMEOUT:30}"

πŸ”„ Profile Management

Using Multiple Profiles

context = ApplicationContext.get_instance()
context.set_active_profiles(["default", "development", "testing"])

Environment-Based Profiles

Set the ACTIVE_PROFILES environment variable:

export ACTIVE_PROFILES=development,testing

⚑ Advanced Features

Constructor Argument Injection

@component(
    scope=Scope.SINGLETON,
    bean_type=DatabaseService,
    constructor_args={
        "timeout": 30,
        "retries": 3,
        "kwargs": {"debug": True}
    }
)
class DatabaseService:
    def __init__(self, timeout: int, retries: int, **kwargs):
        self.timeout = timeout
        self.retries = retries
        self.debug = kwargs.get("debug", False)

Method Injection

@component(Scope.SINGLETON)
class ServiceManager:
    @autowired
    def configure(self, config_service: ConfigService):
        self.config_service = config_service

🀝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

πŸ“œ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™Œ Acknowledgments

This project was developed with the assistance of AI language models:

  • Initial implementation assistance provided by ChatGPT
  • Additional feature development and refinements by Claude.ai

While the code was primarily generated through AI assistance, all implementations have been carefully reviewed and tested to ensure quality and reliability.

❀️ Sponsor this project

Patreon

About

πŸ”—πŸ“š Elegant Python Dependency Injection with Profile-Aware Configuration

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published