mongo-entity-orm 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 mongo-orm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.3
2
+ Name: mongo-entity-orm
3
+ Version: 0.1.0
4
+ Summary: A MongoDB ORM for Python
5
+ License: MIT
6
+ Keywords: mongodb,orm,mongo,async
7
+ Author: Juraj Bezdek
8
+ Author-email: juraj.bezdek@gmail.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Requires-Dist: motor (>=3.7.1,<4.0.0)
20
+ Requires-Dist: pymongo (>=4.13.2,<5.0.0)
21
+ Project-URL: Documentation, https://github.com/ju-bezdek/mongo#readme
22
+ Project-URL: Homepage, https://github.com/ju-bezdek/mongo-orm
23
+ Project-URL: Repository, https://github.com/ju-bezdek/mongo
24
+ Description-Content-Type: text/markdown
25
+
26
+ # mongo-orm
27
+
28
+ A MongoDB ORM for Python
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install mongo-orm
34
+ ```
35
+
36
+ ## Development
37
+
38
+ ```bash
39
+ # Install dependencies
40
+ poetry install
41
+
42
+ # Run tests
43
+ poetry run pytest
44
+
45
+ # Format code
46
+ poetry run black .
47
+ poetry run isort .
48
+
49
+ # Type checking
50
+ poetry run mypy src/
51
+ ```
52
+
53
+ ## License
54
+
55
+ MIT
@@ -0,0 +1,6 @@
1
+ mongo_orm/__init__.py,sha256=0S3o9rReAKbq19D_FdnnsM3-WFP5fbkcs_bTbVbdQF0,205
2
+ mongo_orm/core.py,sha256=acWYPZJdVa4t-4yIObt9vWDN9p8AIziIYb_hSDEj2YI,17806
3
+ mongo_entity_orm-0.1.0.dist-info/LICENSE,sha256=psf-kjUciDGqj7yerybgTki34X9gzXmSpzJZX9VTGSM,1065
4
+ mongo_entity_orm-0.1.0.dist-info/METADATA,sha256=O6DsKXCNdOqLpcsud4frrCkIuog0egeF7a_Ov_JTy1A,1291
5
+ mongo_entity_orm-0.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
6
+ mongo_entity_orm-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
mongo_orm/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """MongoDB ORM for Python."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "Your Name"
5
+ __email__ = "your.email@example.com"
6
+
7
+ from .core import *
8
+
9
+ __all__ = [
10
+ "__version__",
11
+ "__author__",
12
+ "__email__",
13
+ ]
mongo_orm/core.py ADDED
@@ -0,0 +1,562 @@
1
+ import asyncio
2
+ import datetime
3
+ import inspect
4
+ import json
5
+ import logging
6
+ from collections import deque
7
+ import os
8
+ from typing import (
9
+ AsyncIterator,
10
+ Awaitable,
11
+ Callable,
12
+ Generator,
13
+ Iterable,
14
+ List,
15
+ Type,
16
+ TypeVar,
17
+ TypedDict,
18
+ )
19
+ from uuid import NAMESPACE_URL, uuid4, uuid5
20
+ from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection
21
+ from pydantic import BaseModel, Field, PrivateAttr
22
+ from pymongo.collection import Collection
23
+ from pymongo.database import Database as MongoDatabase
24
+ from pymongo.mongo_client import MongoClient
25
+ from pymongo import UpdateOne, InsertOne
26
+
27
+
28
+ class Config:
29
+ @staticmethod
30
+ def MONGODB_URI():
31
+ return os.environ.get("MONGODB_URI", None)
32
+
33
+ @staticmethod
34
+ def MONGO_DATABASE_NAME():
35
+ return os.environ.get("MONGO_DATABASE_NAME", None)
36
+
37
+
38
+ def encode_tenant_id(tenant_id, id):
39
+ return str(uuid5(NAMESPACE_URL, f"{id}/{tenant_id}"))
40
+
41
+
42
+ class SaveResult(TypedDict):
43
+ updated: bool
44
+ inserted: bool
45
+
46
+
47
+ class Repository:
48
+
49
+ def __init__(self, mongo_db: MongoDatabase = None) -> None:
50
+ if mongo_db:
51
+ self.mongo_db = mongo_db
52
+ else:
53
+
54
+ self.mongo_client = MongoClient(
55
+ Config.MONGODB_URI(), tlsAllowInvalidCertificates=True
56
+ )
57
+ self.async_mongo_client = AsyncIOMotorClient(
58
+ Config.MONGODB_URI(), tls=True, tlsAllowInvalidCertificates=True
59
+ )
60
+ self.mongo_client.admin.command("ping")
61
+ self.mongo_db = self.mongo_client[str(Config.MONGO_DATABASE_NAME())]
62
+ self.async_mongo_db = self.async_mongo_client[
63
+ str(Config.MONGO_DATABASE_NAME())
64
+ ]
65
+
66
+
67
+ REPOSITORY = Repository()
68
+
69
+
70
+ T = TypeVar("T", bound="BaseEntity")
71
+
72
+
73
+ class BaseEntity(BaseModel):
74
+ id: str = Field(
75
+ default_factory=lambda: str(uuid4()),
76
+ description="Unique identifier for the entity",
77
+ )
78
+ tenant_id: str = "-"
79
+ _is_new: bool = PrivateAttr(True)
80
+ _is_deleted: bool = PrivateAttr(False)
81
+ _loaded_at: datetime.datetime | None = PrivateAttr(None)
82
+ _tracked_fields_state: dict | None = PrivateAttr(None)
83
+
84
+ def __init__(self, **data) -> None:
85
+ if not hasattr(self.__class__, "collection"):
86
+ raise Exception(
87
+ "collection is not defined... did you forget to decorate the class with @metadata_entity?"
88
+ )
89
+
90
+ super().__init__(**data)
91
+
92
+ def _get_save_callbacks(
93
+ self,
94
+ ) -> list[Callable[["BaseEntity"], None | Awaitable[None]]]:
95
+ return getattr(self.__class__, "_save_callbacks", [])
96
+
97
+ async def _handle_save_callbacks(self):
98
+ awaitable_results = [
99
+ r
100
+ for hook in self._get_save_callbacks()
101
+ if (r := hook(self)) and inspect.isawaitable(r)
102
+ ]
103
+ if awaitable_results:
104
+ results = await asyncio.gather(*awaitable_results, return_exceptions=True)
105
+ for e in results:
106
+ if isinstance(e, Exception):
107
+ logging.error(
108
+ f"Error executing hook for {self.__class__.__name__}: {e}"
109
+ )
110
+
111
+ @classmethod
112
+ async def bulk_save(cls, entities: List["BaseEntity"], skip_callbacks=False):
113
+ if not entities:
114
+ return
115
+ save_payloads = []
116
+ callbacks = []
117
+ results = []
118
+ for entity in entities:
119
+ update_kwargs, callback = entity._prepare_save()
120
+
121
+ save_payloads.append(UpdateOne(**update_kwargs))
122
+ callbacks.append(callback)
123
+
124
+ collection = cls.get_async_collection()
125
+ await collection.bulk_write(save_payloads)
126
+
127
+ @classmethod
128
+ def register_save_callback(
129
+ cls, callback: Callable[["BaseEntity"], None | Awaitable[None]]
130
+ ):
131
+ callbacks = getattr(cls, "_save_callbacks", None)
132
+ if not callbacks:
133
+ callbacks = []
134
+ setattr(cls, "_save_callbacks", callbacks)
135
+ if callback not in callbacks:
136
+ callbacks.append(callback)
137
+
138
+ def _get_self_collection(self) -> Collection:
139
+ return self.collection
140
+
141
+ @classmethod
142
+ def get_collection(cls) -> Collection:
143
+ collection = None
144
+ _cls = cls
145
+ while _cls and collection is None:
146
+ collection = getattr(cls, "collection", None)
147
+ if collection is not None:
148
+ return collection
149
+ _cls = _cls.__base__ if issubclass(_cls.__base__, BaseEntity) else None
150
+
151
+ @classmethod
152
+ def get_async_collection(cls, force_refresh=True) -> AsyncIOMotorCollection:
153
+ if cls.acollection is None or force_refresh:
154
+ # if the collection has not been initialized in wrong event loop, it needs to be reinitialized
155
+ mongo_client = AsyncIOMotorClient(Config.MONGODB_URI())
156
+ db = mongo_client[str(Config.MONGO_DATABASE_NAME())]
157
+ cls.acollection = db[cls.__collection_name__]
158
+
159
+ return cls.acollection
160
+
161
+ def get_id(self) -> str:
162
+ return self.id
163
+
164
+ def save(self):
165
+ update_kwargs, callback = self._prepare_save()
166
+ res = self.get_collection().update_one(**update_kwargs)
167
+ callback(res)
168
+ save_res = SaveResult(
169
+ updated=res.modified_count > 0, inserted=res.upserted_id is not None
170
+ )
171
+ self._after_save(save_res)
172
+ return self
173
+
174
+ def _after_save(self, save_res: SaveResult = None):
175
+ pass
176
+
177
+ async def asave(self):
178
+ update_kwargs, callback = self._prepare_save()
179
+ try:
180
+ res = await self.get_async_collection().update_one(**update_kwargs)
181
+ except RuntimeError as e:
182
+ res = await self.get_async_collection(force_refresh=True).update_one(
183
+ **update_kwargs
184
+ )
185
+ callback(res)
186
+ save_res = SaveResult(
187
+ updated=res.modified_count > 0, inserted=res.upserted_id is not None
188
+ )
189
+
190
+ self._after_save(save_res)
191
+ return self
192
+
193
+ def _prepare_save(self):
194
+ _exclude_fields = []
195
+ payload = self.model_dump(mode="json", exclude=_exclude_fields)
196
+ _id = payload.pop("id", None) or self.get_id()
197
+ if not _id:
198
+ raise Exception(
199
+ "id is not defined. Please generate an id before saving or override get_id() method"
200
+ )
201
+ if hasattr(self, "id"):
202
+ self.id = _id
203
+ payload["_id"] = _id
204
+ payload.pop("created_at", None)
205
+ payload["updated_at"] = datetime.datetime.now(datetime.UTC)
206
+ was_new = self._is_new
207
+ self._is_new = False
208
+ filter_extra = {}
209
+ if getattr(self.__class__, "version_field", None):
210
+ ver_id = str(uuid4())
211
+ if getattr(self, self.__class__.version_field):
212
+ filter_extra[self.__class__.version_field] = getattr(
213
+ self, self.__class__.version_field
214
+ )
215
+ setattr(self, self.__class__.version_field, ver_id)
216
+ payload[self.__class__.version_field] = ver_id
217
+
218
+ update_args = {
219
+ "filter": {"_id": _id, **filter_extra},
220
+ "update": {
221
+ "$set": payload,
222
+ "$setOnInsert": {"created_at": payload["updated_at"]},
223
+ },
224
+ "upsert": True,
225
+ }
226
+
227
+ def callback(res, _was_new=was_new):
228
+ if hasattr(self, "updated_at"):
229
+ setattr(self, "updated_at", payload["updated_at"])
230
+ if res.modified_count == 0 and not _was_new:
231
+
232
+ if getattr(self.__class__, "version_field", None):
233
+ raise Exception(
234
+ f"Save failed for {self.__class__.__name__} with id {_id} and version {ver_id} as it was trying to overwrite a newer version"
235
+ )
236
+ else:
237
+ logging.warning(
238
+ f"No update for {self.__class__.__name__} with id {_id}"
239
+ )
240
+
241
+ if res.upserted_id and hasattr(self, "created_at"):
242
+ setattr(self, "created_at", payload["updated_at"])
243
+
244
+ return update_args, callback
245
+
246
+ def delete(self, ignore_not_found: bool = False):
247
+ if hasattr(self, "id"):
248
+ _id = self.id
249
+ else:
250
+ _id = self.get_id()
251
+ self._is_new = True
252
+ self._is_deleted = True
253
+ res = self.get_collection().delete_one({"_id": _id})
254
+ if res.deleted_count == 0:
255
+ if ignore_not_found:
256
+ logging.warning(
257
+ f"Entity {self.__class__.__name__} with id {_id} not found for deletion"
258
+ )
259
+ else:
260
+ raise Exception(
261
+ f"Entity {self.__class__.__name__} with id {_id} not found for deletion"
262
+ )
263
+
264
+ @classmethod
265
+ def delete_by_id(cls, id, tenant_id: str):
266
+
267
+ filter = {"_id": id}
268
+ if tenant_id != "*":
269
+ filter["tenant_id"] = tenant_id
270
+
271
+ return cls.collection.delete_one(filter).deleted_count == 1
272
+
273
+ @classmethod
274
+ def get(
275
+ cls: Type[T],
276
+ id: str,
277
+ tenant_id: str,
278
+ namespace: str = None,
279
+ raise_not_found: bool = False,
280
+ ) -> T | None:
281
+ filter = {"_id": id}
282
+ if tenant_id != "*":
283
+ filter["tenant_id"] = tenant_id
284
+ if namespace:
285
+ filter["namespace"] = namespace
286
+ data = cls.collection.find_one(filter)
287
+ if not data and raise_not_found:
288
+ raise Exception(
289
+ f"Entity {cls.__name__} with id {id} not found for tenant {tenant_id}"
290
+ )
291
+ res: T = cls.load_entity(data) if data else None
292
+ return res
293
+
294
+ @classmethod
295
+ async def aget(
296
+ cls: Type[T],
297
+ id: str,
298
+ tenant_id: str,
299
+ namespace: str = None,
300
+ raise_not_found: bool = False,
301
+ ) -> T | None:
302
+ filter = {"_id": id}
303
+ if tenant_id != "*":
304
+ filter["tenant_id"] = tenant_id
305
+ if namespace:
306
+ filter["namespace"] = namespace
307
+
308
+ data = await cls.get_async_collection().find_one(filter)
309
+
310
+ if not data and raise_not_found:
311
+ raise Exception(
312
+ f"Entity {cls.__name__} with id {id} not found for tenant {tenant_id}"
313
+ )
314
+ res: T = cls.load_entity(data) if data else None
315
+ return res
316
+
317
+ @classmethod
318
+ def load_entity(cls, raw_data: dict):
319
+ if cls.has_id:
320
+ raw_data["id"] = raw_data.pop("_id")
321
+ elif "_id" in raw_data:
322
+ del raw_data["_id"]
323
+
324
+ if hasattr(cls, "parse_subclass"):
325
+ entity = cls.parse_subclass(raw_data)
326
+ else:
327
+ entity = cls(**raw_data) # cls.delete_by_id(raw_data["id"],"*")
328
+ entity._is_new = False
329
+
330
+ entity._loaded_at = datetime.datetime.now(datetime.UTC)
331
+ return entity
332
+
333
+ @classmethod
334
+ def count(cls, tenant_id: str, filter: dict = None, limit=None) -> int:
335
+
336
+ if not filter:
337
+ _filter = {}
338
+ else:
339
+ _filter = {**filter}
340
+ if tenant_id != "*":
341
+ _filter["tenant_id"] = tenant_id
342
+
343
+ extra = {}
344
+ if limit:
345
+ extra["limit"] = limit
346
+
347
+ return cls.collection.count_documents(_filter, **extra)
348
+
349
+ @classmethod
350
+ async def acount(cls, tenant_id: str, filter: dict = None, limit=None) -> int:
351
+
352
+ if not filter:
353
+ _filter = {}
354
+ else:
355
+ _filter = {**filter}
356
+ if tenant_id != "*":
357
+ _filter["tenant_id"] = tenant_id
358
+
359
+ extra = {}
360
+ if limit:
361
+ extra["limit"] = limit
362
+
363
+ return await cls.get_async_collection().count_documents(_filter, **extra)
364
+
365
+ @classmethod
366
+ async def afind(
367
+ cls: Type[T],
368
+ tenant_id: str,
369
+ namespace: str = None,
370
+ skip: int = 0,
371
+ limit: int = 100,
372
+ order_by: str | list[str] = None,
373
+ filter: dict = None,
374
+ **additional_filters,
375
+ ) -> List[T]:
376
+ if not filter:
377
+ filter = {}
378
+ if tenant_id != "*":
379
+ filter.update({"tenant_id": tenant_id, **additional_filters})
380
+
381
+ filter.update(additional_filters)
382
+ if "id" in filter:
383
+ filter["_id"] = filter.pop("id")
384
+ if namespace:
385
+ filter["namespace"] = namespace
386
+ query = cls.get_async_collection().find(filter)
387
+ if order_by:
388
+ if isinstance(order_by, str):
389
+ order_by = [order_by]
390
+ for ob_key in order_by:
391
+ if ob_key.startswith("-"):
392
+ sort_order = -1
393
+ ob_key = ob_key[1:]
394
+ else:
395
+ sort_order = 1
396
+ query = query.sort([(ob_key, sort_order)])
397
+ raw_items = await query.skip(skip).limit(limit).to_list(limit)
398
+ res: List[T] = []
399
+ for raw_item in raw_items:
400
+ try:
401
+ entity = cls.load_entity(raw_item)
402
+ res.append(entity)
403
+ except Exception as e:
404
+ logging.error(f"Error loading entity {cls.__name__} {raw_item}: {e}")
405
+ return res
406
+
407
+ @classmethod
408
+ def scroll_pages(
409
+ cls: Type[T],
410
+ tenant_id: str,
411
+ filter: dict = None,
412
+ order_by: str = None,
413
+ page_size: int = 100,
414
+ ) -> Iterable[List[T]]:
415
+ page = 0
416
+ while True:
417
+ items = cls.find(
418
+ tenant_id,
419
+ filter=filter,
420
+ skip=page * page_size,
421
+ order_by=order_by,
422
+ limit=page_size,
423
+ )
424
+ if not items:
425
+ break
426
+ yield items
427
+ page += 1
428
+
429
+ @classmethod
430
+ async def ascroll_pages(
431
+ cls: Type[T],
432
+ tenant_id: str,
433
+ filter: dict = None,
434
+ order_by: str = None,
435
+ page_size: int = 100,
436
+ ) -> AsyncIterator[List[T]]:
437
+ page = 0
438
+ while True:
439
+ items = await cls.afind(
440
+ tenant_id,
441
+ filter=filter,
442
+ skip=page * page_size,
443
+ order_by=order_by,
444
+ limit=page_size,
445
+ )
446
+ if not items:
447
+ break
448
+ yield items
449
+ page += 1
450
+
451
+ @classmethod
452
+ def find(
453
+ cls: Type[T],
454
+ tenant_id: str,
455
+ namespace: str = None,
456
+ skip: int = 0,
457
+ limit: int | None = 100,
458
+ order_by: str | list[str] = None,
459
+ filter: dict = None,
460
+ **additional_filters,
461
+ ) -> List[T]:
462
+ if not filter:
463
+ filter = {}
464
+ else:
465
+ filter = {**filter}
466
+ if tenant_id != "*":
467
+ filter.update({"tenant_id": tenant_id, **additional_filters})
468
+
469
+ filter.update(additional_filters)
470
+ if "id" in filter:
471
+ filter["_id"] = filter.pop("id")
472
+ if namespace:
473
+ filter["namespace"] = namespace
474
+ query = cls.get_collection().find(filter)
475
+ if order_by:
476
+ if isinstance(order_by, str):
477
+ order_by = [order_by]
478
+ for ob_key in order_by:
479
+ if ob_key.startswith("-"):
480
+ sort_order = -1
481
+ ob_key = ob_key[1:]
482
+ else:
483
+ sort_order = 1
484
+ query = query.sort([(ob_key, sort_order)])
485
+
486
+ if limit is None:
487
+ limit = 100
488
+
489
+ raw_items = list(query.skip(skip or 0).limit(limit or 100))
490
+ res: List[T] = []
491
+ for raw_item in raw_items:
492
+ try:
493
+ entity = cls.load_entity(raw_item)
494
+ res.append(entity)
495
+ except Exception as e:
496
+ logging.error(f"Error loading entity {cls.__name__} {raw_item}: {e}")
497
+ return res
498
+
499
+ @classmethod
500
+ def find_first(cls, tenant_id, filter=None, order_by=None, **kwargs):
501
+ kwargs.pop("limit", None)
502
+ items = cls.find(tenant_id, filter=filter, order_by=order_by, limit=1, **kwargs)
503
+ return items[0] if items else None
504
+
505
+ @classmethod
506
+ async def afind_first(cls, tenant_id, filter=None, order_by=None, **kwargs):
507
+ items = await cls.afind(
508
+ tenant_id, filter=filter, order_by=order_by, limit=1, **kwargs
509
+ )
510
+ return items[0] if items else None
511
+
512
+ def update(
513
+ self, auto_save: bool = False, raise_on_missing: bool = True, **to_update
514
+ ):
515
+ for k, v in to_update.items():
516
+ if hasattr(self, k):
517
+ setattr(self, k, v)
518
+ elif raise_on_missing:
519
+ raise ValueError(f"Attribute {k} doesn't exist on {self.__class__}")
520
+ if auto_save:
521
+ self.save()
522
+ return self
523
+
524
+ async def aupdate(
525
+ self, auto_save: bool = False, raise_on_missing: bool = True, **to_update
526
+ ):
527
+ self.update(auto_save=False, raise_on_missing=raise_on_missing, **to_update)
528
+ if auto_save:
529
+ await self.asave()
530
+ return self
531
+
532
+ # def __delete__(self):
533
+ # if self._is_new:
534
+ # self.save()
535
+
536
+
537
+ def entity(
538
+ collection_name: str, version_field: str = None, tracked_fields: List[str] = None
539
+ ):
540
+ def decorator(cls: Type[T]) -> Type[T]:
541
+ if Config.MONGODB_URI() is None:
542
+ raise Exception(
543
+ "MONGODB_URI is not set in the environment variables. Please set it to connect to MongoDB."
544
+ )
545
+ if Config.MONGO_DATABASE_NAME() is None:
546
+ raise Exception(
547
+ "MONGO_DATABASE_NAME is not set in the environment variables. Please set it to connect to MongoDB."
548
+ )
549
+ cls.__collection_name__ = collection_name
550
+ cls.collection = REPOSITORY.mongo_db[collection_name]
551
+
552
+ cls.acollection = None
553
+ if "id" in cls.model_fields:
554
+ cls.has_id = True
555
+ else:
556
+ cls.has_id = False
557
+
558
+ cls.version_field = version_field
559
+
560
+ return cls
561
+
562
+ return decorator