@@ -2616,6 +2616,138 @@ class StarletteConfig(TypedDict):
26162616 extra_rollback_statuses: NotRequired[set[int]]
26172617` ` `
26182618
2619+ # ## Disabling Built-in Dependency Injection (disable_di Pattern)
2620+
2621+ **When to Use**: When users want to integrate SQLSpec with their own dependency injection solution (e.g., Dishka, dependency-injector) and need full control over database lifecycle management.
2622+
2623+ **Pattern**: Add a `disable_di` boolean flag to framework extension configuration that conditionally skips the built-in DI setup.
2624+
2625+ **Implementation Steps**:
2626+
2627+ 1. **Add to TypedDict in `sqlspec/config.py`** :
2628+
2629+ ` ` ` python
2630+ class StarletteConfig(TypedDict):
2631+ # ... existing fields ...
2632+
2633+ disable_di: NotRequired[bool]
2634+ """Disable built-in dependency injection. Default: False.
2635+ When True, the Starlette/FastAPI extension will not add middleware for managing
2636+ database connections and sessions. Users are responsible for managing the
2637+ database lifecycle manually via their own DI solution.
2638+ """
2639+ ` ` `
2640+
2641+ 2. **Add to Configuration State Dataclass** :
2642+
2643+ ` ` ` python
2644+ @dataclass
2645+ class SQLSpecConfigState:
2646+ config: "DatabaseConfigProtocol[Any, Any, Any]"
2647+ connection_key: str
2648+ pool_key: str
2649+ session_key: str
2650+ commit_mode: CommitMode
2651+ extra_commit_statuses: "set[int] | None"
2652+ extra_rollback_statuses: "set[int] | None"
2653+ disable_di: bool # Add this field
2654+ ` ` `
2655+
2656+ 3. **Extract from Config and Default to False** :
2657+
2658+ ` ` ` python
2659+ def _extract_starlette_settings(self, config):
2660+ starlette_config = config.extension_config.get("starlette", {})
2661+ return {
2662+ # ... existing keys ...
2663+ "disable_di": starlette_config.get("disable_di", False), # Default False
2664+ }
2665+ ` ` `
2666+
2667+ 4. **Conditionally Skip DI Setup** :
2668+
2669+ **Middleware-based (Starlette/FastAPI)**:
2670+ ` ` ` python
2671+ def init_app(self, app):
2672+ # ... lifespan setup ...
2673+
2674+ for config_state in self._config_states:
2675+ if not config_state.disable_di: # Only add if DI enabled
2676+ self._add_middleware(app, config_state)
2677+ ` ` `
2678+
2679+ **Provider-based (Litestar)**:
2680+ ` ` ` python
2681+ def on_app_init(self, app_config):
2682+ for state in self._plugin_configs:
2683+ # ... signature namespace ...
2684+
2685+ if not state.disable_di: # Only register if DI enabled
2686+ app_config.before_send.append(state.before_send_handler)
2687+ app_config.lifespan.append(state.lifespan_handler)
2688+ app_config.dependencies.update({
2689+ state.connection_key: Provide(state.connection_provider),
2690+ state.pool_key: Provide(state.pool_provider),
2691+ state.session_key: Provide(state.session_provider),
2692+ })
2693+ ` ` `
2694+
2695+ **Hook-based (Flask)**:
2696+ ` ` ` python
2697+ def init_app(self, app):
2698+ # ... pool setup ...
2699+
2700+ # Only register hooks if at least one config has DI enabled
2701+ if any(not state.disable_di for state in self._config_states):
2702+ app.before_request(self._before_request_handler)
2703+ app.after_request(self._after_request_handler)
2704+ app.teardown_appcontext(self._teardown_appcontext_handler)
2705+
2706+ def _before_request_handler(self):
2707+ for config_state in self._config_states:
2708+ if config_state.disable_di: # Skip if DI disabled
2709+ continue
2710+ # ... connection setup ...
2711+ ` ` `
2712+
2713+ **Testing Requirements**:
2714+
2715+ 1. **Test with `disable_di=True`** : Verify DI mechanisms are not active
2716+ 2. **Test default behavior** : Verify `disable_di=False` preserves existing functionality
2717+ 3. **Integration tests** : Demonstrate manual DI setup works correctly
2718+
2719+ **Example Usage**:
2720+
2721+ ` ` ` python
2722+ from sqlspec.adapters.asyncpg import AsyncpgConfig
2723+ from sqlspec.base import SQLSpec
2724+ from sqlspec.extensions.starlette import SQLSpecPlugin
2725+
2726+ sql = SQLSpec()
2727+ config = AsyncpgConfig(
2728+ pool_config={"dsn": "postgresql://localhost/db"},
2729+ extension_config={"starlette": {"disable_di": True}} # Disable built-in DI
2730+ )
2731+ sql.add_config(config)
2732+ plugin = SQLSpecPlugin(sql)
2733+
2734+ # User is now responsible for manual lifecycle management
2735+ async def my_route(request):
2736+ pool = await config.create_pool()
2737+ async with config.provide_connection(pool) as connection:
2738+ session = config.driver_type(connection=connection, statement_config=config.statement_config)
2739+ result = await session.execute("SELECT 1")
2740+ await config.close_pool()
2741+ return result
2742+ ` ` `
2743+
2744+ **Key Principles**:
2745+
2746+ - **Backward Compatible**: Default `False` preserves existing behavior
2747+ - **Consistent Naming**: Use `disable_di` across all frameworks
2748+ - **Clear Documentation**: Warn users they are responsible for lifecycle management
2749+ - **Complete Control**: When disabled, extension does zero automatic DI
2750+
26192751# ## Multi-Database Support
26202752
26212753**Key validation ensures unique state keys**:
0 commit comments