database-mongodb-local 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- database_mongodb_local/__init__.py +0 -0
- database_mongodb_local/src/__init__.py +0 -0
- database_mongodb_local/src/connector.py +51 -0
- database_mongodb_local/src/constants_src.py +24 -0
- database_mongodb_local/src/decorators.py +27 -0
- database_mongodb_local/src/exceptions.py +14 -0
- database_mongodb_local/src/generic_crud.py +341 -0
- database_mongodb_local/src/generic_crud_ml.py +317 -0
- database_mongodb_local/src/generic_mapping.py +135 -0
- database_mongodb_local/src/version.py +5 -0
- database_mongodb_local/tests/__init__.py +0 -0
- database_mongodb_local/tests/generic_crud_ml_system_test.py +99 -0
- database_mongodb_local/tests/generic_crud_ml_test.py +264 -0
- database_mongodb_local/tests/generic_crud_system_test.py +76 -0
- database_mongodb_local/tests/generic_crud_test.py +232 -0
- database_mongodb_local/tests/generic_mapping_system_test.py +69 -0
- database_mongodb_local/tests/generic_mapping_test.py +78 -0
- database_mongodb_local-0.0.1.dist-info/METADATA +24 -0
- database_mongodb_local-0.0.1.dist-info/RECORD +21 -0
- database_mongodb_local-0.0.1.dist-info/WHEEL +5 -0
- database_mongodb_local-0.0.1.dist-info/top_level.txt +1 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
|
|
4
|
+
from dotenv import load_dotenv
|
|
5
|
+
from pymongo import MongoClient
|
|
6
|
+
from pymongo.database import Database
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
DEFAULT_HOST = "localhost"
|
|
11
|
+
DEFAULT_PORT = 27017
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _is_mock() -> bool:
|
|
15
|
+
return os.getenv("MONGODB_MOCK", "false").lower() in ("1", "true", "yes")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@lru_cache(maxsize=None)
|
|
19
|
+
def _get_real_client() -> MongoClient:
|
|
20
|
+
host = os.getenv("MONGODB_HOST", DEFAULT_HOST)
|
|
21
|
+
port = int(os.getenv("MONGODB_PORT", DEFAULT_PORT))
|
|
22
|
+
username = os.getenv("MONGODB_USERNAME")
|
|
23
|
+
password = os.getenv("MONGODB_PASSWORD")
|
|
24
|
+
|
|
25
|
+
if username and password:
|
|
26
|
+
return MongoClient(host=host, port=port, username=username, password=password)
|
|
27
|
+
return MongoClient(host=host, port=port)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Connector:
|
|
31
|
+
"""Single owner of MongoDB connection creation (information hiding).
|
|
32
|
+
|
|
33
|
+
Callers never build a client themselves — they ask Connector for a
|
|
34
|
+
database. When ``MONGODB_MOCK`` is set, a fresh in-memory mongomock
|
|
35
|
+
database is returned so tests run without a real MongoDB.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_database(database_name: str) -> Database:
|
|
40
|
+
if _is_mock():
|
|
41
|
+
import mongomock
|
|
42
|
+
return mongomock.MongoClient()[database_name]
|
|
43
|
+
return _get_real_client()[database_name]
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def is_mongo_available() -> bool:
|
|
47
|
+
try:
|
|
48
|
+
_get_real_client().admin.command("ping")
|
|
49
|
+
return True
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from logger_local.LoggerComponentEnum import LoggerComponentEnum
|
|
2
|
+
|
|
3
|
+
DEVELOPER_EMAIL_ADDRESS = "dor3382@gmail.com"
|
|
4
|
+
|
|
5
|
+
CRUD_MONGODB_CODE_LOGGER_OBJECT = {
|
|
6
|
+
"component_id": 1,
|
|
7
|
+
"component_name": "database-mongodb-local/GenericCrudMongodb",
|
|
8
|
+
"component_category": LoggerComponentEnum.ComponentCategory.Code.value,
|
|
9
|
+
"developer_email_address": DEVELOPER_EMAIL_ADDRESS,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
CRUD_ML_CODE_LOGGER_OBJECT = {
|
|
13
|
+
"component_id": 2,
|
|
14
|
+
"component_name": "database-mongodb-local/GenericCrudMlMongoDB",
|
|
15
|
+
"component_category": LoggerComponentEnum.ComponentCategory.Code.value,
|
|
16
|
+
"developer_email_address": DEVELOPER_EMAIL_ADDRESS,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
MAPPING_CODE_LOGGER_OBJECT = {
|
|
20
|
+
"component_id": 3,
|
|
21
|
+
"component_name": "database-mongodb-local/GenericMappingMongodb",
|
|
22
|
+
"component_category": LoggerComponentEnum.ComponentCategory.Code.value,
|
|
23
|
+
"developer_email_address": DEVELOPER_EMAIL_ADDRESS,
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from pymongo.errors import (
|
|
4
|
+
ConnectionFailure,
|
|
5
|
+
DuplicateKeyError,
|
|
6
|
+
PyMongoError,
|
|
7
|
+
ServerSelectionTimeoutError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from .exceptions import DatabaseConnectionError, DatabaseDuplicateError, DatabaseOperationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_db_errors(func):
|
|
14
|
+
@functools.wraps(func)
|
|
15
|
+
def wrapper(*args, **kwargs):
|
|
16
|
+
try:
|
|
17
|
+
return func(*args, **kwargs)
|
|
18
|
+
except DuplicateKeyError as e:
|
|
19
|
+
raise DatabaseDuplicateError(
|
|
20
|
+
f"Duplicate key error in {func.__name__}: {e}") from e
|
|
21
|
+
except (ConnectionFailure, ServerSelectionTimeoutError) as e:
|
|
22
|
+
raise DatabaseConnectionError(
|
|
23
|
+
f"Connection error in {func.__name__}: {e}") from e
|
|
24
|
+
except PyMongoError as e:
|
|
25
|
+
raise DatabaseOperationError(
|
|
26
|
+
f"Database error in {func.__name__}: {e}") from e
|
|
27
|
+
return wrapper
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from bson import ObjectId
|
|
5
|
+
from pymongo.collection import Collection
|
|
6
|
+
from pymongo.errors import DuplicateKeyError
|
|
7
|
+
|
|
8
|
+
from logger_local.MetaLogger import ABCMetaLogger
|
|
9
|
+
|
|
10
|
+
from database_infrastructure_local.generic_crud_abstract import GenericCrudAbstract
|
|
11
|
+
|
|
12
|
+
from .connector import Connector
|
|
13
|
+
from .constants_src import CRUD_MONGODB_CODE_LOGGER_OBJECT
|
|
14
|
+
from .decorators import handle_db_errors
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GenericCrudMongodb(GenericCrudAbstract, metaclass=ABCMetaLogger,
|
|
18
|
+
object=CRUD_MONGODB_CODE_LOGGER_OBJECT):
|
|
19
|
+
"""MongoDB implementation of the shared GenericCrud interface.
|
|
20
|
+
|
|
21
|
+
To keep the interface consistent across MySQL, PostgreSQL and MongoDB,
|
|
22
|
+
this class uses the same method names and parameters as the MySQL DAL:
|
|
23
|
+
``schema_name`` maps to a MongoDB database and ``table_name`` maps to a
|
|
24
|
+
MongoDB collection.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, *,
|
|
28
|
+
default_schema_name: str,
|
|
29
|
+
default_table_name: str,
|
|
30
|
+
is_test_data: bool = False) -> None:
|
|
31
|
+
self.default_schema_name = default_schema_name
|
|
32
|
+
self.default_table_name = default_table_name
|
|
33
|
+
self.is_test_data = is_test_data
|
|
34
|
+
self._db = Connector.get_database(default_schema_name)
|
|
35
|
+
|
|
36
|
+
def _collection(self, table_name: str = None) -> Collection:
|
|
37
|
+
return self._db[table_name or self.default_table_name]
|
|
38
|
+
|
|
39
|
+
def _live_filter(self, extra: dict = None) -> dict:
|
|
40
|
+
"""Returns a filter that excludes soft-deleted documents.
|
|
41
|
+
|
|
42
|
+
All select/update/delete operations automatically apply this filter —
|
|
43
|
+
documents with a non-null end_timestamp are invisible to callers.
|
|
44
|
+
Use undelete_by_id to restore a soft-deleted document.
|
|
45
|
+
"""
|
|
46
|
+
f = dict(extra or {})
|
|
47
|
+
f["end_timestamp"] = None
|
|
48
|
+
return f
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------ insert
|
|
51
|
+
|
|
52
|
+
@handle_db_errors
|
|
53
|
+
def insert(self, *, schema_name: str = None, table_name: str = None,
|
|
54
|
+
data_dict: dict = None,
|
|
55
|
+
ignore_duplicate: bool = False,
|
|
56
|
+
commit_changes: bool = True) -> Optional[ObjectId]:
|
|
57
|
+
table_name = table_name or self.default_table_name
|
|
58
|
+
data_dict = dict(data_dict or {})
|
|
59
|
+
data_dict.setdefault("end_timestamp", None)
|
|
60
|
+
data_dict.setdefault("is_test_data", self.is_test_data)
|
|
61
|
+
data_dict["created_timestamp"] = datetime.now(timezone.utc)
|
|
62
|
+
data_dict["updated_timestamp"] = datetime.now(timezone.utc)
|
|
63
|
+
try:
|
|
64
|
+
result = self._collection(table_name).insert_one(data_dict)
|
|
65
|
+
return result.inserted_id
|
|
66
|
+
except DuplicateKeyError:
|
|
67
|
+
if ignore_duplicate:
|
|
68
|
+
return None
|
|
69
|
+
raise
|
|
70
|
+
|
|
71
|
+
@handle_db_errors
|
|
72
|
+
def insert_if_not_exists_by_field(self, *, table_name: str = None,
|
|
73
|
+
field: str,
|
|
74
|
+
data_dict: dict) -> Optional[ObjectId]:
|
|
75
|
+
table_name = table_name or self.default_table_name
|
|
76
|
+
existing = self._collection(table_name).find_one(
|
|
77
|
+
self._live_filter({field: data_dict[field]}), {"_id": 1})
|
|
78
|
+
if existing:
|
|
79
|
+
return None
|
|
80
|
+
return self.insert(table_name=table_name, data_dict=data_dict)
|
|
81
|
+
|
|
82
|
+
@handle_db_errors
|
|
83
|
+
def insert_if_not_exists(self, *, table_name: str = None,
|
|
84
|
+
data_dict: dict = None,
|
|
85
|
+
data_dict_compare: dict = None) -> Optional[ObjectId]:
|
|
86
|
+
table_name = table_name or self.default_table_name
|
|
87
|
+
data_dict_compare = data_dict_compare or data_dict
|
|
88
|
+
existing = self._collection(table_name).find_one(
|
|
89
|
+
self._live_filter(data_dict_compare), {"_id": 1})
|
|
90
|
+
if existing:
|
|
91
|
+
return existing["_id"]
|
|
92
|
+
return self.insert(table_name=table_name, data_dict=data_dict)
|
|
93
|
+
|
|
94
|
+
@handle_db_errors
|
|
95
|
+
def insert_many(self, *, table_name: str = None,
|
|
96
|
+
data_dicts: list[dict]) -> int:
|
|
97
|
+
if not data_dicts:
|
|
98
|
+
return 0
|
|
99
|
+
table_name = table_name or self.default_table_name
|
|
100
|
+
now = datetime.now(timezone.utc)
|
|
101
|
+
docs = [dict(d) for d in data_dicts]
|
|
102
|
+
for doc in docs:
|
|
103
|
+
doc.setdefault("end_timestamp", None)
|
|
104
|
+
doc.setdefault("is_test_data", self.is_test_data)
|
|
105
|
+
doc.setdefault("created_timestamp", now)
|
|
106
|
+
doc.setdefault("updated_timestamp", now)
|
|
107
|
+
result = self._collection(table_name).insert_many(docs)
|
|
108
|
+
return len(result.inserted_ids)
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------ upsert
|
|
111
|
+
|
|
112
|
+
@handle_db_errors
|
|
113
|
+
def upsert(self, *, table_name: str = None,
|
|
114
|
+
data_dict: dict = None,
|
|
115
|
+
data_dict_compare: dict = None) -> Optional[ObjectId]:
|
|
116
|
+
table_name = table_name or self.default_table_name
|
|
117
|
+
data_dict = dict(data_dict or {})
|
|
118
|
+
data_dict["updated_timestamp"] = datetime.now(timezone.utc)
|
|
119
|
+
|
|
120
|
+
if not data_dict_compare:
|
|
121
|
+
return self.insert(table_name=table_name, data_dict=data_dict)
|
|
122
|
+
|
|
123
|
+
existing = self._collection(table_name).find_one(
|
|
124
|
+
self._live_filter(data_dict_compare), {"_id": 1})
|
|
125
|
+
if existing:
|
|
126
|
+
self._collection(table_name).update_one(
|
|
127
|
+
{"_id": existing["_id"]}, {"$set": data_dict})
|
|
128
|
+
return existing["_id"]
|
|
129
|
+
return self.insert(table_name=table_name, data_dict=data_dict)
|
|
130
|
+
|
|
131
|
+
# ------------------------------------------------------------------ update
|
|
132
|
+
|
|
133
|
+
@handle_db_errors
|
|
134
|
+
def update_by_id(self, *, table_name: str = None,
|
|
135
|
+
document_id: ObjectId,
|
|
136
|
+
data_dict: dict) -> int:
|
|
137
|
+
return self.update_by_where(
|
|
138
|
+
table_name=table_name,
|
|
139
|
+
where={"_id": document_id},
|
|
140
|
+
data_dict=data_dict)
|
|
141
|
+
|
|
142
|
+
@handle_db_errors
|
|
143
|
+
def update_by_column_and_value(self, *, table_name: str = None,
|
|
144
|
+
column_name: str,
|
|
145
|
+
column_value: Any,
|
|
146
|
+
data_dict: dict) -> int:
|
|
147
|
+
return self.update_by_where(
|
|
148
|
+
table_name=table_name,
|
|
149
|
+
where={column_name: column_value},
|
|
150
|
+
data_dict=data_dict)
|
|
151
|
+
|
|
152
|
+
@handle_db_errors
|
|
153
|
+
def update_by_where(self, *, table_name: str = None,
|
|
154
|
+
where: dict,
|
|
155
|
+
data_dict: dict) -> int:
|
|
156
|
+
"""``where`` is a MongoDB filter dict (the MongoDB equivalent of a SQL WHERE clause)."""
|
|
157
|
+
table_name = table_name or self.default_table_name
|
|
158
|
+
data_dict = dict(data_dict)
|
|
159
|
+
data_dict["updated_timestamp"] = datetime.now(timezone.utc)
|
|
160
|
+
result = self._collection(table_name).update_many(
|
|
161
|
+
self._live_filter(where), {"$set": data_dict})
|
|
162
|
+
return result.modified_count
|
|
163
|
+
|
|
164
|
+
# ------------------------------------------------------------------ delete
|
|
165
|
+
|
|
166
|
+
@handle_db_errors
|
|
167
|
+
def delete_by_id(self, *, table_name: str = None,
|
|
168
|
+
document_id: ObjectId) -> int:
|
|
169
|
+
return self.delete_by_where(
|
|
170
|
+
table_name=table_name,
|
|
171
|
+
where={"_id": document_id})
|
|
172
|
+
|
|
173
|
+
@handle_db_errors
|
|
174
|
+
def delete_by_column_and_value(self, *, table_name: str = None,
|
|
175
|
+
column_name: str,
|
|
176
|
+
column_value: Any) -> int:
|
|
177
|
+
return self.delete_by_where(
|
|
178
|
+
table_name=table_name,
|
|
179
|
+
where={column_name: column_value})
|
|
180
|
+
|
|
181
|
+
@handle_db_errors
|
|
182
|
+
def delete_by_where(self, *, table_name: str = None,
|
|
183
|
+
where: dict) -> int:
|
|
184
|
+
"""``where`` is a MongoDB filter dict (the MongoDB equivalent of a SQL WHERE clause)."""
|
|
185
|
+
table_name = table_name or self.default_table_name
|
|
186
|
+
result = self._collection(table_name).update_many(
|
|
187
|
+
self._live_filter(where),
|
|
188
|
+
{"$set": {"end_timestamp": datetime.now(timezone.utc)}})
|
|
189
|
+
return result.modified_count
|
|
190
|
+
|
|
191
|
+
@handle_db_errors
|
|
192
|
+
def undelete_by_id(self, *, table_name: str = None,
|
|
193
|
+
document_id: ObjectId) -> int:
|
|
194
|
+
table_name = table_name or self.default_table_name
|
|
195
|
+
result = self._collection(table_name).update_one(
|
|
196
|
+
{"_id": document_id}, {"$set": {"end_timestamp": None}})
|
|
197
|
+
return result.modified_count
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------ select
|
|
200
|
+
|
|
201
|
+
@handle_db_errors
|
|
202
|
+
def select_one_dict_by_id(self, *,
|
|
203
|
+
table_name: str = None,
|
|
204
|
+
document_id: ObjectId) -> Optional[dict]:
|
|
205
|
+
return self.select_one_dict_by_where(
|
|
206
|
+
table_name=table_name,
|
|
207
|
+
where={"_id": document_id})
|
|
208
|
+
|
|
209
|
+
@handle_db_errors
|
|
210
|
+
def select_one_dict_by_column_and_value(self, *,
|
|
211
|
+
table_name: str = None,
|
|
212
|
+
column_name: str,
|
|
213
|
+
column_value: Any,
|
|
214
|
+
fields: dict = None) -> Optional[dict]:
|
|
215
|
+
return self.select_one_dict_by_where(
|
|
216
|
+
table_name=table_name,
|
|
217
|
+
where={column_name: column_value},
|
|
218
|
+
fields=fields)
|
|
219
|
+
|
|
220
|
+
@handle_db_errors
|
|
221
|
+
def select_one_dict_by_where(self, *,
|
|
222
|
+
table_name: str = None,
|
|
223
|
+
where: dict = None,
|
|
224
|
+
fields: dict = None) -> Optional[dict]:
|
|
225
|
+
"""``where`` is a MongoDB filter dict (the MongoDB equivalent of a SQL WHERE clause)."""
|
|
226
|
+
table_name = table_name or self.default_table_name
|
|
227
|
+
return self._collection(table_name).find_one(
|
|
228
|
+
self._live_filter(where or {}), fields)
|
|
229
|
+
|
|
230
|
+
@handle_db_errors
|
|
231
|
+
def select_multi_dict_by_column_and_value(self, *,
|
|
232
|
+
table_name: str = None,
|
|
233
|
+
column_name: str,
|
|
234
|
+
column_value: Any,
|
|
235
|
+
fields: dict = None,
|
|
236
|
+
limit: int = 0,
|
|
237
|
+
order_by: list = None) -> list[dict]:
|
|
238
|
+
return self.select_multi_dict_by_where(
|
|
239
|
+
table_name=table_name,
|
|
240
|
+
where={column_name: column_value},
|
|
241
|
+
fields=fields, limit=limit, order_by=order_by)
|
|
242
|
+
|
|
243
|
+
@handle_db_errors
|
|
244
|
+
def select_multi_dict_by_where(self, *,
|
|
245
|
+
table_name: str = None,
|
|
246
|
+
where: dict = None,
|
|
247
|
+
fields: dict = None,
|
|
248
|
+
limit: int = 0,
|
|
249
|
+
order_by: list = None) -> list[dict]:
|
|
250
|
+
"""``where`` is a MongoDB filter dict (the MongoDB equivalent of a SQL WHERE clause)."""
|
|
251
|
+
table_name = table_name or self.default_table_name
|
|
252
|
+
cursor = self._collection(table_name).find(
|
|
253
|
+
self._live_filter(where or {}), fields)
|
|
254
|
+
if order_by:
|
|
255
|
+
cursor = cursor.sort(order_by)
|
|
256
|
+
if limit:
|
|
257
|
+
cursor = cursor.limit(limit)
|
|
258
|
+
return list(cursor)
|
|
259
|
+
|
|
260
|
+
@handle_db_errors
|
|
261
|
+
def select_one_value_by_column_and_value(self, *,
|
|
262
|
+
table_name: str = None,
|
|
263
|
+
column_name: str,
|
|
264
|
+
column_value: Any,
|
|
265
|
+
field: str) -> Any:
|
|
266
|
+
return self.select_one_value_by_where(
|
|
267
|
+
table_name=table_name,
|
|
268
|
+
where={column_name: column_value},
|
|
269
|
+
field=field)
|
|
270
|
+
|
|
271
|
+
@handle_db_errors
|
|
272
|
+
def select_one_value_by_where(self, *,
|
|
273
|
+
table_name: str = None,
|
|
274
|
+
where: dict = None,
|
|
275
|
+
field: str) -> Any:
|
|
276
|
+
doc = self.select_one_dict_by_where(
|
|
277
|
+
table_name=table_name,
|
|
278
|
+
where=where,
|
|
279
|
+
fields={field: 1})
|
|
280
|
+
return doc.get(field) if doc else None
|
|
281
|
+
|
|
282
|
+
@handle_db_errors
|
|
283
|
+
def select_multi_value_by_column_and_value(self, *,
|
|
284
|
+
table_name: str = None,
|
|
285
|
+
column_name: str,
|
|
286
|
+
column_value: Any,
|
|
287
|
+
field: str,
|
|
288
|
+
limit: int = 0) -> list:
|
|
289
|
+
return self.select_multi_value_by_where(
|
|
290
|
+
table_name=table_name,
|
|
291
|
+
where={column_name: column_value},
|
|
292
|
+
field=field, limit=limit)
|
|
293
|
+
|
|
294
|
+
@handle_db_errors
|
|
295
|
+
def select_multi_value_by_where(self, *,
|
|
296
|
+
table_name: str = None,
|
|
297
|
+
where: dict = None,
|
|
298
|
+
field: str,
|
|
299
|
+
limit: int = 0) -> list:
|
|
300
|
+
docs = self.select_multi_dict_by_where(
|
|
301
|
+
table_name=table_name,
|
|
302
|
+
where=where,
|
|
303
|
+
fields={field: 1},
|
|
304
|
+
limit=limit)
|
|
305
|
+
return [doc.get(field) for doc in docs if field in doc]
|
|
306
|
+
|
|
307
|
+
@handle_db_errors
|
|
308
|
+
def get_num_of_rows(self, *, table_name: str = None,
|
|
309
|
+
where: dict = None) -> int:
|
|
310
|
+
table_name = table_name or self.default_table_name
|
|
311
|
+
return self._collection(table_name).count_documents(
|
|
312
|
+
self._live_filter(where or {}))
|
|
313
|
+
|
|
314
|
+
@handle_db_errors
|
|
315
|
+
def foreach(self, *,
|
|
316
|
+
function: callable,
|
|
317
|
+
table_name: str = None,
|
|
318
|
+
where: dict = None,
|
|
319
|
+
batch_size: int = 100) -> None:
|
|
320
|
+
table_name = table_name or self.default_table_name
|
|
321
|
+
skip = 0
|
|
322
|
+
while True:
|
|
323
|
+
batch = list(self._collection(table_name).find(
|
|
324
|
+
self._live_filter(where or {}),
|
|
325
|
+
skip=skip,
|
|
326
|
+
limit=batch_size))
|
|
327
|
+
if not batch:
|
|
328
|
+
break
|
|
329
|
+
for doc in batch:
|
|
330
|
+
function(doc)
|
|
331
|
+
skip += batch_size
|
|
332
|
+
|
|
333
|
+
@handle_db_errors
|
|
334
|
+
def merge_entities(self, *,
|
|
335
|
+
entity_id1: ObjectId,
|
|
336
|
+
entity_id2: ObjectId,
|
|
337
|
+
table_name: str = None) -> None:
|
|
338
|
+
self.delete_by_id(document_id=entity_id1, table_name=table_name)
|
|
339
|
+
|
|
340
|
+
def close(self) -> None:
|
|
341
|
+
pass
|