panther 3.8.2__py3-none-any.whl → 4.0.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.
Files changed (52) hide show
  1. panther/__init__.py +1 -1
  2. panther/_load_configs.py +168 -171
  3. panther/_utils.py +26 -49
  4. panther/app.py +85 -105
  5. panther/authentications.py +86 -55
  6. panther/background_tasks.py +25 -14
  7. panther/base_request.py +38 -14
  8. panther/base_websocket.py +172 -94
  9. panther/caching.py +60 -25
  10. panther/cli/create_command.py +20 -10
  11. panther/cli/monitor_command.py +63 -37
  12. panther/cli/template.py +40 -20
  13. panther/cli/utils.py +32 -18
  14. panther/configs.py +65 -58
  15. panther/db/connections.py +139 -0
  16. panther/db/cursor.py +43 -0
  17. panther/db/models.py +64 -29
  18. panther/db/queries/__init__.py +1 -1
  19. panther/db/queries/base_queries.py +127 -0
  20. panther/db/queries/mongodb_queries.py +77 -38
  21. panther/db/queries/pantherdb_queries.py +59 -30
  22. panther/db/queries/queries.py +232 -117
  23. panther/db/utils.py +17 -18
  24. panther/events.py +44 -0
  25. panther/exceptions.py +26 -12
  26. panther/file_handler.py +2 -2
  27. panther/generics.py +163 -0
  28. panther/logging.py +7 -2
  29. panther/main.py +111 -188
  30. panther/middlewares/base.py +3 -0
  31. panther/monitoring.py +8 -5
  32. panther/pagination.py +48 -0
  33. panther/panel/apis.py +32 -5
  34. panther/panel/urls.py +2 -1
  35. panther/permissions.py +3 -3
  36. panther/request.py +6 -13
  37. panther/response.py +114 -34
  38. panther/routings.py +83 -66
  39. panther/serializer.py +214 -33
  40. panther/test.py +31 -21
  41. panther/utils.py +28 -16
  42. panther/websocket.py +7 -4
  43. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/METADATA +93 -71
  44. panther-4.0.0.dist-info/RECORD +57 -0
  45. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/WHEEL +1 -1
  46. panther/db/connection.py +0 -92
  47. panther/middlewares/db.py +0 -18
  48. panther/middlewares/redis.py +0 -47
  49. panther-3.8.2.dist-info/RECORD +0 -54
  50. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/LICENSE +0 -0
  51. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/entry_points.txt +0 -0
  52. {panther-3.8.2.dist-info → panther-4.0.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,12 @@
1
1
  from sys import version_info
2
+ from typing import Iterable
2
3
 
3
- from panther.db.connection import db
4
- from panther.db.utils import merge_dicts, prepare_id_for_query
4
+ from pantherdb import Cursor
5
+
6
+ from panther.db.connections import db
7
+ from panther.db.queries.base_queries import BaseQuery
8
+ from panther.db.utils import prepare_id_for_query
9
+ from panther.exceptions import DatabaseError
5
10
 
6
11
  if version_info >= (3, 11):
7
12
  from typing import Self
@@ -11,71 +16,95 @@ else:
11
16
  Self = TypeVar('Self', bound='BasePantherDBQuery')
12
17
 
13
18
 
14
- class BasePantherDBQuery:
19
+ class BasePantherDBQuery(BaseQuery):
15
20
  @classmethod
16
- def _merge(cls, *args) -> dict:
17
- prepare_id_for_query(*args)
18
- return merge_dicts(*args)
21
+ def _merge(cls, *args, is_mongo: bool = False) -> dict:
22
+ return super()._merge(*args, is_mongo=is_mongo)
19
23
 
20
24
  # # # # # Find # # # # #
21
25
  @classmethod
22
- def find_one(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
23
- if document := db.session.collection(cls.__name__).find_one(**cls._merge(_data, kwargs)):
26
+ async def find_one(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
27
+ if document := db.session.collection(cls.__name__).find_one(**cls._merge(_filter, kwargs)):
24
28
  return cls._create_model_instance(document=document)
25
29
  return None
26
30
 
27
31
  @classmethod
28
- def find(cls, _data: dict | None = None, /, **kwargs) -> list[Self]:
29
- documents = db.session.collection(cls.__name__).find(**cls._merge(_data, kwargs))
30
- return [cls._create_model_instance(document=document) for document in documents]
32
+ async def find(cls, _filter: dict | None = None, /, **kwargs) -> Cursor:
33
+ cursor = db.session.collection(cls.__name__).find(**cls._merge(_filter, kwargs))
34
+ cursor.response_type = cls._create_model_instance
35
+ cursor.cls = cls
36
+ return cursor
31
37
 
32
38
  @classmethod
33
- def first(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
34
- if document := db.session.collection(cls.__name__).first(**cls._merge(_data, kwargs)):
39
+ async def first(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
40
+ if document := db.session.collection(cls.__name__).first(**cls._merge(_filter, kwargs)):
35
41
  return cls._create_model_instance(document=document)
36
42
  return None
37
43
 
38
44
  @classmethod
39
- def last(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
40
- if document := db.session.collection(cls.__name__).last(**cls._merge(_data, kwargs)):
45
+ async def last(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
46
+ if document := db.session.collection(cls.__name__).last(**cls._merge(_filter, kwargs)):
41
47
  return cls._create_model_instance(document=document)
42
48
  return None
43
49
 
50
+ @classmethod
51
+ async def aggregate(cls, *args, **kwargs):
52
+ msg = 'aggregate() does not supported in `PantherDB`.'
53
+ raise DatabaseError(msg) from None
54
+
44
55
  # # # # # Count # # # # #
45
56
  @classmethod
46
- def count(cls, _data: dict | None = None, /, **kwargs) -> int:
47
- return db.session.collection(cls.__name__).count(**cls._merge(_data, kwargs))
57
+ async def count(cls, _filter: dict | None = None, /, **kwargs) -> int:
58
+ return db.session.collection(cls.__name__).count(**cls._merge(_filter, kwargs))
48
59
 
49
60
  # # # # # Insert # # # # #
50
61
  @classmethod
51
- def insert_one(cls, _data: dict | None = None, **kwargs) -> Self:
52
- document = db.session.collection(cls.__name__).insert_one(**cls._merge(_data, kwargs))
62
+ async def insert_one(cls, _document: dict | None = None, /, **kwargs) -> Self:
63
+ _document = cls._merge(_document, kwargs)
64
+ cls._validate_data(data=_document)
65
+
66
+ document = db.session.collection(cls.__name__).insert_one(**_document)
53
67
  return cls._create_model_instance(document=document)
54
68
 
69
+ @classmethod
70
+ async def insert_many(cls, documents: Iterable[dict]) -> list[Self]:
71
+ result = []
72
+ for document in documents:
73
+ prepare_id_for_query(document, is_mongo=False)
74
+ cls._validate_data(data=document)
75
+ inserted_document = db.session.collection(cls.__name__).insert_one(**document)
76
+ document['_id'] = inserted_document['_id']
77
+ result.append(cls._create_model_instance(document=document))
78
+ return result
79
+
55
80
  # # # # # Delete # # # # #
56
- def delete(self) -> None:
81
+ async def delete(self) -> None:
57
82
  db.session.collection(self.__class__.__name__).delete_one(_id=self._id)
58
83
 
59
84
  @classmethod
60
- def delete_one(cls, _data: dict | None = None, /, **kwargs) -> bool:
61
- return db.session.collection(cls.__name__).delete_one(**cls._merge(_data, kwargs))
85
+ async def delete_one(cls, _filter: dict | None = None, /, **kwargs) -> bool:
86
+ return db.session.collection(cls.__name__).delete_one(**cls._merge(_filter, kwargs))
62
87
 
63
88
  @classmethod
64
- def delete_many(cls, _data: dict | None = None, /, **kwargs) -> int:
65
- return db.session.collection(cls.__name__).delete_many(**cls._merge(_data, kwargs))
89
+ async def delete_many(cls, _filter: dict | None = None, /, **kwargs) -> int:
90
+ return db.session.collection(cls.__name__).delete_many(**cls._merge(_filter, kwargs))
66
91
 
67
92
  # # # # # Update # # # # #
68
- def update(self, **kwargs) -> None:
69
- for field, value in kwargs.items():
93
+ async def update(self, _update: dict | None = None, /, **kwargs) -> None:
94
+ document = self._merge(_update, kwargs)
95
+ document.pop('_id', None)
96
+ self._validate_data(data=kwargs, is_updating=True)
97
+
98
+ for field, value in document.items():
70
99
  setattr(self, field, value)
71
- db.session.collection(self.__class__.__name__).update_one({'_id': self._id}, **kwargs)
100
+ db.session.collection(self.__class__.__name__).update_one({'_id': self._id}, **document)
72
101
 
73
102
  @classmethod
74
- def update_one(cls, _filter: dict, _data: dict | None = None, /, **kwargs) -> bool:
103
+ async def update_one(cls, _filter: dict, _update: dict | None = None, /, **kwargs) -> bool:
75
104
  prepare_id_for_query(_filter)
76
- return db.session.collection(cls.__name__).update_one(_filter, **cls._merge(_data, kwargs))
105
+ return db.session.collection(cls.__name__).update_one(_filter, **cls._merge(_update, kwargs))
77
106
 
78
107
  @classmethod
79
- def update_many(cls, _filter: dict, _data: dict | None = None, /, **kwargs) -> int:
108
+ async def update_many(cls, _filter: dict, _data: dict | None = None, /, **kwargs) -> int:
80
109
  prepare_id_for_query(_filter)
81
110
  return db.session.collection(cls.__name__).update_many(_filter, **cls._merge(_data, kwargs))
@@ -1,11 +1,13 @@
1
1
  import sys
2
+ from typing import Sequence, Iterable
2
3
 
3
- from pydantic import ValidationError
4
+ from pantherdb import Cursor as PantherDBCursor
4
5
 
5
- from panther import status
6
6
  from panther.configs import QueryObservable
7
+ from panther.db.cursor import Cursor
8
+ from panther.db.queries.base_queries import BaseQuery
7
9
  from panther.db.utils import log_query, check_connection
8
- from panther.exceptions import APIException, DBException
10
+ from panther.exceptions import NotFoundAPIError
9
11
 
10
12
  __all__ = ('Query',)
11
13
 
@@ -17,12 +19,16 @@ else:
17
19
  Self = TypeVar('Self', bound='Query')
18
20
 
19
21
 
20
- class Query:
22
+ class Query(BaseQuery):
21
23
  def __init_subclass__(cls, **kwargs):
22
24
  QueryObservable.observe(cls)
23
25
 
24
26
  @classmethod
25
27
  def _reload_bases(cls, parent):
28
+ if not issubclass(parent, BaseQuery):
29
+ msg = f'Invalid Query Class: `{parent.__name__}` should be subclass of `BaseQuery`'
30
+ raise ValueError(msg)
31
+
26
32
  if cls.__bases__.count(Query):
27
33
  cls.__bases__ = (*cls.__bases__[: cls.__bases__.index(Query) + 1], parent)
28
34
  else:
@@ -30,247 +36,356 @@ class Query:
30
36
  if kls.__bases__.count(Query):
31
37
  kls.__bases__ = (*kls.__bases__[:kls.__bases__.index(Query) + 1], parent)
32
38
 
39
+ # # # # # Find # # # # #
33
40
  @classmethod
34
- def _validate_data(cls, *, data: dict, is_updating: bool = False):
35
- """
36
- *. Validate the input of user with its class
37
- *. If is_updating is True & exception happens but the message was empty
38
- """
39
- try:
40
- cls(**data)
41
- except ValidationError as validation_error:
42
- error = {
43
- '.'.join(loc for loc in e['loc']): e['msg']
44
- for e in validation_error.errors()
45
- if not is_updating or e['type'] != 'missing'
46
- }
47
- if error:
48
- raise APIException(detail=error, status_code=status.HTTP_400_BAD_REQUEST)
41
+ @check_connection
42
+ @log_query
43
+ async def find_one(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
44
+ """
45
+ Get a single document from the database.
49
46
 
50
- @classmethod
51
- def _create_model_instance(cls, document: dict):
52
- try:
53
- return cls(**document)
54
- except ValidationError as validation_error:
55
- error = ', '.join(
56
- '{field}="{error}"'.format(field='.'.join(loc for loc in e['loc']), error=e['msg'])
57
- for e in validation_error.errors()
58
- )
59
- if error:
60
- message = f'{cls.__name__}({error})'
61
- raise DBException(message)
47
+ Example:
48
+ -------
49
+ >>> from app.models import User
50
+
51
+ >>> await User.find_one(id=1, name='Ali')
52
+ or
53
+ >>> await User.find_one({'id': 1, 'name': 'Ali'})
54
+ or
55
+ >>> await User.find_one({'id': 1}, name='Ali')
56
+ """
57
+ return await super().find_one(_filter, **kwargs)
62
58
 
63
- # # # # # Find # # # # #
64
59
  @classmethod
65
60
  @check_connection
66
61
  @log_query
67
- def find_one(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
62
+ async def find(cls, _filter: dict | None = None, /, **kwargs) -> PantherDBCursor | Cursor:
68
63
  """
64
+ Get documents from the database.
65
+
69
66
  Example:
70
67
  -------
71
- >>> from example.app.models import User
72
- >>> User.find_one(id=1)
68
+ >>> from app.models import User
69
+
70
+ >>> await User.find(age=18, name='Ali')
71
+ or
72
+ >>> await User.find({'age': 18, 'name': 'Ali'})
73
+ or
74
+ >>> await User.find({'age': 18}, name='Ali')
73
75
  """
74
- return super().find_one(_data, **kwargs)
76
+ return await super().find(_filter, **kwargs)
75
77
 
76
78
  @classmethod
77
79
  @check_connection
78
80
  @log_query
79
- def find(cls, _data: dict | None = None, /, **kwargs) -> list[Self]:
81
+ async def first(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
80
82
  """
83
+ Get the first document from the database.
84
+
81
85
  Example:
82
86
  -------
83
- >>> from example.app.models import User
84
- >>> User.find(name='Ali')
87
+ >>> from app.models import User
88
+
89
+ >>> await User.first(age=18, name='Ali')
90
+ or
91
+ >>> await User.first({'age': 18, 'name': 'Ali'})
92
+ or
93
+ >>> await User.first({'age': 18}, name='Ali')
85
94
  """
86
- return super().find(_data, **kwargs)
95
+ return await super().first(_filter, **kwargs)
87
96
 
88
97
  @classmethod
89
98
  @check_connection
90
99
  @log_query
91
- def first(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
100
+ async def last(cls, _filter: dict | None = None, /, **kwargs) -> Self | None:
92
101
  """
102
+ Get the last document from the database.
103
+
93
104
  Example:
94
105
  -------
95
- >>> from example.app.models import User
96
- >>> user = User.first(name='Ali')
97
- * Alias of find_one()
106
+ >>> from app.models import User
107
+
108
+ >>> await User.last(age=18, name='Ali')
109
+ or
110
+ >>> await User.last({'age': 18, 'name': 'Ali'})
111
+ or
112
+ >>> await User.last({'age': 18}, name='Ali')
98
113
  """
99
- return super().first(_data, **kwargs)
114
+ return await super().last(_filter, **kwargs)
100
115
 
101
116
  @classmethod
102
117
  @check_connection
103
118
  @log_query
104
- def last(cls, _data: dict | None = None, /, **kwargs) -> Self | None:
119
+ async def aggregate(cls, pipeline: Sequence[dict]) -> Iterable[dict]:
105
120
  """
121
+ Perform an aggregation using the aggregation framework on this collection.
122
+
106
123
  Example:
107
124
  -------
108
- >>> from example.app.models import User
109
- >>> user = User.last(name='Ali')
125
+ >>> from app.models import User
126
+
127
+ >>> pipeline = [
128
+ >>> {'$match': {...}},
129
+ >>> {'$unwind': ...},
130
+ >>> {'$group': {...}},
131
+ >>> {'$project': {...}},
132
+ >>> {'$sort': {...}}
133
+ >>> ...
134
+ >>> ]
135
+
136
+ >>> await User.aggregate(pipeline)
110
137
  """
111
- return super().last(_data, **kwargs)
138
+ return await super().aggregate(pipeline)
112
139
 
113
140
  # # # # # Count # # # # #
114
141
  @classmethod
115
142
  @check_connection
116
143
  @log_query
117
- def count(cls, _data: dict | None = None, /, **kwargs) -> int:
144
+ async def count(cls, _filter: dict | None = None, /, **kwargs) -> int:
118
145
  """
146
+ Count the number of documents in this collection.
147
+
119
148
  Example:
120
149
  -------
121
- >>> from example.app.models import User
122
- >>> User.count(name='Ali')
150
+ >>> from app.models import User
151
+
152
+ >>> await User.count(age=18, name='Ali')
153
+ or
154
+ >>> await User.count({'age': 18, 'name': 'Ali'})
155
+ or
156
+ >>> await User.count({'age': 18}, name='Ali')
123
157
  """
124
- return super().count(_data, **kwargs)
158
+ return await super().count(_filter, **kwargs)
125
159
 
126
160
  # # # # # Insert # # # # #
127
161
  @classmethod
128
162
  @check_connection
129
163
  @log_query
130
- def insert_one(cls, _data: dict | None = None, /, **kwargs) -> Self:
164
+ async def insert_one(cls, _document: dict | None = None, /, **kwargs) -> Self:
131
165
  """
166
+ Insert a single document.
167
+
132
168
  Example:
133
169
  -------
134
- >>> from example.app.models import User
135
- >>> User.insert_one(name='Ali', age=24, ...)
170
+ >>> from app.models import User
171
+
172
+ >>> await User.insert_one(age=18, name='Ali')
173
+ or
174
+ >>> await User.insert_one({'age': 18, 'name': 'Ali'})
175
+ or
176
+ >>> await User.insert_one({'age': 18}, name='Ali')
136
177
  """
137
- cls._validate_data(data=kwargs)
138
- return super().insert_one(_data, **kwargs)
178
+ return await super().insert_one(_document, **kwargs)
139
179
 
140
180
  @classmethod
141
181
  @check_connection
142
182
  @log_query
143
- def insert_many(cls, _data: dict | None = None, /, **kwargs):
144
- msg = 'insert_many() is not supported yet.'
145
- raise DBException(msg)
183
+ async def insert_many(cls, documents: Iterable[dict]) -> list[Self]:
184
+ """
185
+ Insert an iterable of documents.
186
+
187
+ Example:
188
+ -------
189
+ >>> from app.models import User
190
+
191
+ >>> users = [
192
+ >>> {'age': 18, 'name': 'Ali'},
193
+ >>> {'age': 17, 'name': 'Saba'},
194
+ >>> {'age': 16, 'name': 'Amin'}
195
+ >>> ]
196
+ >>> await User.insert_many(users)
197
+ """
198
+ return await super().insert_many(documents)
146
199
 
147
200
  # # # # # Delete # # # # #
148
201
  @check_connection
149
202
  @log_query
150
- def delete(self) -> None:
203
+ async def delete(self) -> None:
151
204
  """
205
+ Delete the document.
206
+
152
207
  Example:
153
208
  -------
154
- >>> from example.app.models import User
155
- >>> user = User.find_one(name='Ali')
156
- >>> user.delete()
209
+ >>> from app.models import User
210
+
211
+ >>> user = await User.find_one(name='Ali')
212
+
213
+ >>> await user.delete()
157
214
  """
158
- return super().delete()
215
+ await super().delete()
159
216
 
160
217
  @classmethod
161
218
  @check_connection
162
219
  @log_query
163
- def delete_one(cls, _data: dict | None = None, /, **kwargs) -> bool:
220
+ async def delete_one(cls, _filter: dict | None = None, /, **kwargs) -> bool:
164
221
  """
222
+ Delete a single document matching the filter.
223
+
165
224
  Example:
166
225
  -------
167
- >>> from example.app.models import User
168
- >>> User.delete_one(id=1)
226
+ >>> from app.models import User
227
+
228
+ >>> await User.delete_one(age=18, name='Ali')
229
+ or
230
+ >>> await User.delete_one({'age': 18, 'name': 'Ali'})
231
+ or
232
+ >>> await User.delete_one({'age': 18}, name='Ali')
169
233
  """
170
- return super().delete_one(_data, **kwargs)
234
+ return await super().delete_one(_filter, **kwargs)
171
235
 
172
236
  @classmethod
173
237
  @check_connection
174
238
  @log_query
175
- def delete_many(cls, _data: dict | None = None, /, **kwargs) -> int:
239
+ async def delete_many(cls, _filter: dict | None = None, /, **kwargs) -> int:
176
240
  """
241
+ Delete one or more documents matching the filter.
242
+
177
243
  Example:
178
244
  -------
179
- >>> from example.app.models import User
180
- >>> User.delete_many(last_name='Rn')
245
+ >>> from app.models import User
246
+
247
+ >>> await User.delete_many(age=18, name='Ali')
248
+ or
249
+ >>> await User.delete_many({'age': 18, 'name': 'Ali'})
250
+ or
251
+ >>> await User.delete_many({'age': 18}, name='Ali')
181
252
  """
182
- return super().delete_many(_data, **kwargs)
253
+ return await super().delete_many(_filter, **kwargs)
183
254
 
184
255
  # # # # # Update # # # # #
185
256
  @check_connection
186
257
  @log_query
187
- def update(self, **kwargs) -> None:
258
+ async def update(self, _update: dict | None = None, /, **kwargs) -> None:
188
259
  """
260
+ Update the document.
261
+
189
262
  Example:
190
263
  -------
191
- >>> from example.app.models import User
192
- >>> user = User.find_one(name='Ali')
193
- >>> user.update(name='Saba')
264
+ >>> from app.models import User
265
+
266
+ >>> user = await User.find_one(age=18, name='Ali')
267
+
268
+ >>> await user.update(name='Saba', age=19)
269
+ or
270
+ >>> await user.update({'name': 'Saba'}, age=19)
271
+ or
272
+ >>> await user.update({'name': 'Saba', 'age': 19})
194
273
  """
195
- self._validate_data(data=kwargs, is_updating=True)
196
- return super().update(**kwargs)
274
+ await super().update(_update, **kwargs)
197
275
 
198
276
  @classmethod
199
277
  @check_connection
200
278
  @log_query
201
- def update_one(cls, _filter: dict, _data: dict | None = None, /, **kwargs) -> bool:
279
+ async def update_one(cls, _filter: dict, _update: dict | None = None, /, **kwargs) -> bool:
202
280
  """
281
+ Update a single document matching the filter.
282
+
203
283
  Example:
204
284
  -------
205
- >>> from example.app.models import User
206
- >>> User.update_one({'id': 1}, name='Ali')
207
- >>> User.update_one({'id': 2}, {'name': 'Ali', 'age': 25})
285
+ >>> from app.models import User
286
+
287
+ >>> await User.update_one({'id': 1}, age=18, name='Ali')
288
+ or
289
+ >>> await User.update_one({'id': 1}, {'age': 18, 'name': 'Ali'})
290
+ or
291
+ >>> await User.update_one({'id': 1}, {'age': 18}, name='Ali')
208
292
  """
209
- return super().update_one(_filter, _data, **kwargs)
293
+ return await super().update_one(_filter, _update, **kwargs)
210
294
 
211
295
  @classmethod
212
296
  @check_connection
213
297
  @log_query
214
- def update_many(cls, _filter: dict, _data: dict | None = None, /, **kwargs) -> int:
298
+ async def update_many(cls, _filter: dict, _update: dict | None = None, /, **kwargs) -> int:
215
299
  """
300
+ Update one or more documents that match the filter.
301
+
216
302
  Example:
217
303
  -------
218
- >>> from example.app.models import User
219
- >>> User.update_many({'name': 'Mohsen'}, name='Ali')
220
- >>> User.update_many({'name': 'Mohsen'}, {'name': 'Ali'})
304
+ >>> from app.models import User
305
+
306
+ >>> await User.update_many({'name': 'Saba'}, age=18, name='Ali')
307
+ or
308
+ >>> await User.update_many({'name': 'Saba'}, {'age': 18, 'name': 'Ali'})
309
+ or
310
+ >>> await User.update_many({'name': 'Saba'}, {'age': 18}, name='Ali')
221
311
  """
222
- return super().update_many(_filter, _data, **kwargs)
312
+ return await super().update_many(_filter, _update, **kwargs)
223
313
 
224
314
  # # # # # Other # # # # #
225
315
  @classmethod
226
- def all(cls) -> list[Self]:
316
+ async def all(cls) -> list[Self] | Cursor:
227
317
  """
318
+ Alias of find() without args
319
+
228
320
  Example:
229
321
  -------
230
- >>> from example.app.models import User
231
- >>> User.all()
232
- * Alias of find() without args
322
+ >>> from app.models import User
323
+
324
+ >>> await User.all()
233
325
  """
234
- return cls.find()
326
+ return await cls.find()
235
327
 
236
328
  @classmethod
237
- def find_or_insert(cls, **kwargs) -> tuple[bool, any]:
329
+ async def find_one_or_insert(cls, _filter: dict | None = None, /, **kwargs) -> tuple[Self, bool]:
238
330
  """
331
+ Get a single document from the database.
332
+ or
333
+ Insert a single document.
334
+
239
335
  Example:
240
336
  -------
241
- >>> from example.app.models import User
242
- >>> user = User.find_or_insert(name='Ali')
243
- """
244
- if obj := cls.find_one(**kwargs):
245
- return False, obj
246
- return True, cls.insert_one(**kwargs)
337
+ >>> from app.models import User
338
+
339
+ >>> await User.find_one_or_insert(age=18, name='Ali')
340
+ or
341
+ >>> await User.find_one_or_insert({'age': 18, 'name': 'Ali'})
342
+ or
343
+ >>> await User.find_one_or_insert({'age': 18}, name='Ali')
344
+ """
345
+ if obj := await cls.find_one(_filter, **kwargs):
346
+ return obj, False
347
+ return await cls.insert_one(_filter, **kwargs), True
247
348
 
248
349
  @classmethod
249
- def find_one_or_raise(cls, **kwargs) -> Self:
350
+ async def find_one_or_raise(cls, _filter: dict | None = None, /, **kwargs) -> Self:
250
351
  """
251
352
  Example:
252
353
  -------
253
- >>> from example.app.models import User
254
- >>> user = User.find_one_or_raise(name='Ali')
354
+ >>> from app.models import User
355
+
356
+ >>> await User.find_one_or_raise(age=18, name='Ali')
357
+ or
358
+ >>> await User.find_one_or_raise({'age': 18, 'name': 'Ali'})
359
+ or
360
+ >>> await User.find_one_or_raise({'age': 18}, name='Ali')
255
361
  """
256
- if obj := cls.find_one(**kwargs):
362
+ if obj := await cls.find_one(_filter, **kwargs):
257
363
  return obj
258
364
 
259
- raise APIException(
260
- detail=f'{cls.__name__} Does Not Exists',
261
- status_code=status.HTTP_404_NOT_FOUND,
262
- )
365
+ raise NotFoundAPIError(detail=f'{cls.__name__} Does Not Exist')
263
366
 
264
367
  @check_connection
265
368
  @log_query
266
- def save(self) -> None:
369
+ async def save(self) -> None:
267
370
  """
371
+ Save the document
372
+ If it has `id` --> Update It
373
+ else --> Insert It
268
374
  Example:
269
375
  -------
270
- >>> from example.app.models import User
271
- >>> user = User.find_one(name='Ali')
376
+ >>> from app.models import User
377
+
378
+ # Update
379
+ >>> user = await User.find_one(name='Ali')
272
380
  >>> user.name = 'Saba'
273
- >>> user.save()
274
- """
275
- msg = 'save() is not supported yet.'
276
- raise DBException(msg) from None
381
+ >>> await user.save()
382
+ or
383
+ # Insert
384
+ >>> user = User(name='Ali')
385
+ >>> await user.save()
386
+ """
387
+ document = self.model_dump(exclude=['_id'])
388
+ if self.id:
389
+ await self.update(document)
390
+ else:
391
+ await self.insert_one(document)