Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

common_service.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #
  2. # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. from datetime import datetime
  17. import peewee
  18. from api.db.db_models import DB
  19. from api.utils import current_timestamp, datetime_format, get_uuid
  20. class CommonService:
  21. """Base service class that provides common database operations.
  22. This class serves as a foundation for all service classes in the application,
  23. implementing standard CRUD operations and common database query patterns.
  24. It uses the Peewee ORM for database interactions and provides a consistent
  25. interface for database operations across all derived service classes.
  26. Attributes:
  27. model: The Peewee model class that this service operates on. Must be set by subclasses.
  28. """
  29. model = None
  30. @classmethod
  31. @DB.connection_context()
  32. def query(cls, cols=None, reverse=None, order_by=None, **kwargs):
  33. """Execute a database query with optional column selection and ordering.
  34. This method provides a flexible way to query the database with various filters
  35. and sorting options. It supports column selection, sort order control, and
  36. additional filter conditions.
  37. Args:
  38. cols (list, optional): List of column names to select. If None, selects all columns.
  39. reverse (bool, optional): If True, sorts in descending order. If False, sorts in ascending order.
  40. order_by (str, optional): Column name to sort results by.
  41. **kwargs: Additional filter conditions passed as keyword arguments.
  42. Returns:
  43. peewee.ModelSelect: A query result containing matching records.
  44. """
  45. return cls.model.query(cols=cols, reverse=reverse, order_by=order_by, **kwargs)
  46. @classmethod
  47. @DB.connection_context()
  48. def get_all(cls, cols=None, reverse=None, order_by=None):
  49. """Retrieve all records from the database with optional column selection and ordering.
  50. This method fetches all records from the model's table with support for
  51. column selection and result ordering. If no order_by is specified and reverse
  52. is True, it defaults to ordering by create_time.
  53. Args:
  54. cols (list, optional): List of column names to select. If None, selects all columns.
  55. reverse (bool, optional): If True, sorts in descending order. If False, sorts in ascending order.
  56. order_by (str, optional): Column name to sort results by. Defaults to 'create_time' if reverse is specified.
  57. Returns:
  58. peewee.ModelSelect: A query containing all matching records.
  59. """
  60. if cols:
  61. query_records = cls.model.select(*cols)
  62. else:
  63. query_records = cls.model.select()
  64. if reverse is not None:
  65. if not order_by or not hasattr(cls, order_by):
  66. order_by = "create_time"
  67. if reverse is True:
  68. query_records = query_records.order_by(cls.model.getter_by(order_by).desc())
  69. elif reverse is False:
  70. query_records = query_records.order_by(cls.model.getter_by(order_by).asc())
  71. return query_records
  72. @classmethod
  73. @DB.connection_context()
  74. def get(cls, **kwargs):
  75. """Get a single record matching the given criteria.
  76. This method retrieves a single record from the database that matches
  77. the specified filter conditions.
  78. Args:
  79. **kwargs: Filter conditions as keyword arguments.
  80. Returns:
  81. Model instance: Single matching record.
  82. Raises:
  83. peewee.DoesNotExist: If no matching record is found.
  84. """
  85. return cls.model.get(**kwargs)
  86. @classmethod
  87. @DB.connection_context()
  88. def get_or_none(cls, **kwargs):
  89. """Get a single record or None if not found.
  90. This method attempts to retrieve a single record matching the given criteria,
  91. returning None if no match is found instead of raising an exception.
  92. Args:
  93. **kwargs: Filter conditions as keyword arguments.
  94. Returns:
  95. Model instance or None: Matching record if found, None otherwise.
  96. """
  97. try:
  98. return cls.model.get(**kwargs)
  99. except peewee.DoesNotExist:
  100. return None
  101. @classmethod
  102. @DB.connection_context()
  103. def save(cls, **kwargs):
  104. """Save a new record to database.
  105. This method creates a new record in the database with the provided field values,
  106. forcing an insert operation rather than an update.
  107. Args:
  108. **kwargs: Record field values as keyword arguments.
  109. Returns:
  110. Model instance: The created record object.
  111. """
  112. sample_obj = cls.model(**kwargs).save(force_insert=True)
  113. return sample_obj
  114. @classmethod
  115. @DB.connection_context()
  116. def insert(cls, **kwargs):
  117. """Insert a new record with automatic ID and timestamps.
  118. This method creates a new record with automatically generated ID and timestamp fields.
  119. It handles the creation of create_time, create_date, update_time, and update_date fields.
  120. Args:
  121. **kwargs: Record field values as keyword arguments.
  122. Returns:
  123. Model instance: The newly created record object.
  124. """
  125. if "id" not in kwargs:
  126. kwargs["id"] = get_uuid()
  127. kwargs["create_time"] = current_timestamp()
  128. kwargs["create_date"] = datetime_format(datetime.now())
  129. kwargs["update_time"] = current_timestamp()
  130. kwargs["update_date"] = datetime_format(datetime.now())
  131. sample_obj = cls.model(**kwargs).save(force_insert=True)
  132. return sample_obj
  133. @classmethod
  134. @DB.connection_context()
  135. def insert_many(cls, data_list, batch_size=100):
  136. """Insert multiple records in batches.
  137. This method efficiently inserts multiple records into the database using batch processing.
  138. It automatically sets creation timestamps for all records.
  139. Args:
  140. data_list (list): List of dictionaries containing record data to insert.
  141. batch_size (int, optional): Number of records to insert in each batch. Defaults to 100.
  142. """
  143. with DB.atomic():
  144. for d in data_list:
  145. d["create_time"] = current_timestamp()
  146. d["create_date"] = datetime_format(datetime.now())
  147. for i in range(0, len(data_list), batch_size):
  148. cls.model.insert_many(data_list[i : i + batch_size]).execute()
  149. @classmethod
  150. @DB.connection_context()
  151. def update_many_by_id(cls, data_list):
  152. """Update multiple records by their IDs.
  153. This method updates multiple records in the database, identified by their IDs.
  154. It automatically updates the update_time and update_date fields for each record.
  155. Args:
  156. data_list (list): List of dictionaries containing record data to update.
  157. Each dictionary must include an 'id' field.
  158. """
  159. with DB.atomic():
  160. for data in data_list:
  161. data["update_time"] = current_timestamp()
  162. data["update_date"] = datetime_format(datetime.now())
  163. cls.model.update(data).where(cls.model.id == data["id"]).execute()
  164. @classmethod
  165. @DB.connection_context()
  166. def update_by_id(cls, pid, data):
  167. # Update a single record by ID
  168. # Args:
  169. # pid: Record ID
  170. # data: Updated field values
  171. # Returns:
  172. # Number of records updated
  173. data["update_time"] = current_timestamp()
  174. data["update_date"] = datetime_format(datetime.now())
  175. num = cls.model.update(data).where(cls.model.id == pid).execute()
  176. return num
  177. @classmethod
  178. @DB.connection_context()
  179. def get_by_id(cls, pid):
  180. # Get a record by ID
  181. # Args:
  182. # pid: Record ID
  183. # Returns:
  184. # Tuple of (success, record)
  185. try:
  186. obj = cls.model.get_or_none(cls.model.id == pid)
  187. if obj:
  188. return True, obj
  189. except Exception:
  190. pass
  191. return False, None
  192. @classmethod
  193. @DB.connection_context()
  194. def get_by_ids(cls, pids, cols=None):
  195. # Get multiple records by their IDs
  196. # Args:
  197. # pids: List of record IDs
  198. # cols: List of columns to select
  199. # Returns:
  200. # Query of matching records
  201. if cols:
  202. objs = cls.model.select(*cols)
  203. else:
  204. objs = cls.model.select()
  205. return objs.where(cls.model.id.in_(pids))
  206. @classmethod
  207. @DB.connection_context()
  208. def delete_by_id(cls, pid):
  209. # Delete a record by ID
  210. # Args:
  211. # pid: Record ID
  212. # Returns:
  213. # Number of records deleted
  214. return cls.model.delete().where(cls.model.id == pid).execute()
  215. @classmethod
  216. @DB.connection_context()
  217. def delete_by_ids(cls, pids):
  218. # Delete multiple records by their IDs
  219. # Args:
  220. # pids: List of record IDs
  221. # Returns:
  222. # Number of records deleted
  223. with DB.atomic():
  224. res = cls.model.delete().where(cls.model.id.in_(pids)).execute()
  225. return res
  226. @classmethod
  227. @DB.connection_context()
  228. def filter_delete(cls, filters):
  229. # Delete records matching given filters
  230. # Args:
  231. # filters: List of filter conditions
  232. # Returns:
  233. # Number of records deleted
  234. with DB.atomic():
  235. num = cls.model.delete().where(*filters).execute()
  236. return num
  237. @classmethod
  238. @DB.connection_context()
  239. def filter_update(cls, filters, update_data):
  240. # Update records matching given filters
  241. # Args:
  242. # filters: List of filter conditions
  243. # update_data: Updated field values
  244. # Returns:
  245. # Number of records updated
  246. with DB.atomic():
  247. return cls.model.update(update_data).where(*filters).execute()
  248. @staticmethod
  249. def cut_list(tar_list, n):
  250. # Split a list into chunks of size n
  251. # Args:
  252. # tar_list: List to split
  253. # n: Chunk size
  254. # Returns:
  255. # List of tuples containing chunks
  256. length = len(tar_list)
  257. arr = range(length)
  258. result = [tuple(tar_list[x : (x + n)]) for x in arr[::n]]
  259. return result
  260. @classmethod
  261. @DB.connection_context()
  262. def filter_scope_list(cls, in_key, in_filters_list, filters=None, cols=None):
  263. # Get records matching IN clause filters with optional column selection
  264. # Args:
  265. # in_key: Field name for IN clause
  266. # in_filters_list: List of values for IN clause
  267. # filters: Additional filter conditions
  268. # cols: List of columns to select
  269. # Returns:
  270. # List of matching records
  271. in_filters_tuple_list = cls.cut_list(in_filters_list, 20)
  272. if not filters:
  273. filters = []
  274. res_list = []
  275. if cols:
  276. for i in in_filters_tuple_list:
  277. query_records = cls.model.select(*cols).where(getattr(cls.model, in_key).in_(i), *filters)
  278. if query_records:
  279. res_list.extend([query_record for query_record in query_records])
  280. else:
  281. for i in in_filters_tuple_list:
  282. query_records = cls.model.select().where(getattr(cls.model, in_key).in_(i), *filters)
  283. if query_records:
  284. res_list.extend([query_record for query_record in query_records])
  285. return res_list