Coverage for src/dataknobs_bots/api/dependencies.py: 0%

1"""Dependency injection helpers for FastAPI.

2

3 This module provides singleton management and FastAPI dependency injection

4 for bot-related services.

5

6 Example:

7 ```python

8 from fastapi import FastAPI

9 from dataknobs_bots.api.dependencies import (

10 init_bot_manager,

11 BotManagerDep,

12 )

13

14 app = FastAPI()

15

16 @app.on_event("startup")

17 async def startup():

18 # Initialize with a config loader

19 init_bot_manager(config_loader=my_loader)

20

21 @app.post("/chat/{bot_id}")

22 async def chat(

23 bot_id: str,

24 message: str,

25 manager: BotManagerDep,

26 ):

27 bot = await manager.get_or_create(bot_id)

28 return await bot.chat(message, context)

29 ```

30"""

31

32 from __future__ import annotations

33

34 import logging

35 from typing import Annotated, Any

36

37 from dataknobs_bots.bot.manager import BotManager, ConfigLoaderType

38

39

40 logger = logging.getLogger(__name__)

41

42

43 class _BotManagerSingleton:

44 """Singleton container for BotManager instance.

45

46 Using a class-based approach avoids global statement warnings

47 while maintaining singleton semantics.

48 """

49

50 _instance: BotManager | None = None

51

52 @classmethod

53 def get(cls) -> BotManager:

54 """Get the singleton instance, creating with defaults if needed."""

55 if cls._instance is None:

56 cls._instance = BotManager()

57 logger.info("Created default BotManager singleton (no config loader)")

58 return cls._instance

59

60 @classmethod

61 def init(

62 cls,

63 config_loader: ConfigLoaderType | None = None,

64 **kwargs: Any,

65 ) -> BotManager:

66 """Initialize the singleton with configuration."""

67 cls._instance = BotManager(config_loader=config_loader, **kwargs)

68 logger.info("Initialized BotManager singleton")

69 return cls._instance

70

71 @classmethod

72 def reset(cls) -> None:

73 """Reset the singleton instance."""

74 cls._instance = None

75 logger.info("Reset BotManager singleton")

76

77

78 def get_bot_manager() -> BotManager:

79 """Get or create BotManager singleton instance.

80

81 Returns:

82 BotManager instance

83

84 Note:

85 Call `init_bot_manager()` during app startup to configure

86 the singleton before using this dependency.

87 """

88 return _BotManagerSingleton.get()

89

90

91 def init_bot_manager(

92 config_loader: ConfigLoaderType | None = None,

93 **kwargs: Any,

94) -> BotManager:

95 """Initialize the BotManager singleton with configuration.

96

97 Call this during application startup to configure the singleton.

98

99 Args:

100 config_loader: Optional configuration loader for bots

101 **kwargs: Additional arguments passed to BotManager

102

103 Returns:

104 Configured BotManager instance

105

106 Example:

107 ```python

108 @app.on_event("startup")

109 async def startup():

110 init_bot_manager(

111 config_loader=MyConfigLoader("./configs")

112 )

113 ```

114 """

115 return _BotManagerSingleton.init(config_loader=config_loader, **kwargs)

116

117

118 def reset_bot_manager() -> None:

119 """Reset the BotManager singleton.

120

121 Useful for testing or when reconfiguring the application.

122 """

123 _BotManagerSingleton.reset()

124

125

126# Dependency function for FastAPI

127 def _get_bot_manager_dep() -> BotManager:

128 """Dependency function for FastAPI."""

129 return get_bot_manager()

130

131

132# Type alias for FastAPI dependency injection

133# Usage: async def endpoint(manager: BotManagerDep):

134 try:

135 from fastapi import Depends

136

137 BotManagerDep = Annotated[BotManager, Depends(_get_bot_manager_dep)]

138 except ImportError:

139 # FastAPI not installed - provide a placeholder

140 BotManagerDep = BotManager # type: ignore