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

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

2

3 from __future__ import annotations

4

5 from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable

6

7 if TYPE_CHECKING:

8 from .models import Registration

9

10

11@runtime_checkable

12 class 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 ...