Coverage for functions \ flipdare \ firestore \ db_bridge.py: 47%
98 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13"""
14Generic database operations for PersistedWrapper/AppBaseModel subclasses.
16This module provides a generic way to perform common CRUD operations on any
17PersistedWrapper/AppBaseModel subclass using AppDb or AppSubDb instances, reducing code duplication
18across admin and service classes. All database operations return PersistedWrapper
19to guarantee doc_id exists.
20"""
22from typing import Any
24from flipdare.app_log import LOG
25from flipdare.constants import IS_DEBUG
26from flipdare.result.app_result import AppResult
27from flipdare.firestore._app_db import AppDb
28from flipdare.firestore.core.app_base_model import AppBaseModel
29from flipdare.generated.shared.app_error_code import AppErrorCode
30from flipdare.wrapper import PersistedWrapper
32__all__ = ["DbBridge"]
35class DbBridge[W: PersistedWrapper[Any], D: AppDb[Any, Any]]:
36 """
37 Generic database operations handler for PersistedWrapper/AppBaseModel subclasses.
39 Provides common get and update operations with consistent error handling
40 and AppResult wrapping.
42 Example usage:
43 user_ops = DbBridge(user_db, "User")
44 result = user_ops.get("user_123")
45 if result.has_errors:
46 # handle error
47 user = result.generated
49 # Update
50 user.update_field("some_field", "new_value")
51 update_result = user_ops.update(user)
52 """
54 def __init__(self, db: D, model_name: str) -> None:
55 """
56 Initialize generic database operations.
58 Args:
59 db: The AppDb instance for this model type
60 model_name: Human-readable name for the model (e.g., "User", "Flag", "FlagAction")
62 """
63 self._db = db
64 self._model_name = model_name
66 @property
67 def db(self) -> D:
68 """Get the underlying database instance."""
69 return self._db
71 @property
72 def model_name(self) -> str:
73 """Get the model name."""
74 return self._model_name
76 def get(self, doc_id: str) -> AppResult[W]:
77 """
78 Get a model by document ID.
80 Args:
81 doc_id: The document ID to retrieve
83 Returns:
84 AppResult with specific wrapper type in .generated if successful, errors otherwise
86 """
87 result = AppResult[W](doc_id=doc_id)
88 debug_msg = f"{self._model_name} {doc_id}"
90 try:
91 model = self._db.get(doc_id)
92 if model is None:
93 msg = f"{debug_msg} not found."
94 result.add_error(AppErrorCode.NOT_FOUND, msg)
95 return result
97 result.generated = model
98 return result
99 except Exception as e:
100 msg = f"Exception retrieving {debug_msg}: {e}"
101 LOG().error(msg)
102 result.add_error(AppErrorCode.DATABASE_EX, msg)
103 return result
105 def update(self, model: W) -> AppResult[W]:
106 """
107 Update a PersistedWrapper with doc_id set that has changes.
109 Args:
110 model: The wrapper instance to update (must have doc_id set)
112 Returns:
113 AppResult with updated wrapper of specific type in .generated if successful, errors otherwise
115 """
116 doc_id = model.doc_id
117 debug_name = f"{self.model_name}:{doc_id}"
118 result = AppResult[W](doc_id=model.doc_id)
120 try:
121 if not model.has_changes:
122 msg = f"No updates found for {debug_name}."
123 LOG().warning(msg)
124 result.add_error(AppErrorCode.UNEXPECTED_CODE_PATH, msg)
125 return result
127 updates = model.get_updates()
128 if IS_DEBUG:
129 LOG().debug(f"Updating {debug_name} with changes: {updates}")
131 updated_model = self._db.update(model.doc_id, updates)
132 if updated_model is None:
133 msg = f"Failed to update {debug_name}."
134 result.add_error(AppErrorCode.DATABASE_EX, msg)
135 return result
137 result.generated = updated_model
138 return result
139 except Exception as e:
140 msg = f"Exception updating {debug_name}: {e}"
141 LOG().error(msg)
142 result.add_error(AppErrorCode.DATABASE_EX, msg)
143 return result
145 def create(self, model: AppBaseModel) -> AppResult[W]:
146 """
147 Create a new wrapper in the database.
149 Args:
150 model: The wrapper instance to create
152 Returns:
153 AppResult with specific wrapper type of created model in .generated if successful, errors otherwise
155 """
156 result = AppResult[W]()
158 try:
159 created_model = self._db.create(model)
160 result.generated = created_model
161 result.doc_id = created_model.doc_id
162 return result
163 except Exception as e:
164 msg = f"Exception creating {self.model_name}: {e}"
165 LOG().error(msg)
166 result.add_error(AppErrorCode.DATABASE_EX, msg)
167 return result
169 def delete(self, doc_id: str) -> AppResult[None]:
170 """
171 Delete a model from the database.
173 Args:
174 doc_id: The document ID to delete
176 Returns:
177 AppResult with success or error status
179 """
180 result = AppResult[None](doc_id=doc_id)
182 try:
183 self._db.delete(doc_id)
184 return result
185 except Exception as e:
186 msg = f"Exception deleting {self.model_name} {doc_id}: {e}"
187 LOG().error(msg)
188 result.add_error(AppErrorCode.DATABASE_EX, msg)
189 return result
191 def exists(self, doc_id: str) -> bool:
192 """
193 Check if a model exists in the database.
195 Args:
196 doc_id: The document ID to check
198 Returns:
199 True if the document exists, False otherwise
201 """
202 try:
203 return self._db.exists(doc_id)
204 except Exception:
205 return False
207 def get_bulk(self, doc_ids: list[str]) -> AppResult[list[W]]:
208 """
209 Get multiple wrappers by their document IDs.
211 Args:
212 doc_ids: List of document IDs to retrieve
214 Returns:
215 AppResult with list of PersistedWrapper in .generated if successful, errors otherwise
217 """
218 result = AppResult[list[W]]()
220 try:
221 models = self._db.get_bulk(doc_ids)
222 result.generated = models
223 return result
224 except Exception as e:
225 msg = f"Exception retrieving bulk {self.model_name}s: {e}"
226 LOG().error(msg)
227 result.add_error(AppErrorCode.DATABASE_EX, msg)
228 return result