Coverage for src / lexigram / contracts / data / nosql / nosql.py: 0%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:57 +0800

1"""NoSQL database protocols for document-oriented storage. 

2 

3Provides driver-agnostic abstractions for document stores (MongoDB, 

4DynamoDB, CouchDB, etc.) parallel to the SQL-centric 

5``DatabaseProviderProtocol``. 

6 

7Key protocols: 

8 

9- :class:`CollectionProtocol` — CRUD, indexing, and aggregation on a 

10 single collection / table. 

11- :class:`DocumentStoreProtocol` — connection lifecycle, collection 

12 access, and health checks. 

13""" 

14 

15from __future__ import annotations 

16 

17from dataclasses import dataclass, field 

18from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable 

19 

20if TYPE_CHECKING: 

21 from collections.abc import AsyncIterator 

22 from contextlib import AbstractAsyncContextManager 

23 

24 from lexigram.contracts.core.health import HealthCheckResult 

25 

26 

27# ============================================================ 

28# Result types 

29# ============================================================ 

30 

31 

32@dataclass(frozen=True, slots=True) 

33class DocumentResult: 

34 """Result of a single document operation.""" 

35 

36 document_id: str | None = None 

37 matched_count: int = 0 

38 modified_count: int = 0 

39 upserted_id: str | None = None 

40 acknowledged: bool = True 

41 

42 

43@dataclass(frozen=True, slots=True) 

44class BulkWriteResult: 

45 """Result of a bulk write operation.""" 

46 

47 inserted_count: int = 0 

48 matched_count: int = 0 

49 modified_count: int = 0 

50 deleted_count: int = 0 

51 upserted_ids: list[str] = field(default_factory=list) 

52 

53 

54# ============================================================ 

55# Collection protocol 

56# ============================================================ 

57 

58 

59@runtime_checkable 

60class CollectionProtocol(Protocol): 

61 """Protocol for a NoSQL collection / table abstraction. 

62 

63 Provides document-oriented CRUD operations without SQL 

64 assumptions. Maps to MongoDB collections, DynamoDB tables, etc. 

65 """ 

66 

67 @property 

68 def name(self) -> str: 

69 """Collection / table name.""" 

70 ... 

71 

72 async def insert_one(self, document: dict[str, Any]) -> DocumentResult: 

73 """Insert a single document.""" 

74 ... 

75 

76 async def insert_many(self, documents: list[dict[str, Any]]) -> BulkWriteResult: 

77 """Insert multiple documents.""" 

78 ... 

79 

80 async def find_one( 

81 self, 

82 filter: dict[str, Any], 

83 *, 

84 projection: dict[str, Any] | None = None, 

85 ) -> dict[str, Any] | None: 

86 """Find a single document matching the filter.""" 

87 ... 

88 

89 async def find( 

90 self, 

91 filter: dict[str, Any], 

92 *, 

93 projection: dict[str, Any] | None = None, 

94 sort: list[tuple[str, int]] | None = None, 

95 skip: int = 0, 

96 limit: int = 0, 

97 ) -> AsyncIterator[dict[str, Any]]: 

98 """Find documents matching the filter. Returns async iterator.""" 

99 ... 

100 

101 async def update_one( 

102 self, 

103 filter: dict[str, Any], 

104 update: dict[str, Any], 

105 *, 

106 upsert: bool = False, 

107 ) -> DocumentResult: 

108 """Update a single document matching the filter.""" 

109 ... 

110 

111 async def update_many( 

112 self, 

113 filter: dict[str, Any], 

114 update: dict[str, Any], 

115 ) -> DocumentResult: 

116 """Update all documents matching the filter.""" 

117 ... 

118 

119 async def delete_one(self, filter: dict[str, Any]) -> DocumentResult: 

120 """Delete a single document matching the filter.""" 

121 ... 

122 

123 async def delete_many(self, filter: dict[str, Any]) -> DocumentResult: 

124 """Delete all documents matching the filter.""" 

125 ... 

126 

127 async def replace_one( 

128 self, 

129 filter: dict[str, Any], 

130 replacement: dict[str, Any], 

131 *, 

132 upsert: bool = False, 

133 ) -> DocumentResult: 

134 """Replace a single document matching the filter.""" 

135 ... 

136 

137 async def find_one_and_update( 

138 self, 

139 filter: dict[str, Any], 

140 update: dict[str, Any], 

141 *, 

142 upsert: bool = False, 

143 return_document: bool = True, 

144 ) -> dict[str, Any] | None: 

145 """Atomically find and update a document. 

146 

147 Args: 

148 filter: Match criteria. 

149 update: Update operations. 

150 upsert: Insert if no match. 

151 return_document: If True, return the updated document. 

152 

153 Returns: 

154 The document (before or after update) or None. 

155 """ 

156 ... 

157 

158 async def count_documents(self, filter: dict[str, Any] | None = None) -> int: 

159 """Count documents matching the filter.""" 

160 ... 

161 

162 async def create_index( 

163 self, 

164 keys: list[tuple[str, int]], 

165 *, 

166 unique: bool = False, 

167 name: str | None = None, 

168 ) -> str: 

169 """Create an index on the collection. Returns the index name.""" 

170 ... 

171 

172 async def aggregate( 

173 self, 

174 pipeline: list[dict[str, Any]], 

175 ) -> AsyncIterator[dict[str, Any]]: 

176 """Execute an aggregation pipeline.""" 

177 ... 

178 

179 async def list_indexes(self) -> list[dict[str, Any]]: 

180 """List all indexes on the collection.""" 

181 ... 

182 

183 async def distinct( 

184 self, 

185 key: str, 

186 filter: dict[str, Any] | None = None, 

187 ) -> list[Any]: 

188 """Get distinct values for a specified key.""" 

189 ... 

190 

191 async def bulk_write(self, operations: list[Any]) -> BulkWriteResult: 

192 """Execute multiple write operations in a single batch. 

193 

194 Operations are driver-specific write models (e.g., UpdateOne, InsertOne). 

195 """ 

196 ... 

197 

198 

199# ============================================================ 

200# Document store protocol 

201# ============================================================ 

202 

203 

204@runtime_checkable 

205class DocumentStoreProtocol(Protocol): 

206 """Protocol for a document-oriented database provider. 

207 

208 Parallel to ``DatabaseProviderProtocol`` but without SQL 

209 assumptions. Provides collection access, session management, 

210 and health checks. 

211 """ 

212 

213 async def connect(self) -> None: 

214 """Establish connection to the document store.""" 

215 ... 

216 

217 async def disconnect(self) -> None: 

218 """Close all connections.""" 

219 ... 

220 

221 def is_connected(self) -> bool: 

222 """Check if the store is connected.""" 

223 ... 

224 

225 def collection(self, name: str) -> CollectionProtocol: 

226 """Get a collection / table handle by name.""" 

227 ... 

228 

229 def session(self) -> AbstractAsyncContextManager[Any]: 

230 """Create a session for multi-document transactions.""" 

231 ... 

232 

233 async def list_collections(self) -> list[str]: 

234 """List all collection names in the database.""" 

235 ... 

236 

237 async def drop_collection(self, name: str) -> None: 

238 """Drop a collection.""" 

239 ... 

240 

241 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult: 

242 """Check document store connectivity and health.""" 

243 ... 

244 

245 

246__all__ = [ 

247 "BulkWriteResult", 

248 "CollectionProtocol", 

249 "DocumentResult", 

250 "DocumentStoreProtocol", 

251]