ADR-001: Dual Package Architecture¶
Status¶
Accepted
Date¶
2025-11-26
Context¶
We need to design a debug toolbar for async Python web applications, with initial support for Litestar. The key decisions involve:
Package organization: Single monolithic package vs. split core/plugin packages
Framework coupling: Tight integration vs. adapter pattern
Async design: Sync-first with async wrappers vs. async-native
Requirements¶
Support Litestar as primary framework
Enable future support for other async frameworks (FastAPI, Starlette, Quart)
Maintain clean separation between core functionality and framework integration
Support optional SQLAlchemy/Advanced-Alchemy integration
Minimize dependencies in core package
Options Considered¶
Option 1: Monolithic Package¶
Single litestar-debug-toolbar package containing all functionality.
Pros:
Simpler installation (
pip install litestar-debug-toolbar)Single source of truth
Easier maintenance
Cons:
Framework-specific code mixed with generic code
Cannot reuse core for other frameworks
Litestar becomes hard dependency
Option 3: Plugin Architecture with Core¶
Single async-debug-toolbar package with optional extras:
pip install async-debug-toolbar[litestar]
pip install async-debug-toolbar[fastapi]
Pros:
Single package to install
Framework as optional extra
Clear extension pattern
Cons:
All code in one repo (could grow large)
Plugin code shipped even if unused
Namespace management complexity
Decision¶
We will implement Option 2: Dual Package with Shared Core.
Package Structure¶
async-debug-toolbar/ # Core package
- Panel base classes
- Configuration system
- Storage backend
- Template engine
- Built-in panels
- Abstract ASGI adapter
litestar-debug-toolbar/ # Plugin package
- LitestarDebugToolbarPlugin
- LitestarDebugToolbarMiddleware
- LitestarAdapter (implements abstract adapter)
- Litestar-specific panels
Dependency Flow¶
litestar-debug-toolbar
└── depends on: async-debug-toolbar>=1.0
└── depends on: litestar>=2.0
async-debug-toolbar
└── depends on: jinja2>=3.1
└── optional[advanced-alchemy]: advanced-alchemy>=0.10
Consequences¶
Positive¶
Framework Independence: Core can be used as foundation for FastAPI, Starlette, Quart, etc.
Minimal Dependencies: Core has only Jinja2 as dependency
Clear Boundaries: Framework-specific code isolated in plugin package
Testing Isolation: Core tests don’t require Litestar
Versioning Flexibility: Can release core and plugin independently
Negative¶
Installation Complexity: Users install two packages (mitigated by
pip install litestar-debug-toolbarwhich pulls in core)Cross-Package Changes: Some changes require coordinated releases
Documentation Split: Must document both packages clearly
Neutral¶
Monorepo vs. Multi-repo: We’ll use a monorepo with both packages for easier development, but publish as separate PyPI packages
Implementation Notes¶
Core Package (async-debug-toolbar)¶
[project]
name = "async-debug-toolbar"
dependencies = ["jinja2>=3.1", "markupsafe>=2.1"]
[project.optional-dependencies]
advanced-alchemy = ["advanced-alchemy>=0.10", "sqlalchemy>=2.0"]
Plugin Package (litestar-debug-toolbar)¶
[project]
name = "litestar-debug-toolbar"
dependencies = ["async-debug-toolbar>=1.0", "litestar>=2.0"]
Abstract Adapter Pattern¶
The core defines an abstract ASGIAdapter that framework plugins implement:
# async_debug_toolbar/adapters/base.py
class ASGIAdapter(ABC):
@abstractmethod
def get_routes(self) -> list[dict]: ...
@abstractmethod
def should_inject_toolbar(self, ...) -> bool: ...
@abstractmethod
def inject_toolbar(self, ...) -> bytes: ...
Framework plugins provide concrete implementations:
# litestar_debug_toolbar/adapter.py
class LitestarAdapter(ASGIAdapter):
def __init__(self, app: Litestar): ...
def get_routes(self) -> list[dict]:
# Litestar-specific route extraction
...