Skip to main content

Overview

Bundles are modular components that extend Framefox’s functionality. They allow you to package services, commands, templates, and configuration into reusable units that can be shared across projects.

Bundle System

The bundle system in Framefox provides:
  • Service Registration: Register services in the dependency injection container
  • Command Registration: Add custom CLI commands
  • Auto-discovery: Automatically load bundles via entry points
  • Lifecycle Management: Control initialization and bootstrapping
  • Template & Static Files: Package UI components with your bundle

Creating a Bundle

To create a bundle, extend the AbstractBundle class:
from framefox.core.bundle.abstract_bundle import AbstractBundle
from framefox.core.di.service_container import ServiceContainer
from framefox.terminal.command_registry import CommandRegistry
from typing import List, Dict, Any

class MyCustomBundle(AbstractBundle):
    @property
    def name(self) -> str:
        """Unique identifier for your bundle"""
        return "my_custom_bundle"
    
    @property
    def dependencies(self) -> List[str]:
        """Bundles this bundle depends on"""
        return ["core_bundle"]  # Optional
    
    def register_services(self, container: ServiceContainer) -> None:
        """Register your services"""
        from .services import MyService
        container.register(MyService)
    
    def register_commands(self, registry: CommandRegistry) -> None:
        """Register CLI commands"""
        from .commands import MyCommand
        registry.register(MyCommand)
    
    def get_config_schema(self) -> Dict[str, Any]:
        """Define configuration schema"""
        return {
            "api_key": {"type": "string", "required": True},
            "timeout": {"type": "integer", "default": 30}
        }
    
    def boot(self, container: ServiceContainer) -> None:
        """Called after all bundles are registered"""
        # Initialize resources, connect to services, etc.
        my_service = container.get(MyService)
        my_service.initialize()
    
    def get_templates_dir(self) -> str:
        """Path to bundle templates"""
        import os
        return os.path.join(os.path.dirname(__file__), "templates")
    
    def get_static_dir(self) -> str:
        """Path to static files"""
        import os
        return os.path.join(os.path.dirname(__file__), "static")

Bundle Discovery

Framefox automatically discovers bundles using Python entry points. Add this to your setup.py or pyproject.toml: setup.py:
from setuptools import setup, find_packages

setup(
    name="my-framefox-bundle",
    version="1.0.0",
    packages=find_packages(),
    entry_points={
        "framefox.bundles": [
            "my_bundle = my_package.bundle:MyCustomBundle",
        ],
    },
)
pyproject.toml:
[project.entry-points."framefox.bundles"]
my_bundle = "my_package.bundle:MyCustomBundle"

BundleManager

The BundleManager handles bundle lifecycle:
from framefox.core.bundle.bundle_manager import BundleManager
from framefox.core.di.service_container import ServiceContainer
from framefox.terminal.command_registry import CommandRegistry

# Create manager
manager = BundleManager()

# Discover all bundles
manager.discover_bundles()

# Register bundle services
container = ServiceContainer()
manager.register_bundle_services(container)

# Register bundle commands
registry = CommandRegistry()
manager.register_bundle_commands(registry)

# Boot all bundles
manager.boot_bundles(container)

# Check if bundle exists
if manager.has_bundle("my_custom_bundle"):
    bundle = manager.get_bundle("my_custom_bundle")

# List all bundles
bundles = manager.list_bundles()
for name, description in bundles.items():
    print(f"{name}: {description}")

Bundle Lifecycle

Bundles follow this lifecycle:
  1. Discovery: Entry points are scanned and bundle classes loaded
  2. Service Registration: register_services() called for each bundle
  3. Command Registration: register_commands() called for each bundle
  4. Boot: boot() called after all bundles registered
# Lifecycle flow
manager = BundleManager()

# 1. Discovery
manager.discover_bundles()  # Loads all entry points

# 2. Service Registration
manager.register_bundle_services(container)

# 3. Command Registration
manager.register_bundle_commands(registry)

# 4. Boot
manager.boot_bundles(container)  # Bundles can now interact

Example: Analytics Bundle

Here’s a complete example bundle:
from framefox.core.bundle.abstract_bundle import AbstractBundle
from framefox.core.di.service_container import ServiceContainer
from typing import Dict, Any
import os

class AnalyticsBundle(AbstractBundle):
    @property
    def name(self) -> str:
        return "analytics"
    
    def register_services(self, container: ServiceContainer) -> None:
        """Register analytics services"""
        from .services.tracker import AnalyticsTracker
        from .services.reporter import AnalyticsReporter
        
        container.register(AnalyticsTracker)
        container.register(AnalyticsReporter)
    
    def register_commands(self, registry) -> None:
        """Register analytics commands"""
        from .commands.report import ReportCommand
        registry.register(ReportCommand)
    
    def get_config_schema(self) -> Dict[str, Any]:
        return {
            "tracking_id": {"type": "string", "required": True},
            "enabled": {"type": "boolean", "default": True},
            "sample_rate": {"type": "float", "default": 1.0}
        }
    
    def boot(self, container: ServiceContainer) -> None:
        """Initialize analytics tracking"""
        tracker = container.get(AnalyticsTracker)
        
        # Set up event listeners
        from framefox.core.events.event_dispatcher import dispatcher
        dispatcher.add_listener("kernel.request_received", tracker.track_request)
        dispatcher.add_listener("user.login", tracker.track_login)
    
    def get_templates_dir(self) -> str:
        return os.path.join(os.path.dirname(__file__), "templates")

Best Practices

  1. Single Responsibility: Each bundle should have a clear, focused purpose
  2. Dependency Management: Declare bundle dependencies explicitly
  3. Configuration Schema: Define clear configuration requirements
  4. Error Handling: Handle initialization errors gracefully
  5. Documentation: Include clear usage instructions
  6. Testing: Test bundle initialization and integration

Advanced Features

Bundle Dependencies

Declare dependencies to ensure proper loading order:
@property
def dependencies(self) -> List[str]:
    return ["database", "cache", "events"]

Configuration Validation

Define schema to validate bundle configuration:
def get_config_schema(self) -> Dict[str, Any]:
    return {
        "api_endpoint": {
            "type": "string",
            "required": True,
            "pattern": "^https://"
        },
        "retry_attempts": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 3
        }
    }

Resource Cleanup

Implement cleanup in your services:
class MyService:
    def __del__(self):
        self.cleanup()
    
    def cleanup(self):
        # Close connections, release resources
        pass

Build docs developers (and LLMs) love