Coverage for src / dataknobs_bots / registry / backend.py: 57%

30 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-16 10:13 -0700

1"""Registry backend protocol for pluggable storage.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable 

6 

7if TYPE_CHECKING: 

8 from .models import Registration 

9 

10 

11@runtime_checkable 

12class RegistryBackend(Protocol): 

13 """Protocol for bot registry storage backends. 

14 

15 Implementations store bot configurations with metadata. 

16 The backend is responsible for persistence; the BotRegistry 

17 handles caching and bot instantiation. 

18 

19 This protocol defines the interface for storage backends: 

20 - InMemoryBackend: Simple dict storage (default, good for tests) 

21 - PostgreSQLBackend: Database persistence (future/external) 

22 - RedisBackend: Distributed caching (future/external) 

23 

24 All methods are async to support both sync and async backends. 

25 

26 Example: 

27 ```python 

28 class MyCustomBackend: 

29 async def initialize(self) -> None: 

30 # Setup database connection 

31 ... 

32 

33 async def register(self, bot_id: str, config: dict, status: str = "active"): 

34 # Store in database 

35 ... 

36 

37 # ... implement other methods 

38 ``` 

39 """ 

40 

41 async def initialize(self) -> None: 

42 """Initialize the backend (create tables, connections, etc.). 

43 

44 Called before the backend is used. Should be idempotent. 

45 """ 

46 ... 

47 

48 async def close(self) -> None: 

49 """Close the backend (release connections, etc.). 

50 

51 Called when the registry is shutting down. 

52 """ 

53 ... 

54 

55 async def register( 

56 self, 

57 bot_id: str, 

58 config: dict[str, Any], 

59 status: str = "active", 

60 ) -> Registration: 

61 """Register a bot or update existing registration. 

62 

63 If a registration with the same bot_id exists, it should be updated 

64 (config replaced, status updated, updated_at set to now). 

65 

66 Args: 

67 bot_id: Unique bot identifier 

68 config: Bot configuration dictionary (should be portable) 

69 status: Registration status (default: active) 

70 

71 Returns: 

72 Registration object with metadata 

73 """ 

74 ... 

75 

76 async def get(self, bot_id: str) -> Registration | None: 

77 """Get registration by ID. 

78 

79 Should update last_accessed_at timestamp on access. 

80 

81 Args: 

82 bot_id: Bot identifier 

83 

84 Returns: 

85 Registration if found, None otherwise 

86 """ 

87 ... 

88 

89 async def get_config(self, bot_id: str) -> dict[str, Any] | None: 

90 """Get just the config for a bot. 

91 

92 Convenience method that returns only the config dict. 

93 Should also update last_accessed_at. 

94 

95 Args: 

96 bot_id: Bot identifier 

97 

98 Returns: 

99 Config dict if found, None otherwise 

100 """ 

101 ... 

102 

103 async def exists(self, bot_id: str) -> bool: 

104 """Check if an active registration exists. 

105 

106 Args: 

107 bot_id: Bot identifier 

108 

109 Returns: 

110 True if registration exists and is active 

111 """ 

112 ... 

113 

114 async def unregister(self, bot_id: str) -> bool: 

115 """Hard delete a registration. 

116 

117 Permanently removes the registration from storage. 

118 

119 Args: 

120 bot_id: Bot identifier 

121 

122 Returns: 

123 True if deleted, False if not found 

124 """ 

125 ... 

126 

127 async def deactivate(self, bot_id: str) -> bool: 

128 """Soft delete (set status to inactive). 

129 

130 Marks the registration as inactive without deleting. 

131 Inactive registrations should not be returned by exists() 

132 or list_active(). 

133 

134 Args: 

135 bot_id: Bot identifier 

136 

137 Returns: 

138 True if deactivated, False if not found 

139 """ 

140 ... 

141 

142 async def list_active(self) -> list[Registration]: 

143 """List all active registrations. 

144 

145 Returns: 

146 List of active Registration objects 

147 """ 

148 ... 

149 

150 async def list_all(self) -> list[Registration]: 

151 """List all registrations including inactive. 

152 

153 Returns: 

154 List of all Registration objects 

155 """ 

156 ... 

157 

158 async def list_ids(self) -> list[str]: 

159 """List active bot IDs only. 

160 

161 More efficient than list_active() when only IDs are needed. 

162 

163 Returns: 

164 List of active bot IDs 

165 """ 

166 ... 

167 

168 async def count(self) -> int: 

169 """Count active registrations. 

170 

171 Returns: 

172 Number of active registrations 

173 """ 

174 ... 

175 

176 async def clear(self) -> None: 

177 """Clear all registrations. 

178 

179 Primarily useful for testing. 

180 """ 

181 ...