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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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 datetime_format, current_timestamp, 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,
  46. order_by=order_by, **kwargs)
  47. @classmethod
  48. @DB.connection_context()
  49. def get_all(cls, cols=None, reverse=None, order_by=None):
  50. """Retrieve all records from the database with optional column selection and ordering.
  51. This method fetches all records from the model's table with support for
  52. column selection and result ordering. If no order_by is specified and reverse
  53. is True, it defaults to ordering by create_time.
  54. Args:
  55. cols (list, optional): List of column names to select. If None, selects all columns.
  56. reverse (bool, optional): If True, sorts in descending order. If False, sorts in ascending order.
  57. order_by (str, optional): Column name to sort results by. Defaults to 'create_time' if reverse is specified.
  58. Returns:
  59. peewee.ModelSelect: A query containing all matching records.
  60. """
  61. if cols:
  62. query_records = cls.model.select(*cols)
  63. else:
  64. query_records = cls.model.select()
  65. if reverse is not None:
  66. if not order_by or not hasattr(cls, order_by):
  67. order_by = "create_time"
  68. if reverse is True:
  69. query_records = query_records.order_by(
  70. cls.model.getter_by(order_by).desc())
  71. elif reverse is False:
  72. query_records = query_records.order_by(
  73. cls.model.getter_by(order_by).asc())
  74. return query_records
  75. @classmethod
  76. @DB.connection_context()
  77. def get(cls, **kwargs):
  78. """Get a single record matching the given criteria.
  79. This method retrieves a single record from the database that matches
  80. the specified filter conditions.
  81. Args:
  82. **kwargs: Filter conditions as keyword arguments.
  83. Returns:
  84. Model instance: Single matching record.
  85. Raises:
  86. peewee.DoesNotExist: If no matching record is found.
  87. """
  88. return cls.model.get(**kwargs)
  89. @classmethod
  90. @DB.connection_context()
  91. def get_or_none(cls, **kwargs):
  92. """Get a single record or None if not found.
  93. This method attempts to retrieve a single record matching the given criteria,
  94. returning None if no match is found instead of raising an exception.
  95. Args:
  96. **kwargs: Filter conditions as keyword arguments.
  97. Returns:
  98. Model instance or None: Matching record if found, None otherwise.
  99. """
  100. try:
  101. return cls.model.get(**kwargs)
  102. except peewee.DoesNotExist:
  103. return None
  104. @classmethod
  105. @DB.connection_context()
  106. def save(cls, **kwargs):
  107. """Save a new record to database.
  108. This method creates a new record in the database with the provided field values,
  109. forcing an insert operation rather than an update.
  110. Args:
  111. **kwargs: Record field values as keyword arguments.
  112. Returns:
  113. Model instance: The created record object.
  114. """
  115. sample_obj = cls.model(**kwargs).save(force_insert=True)
  116. return sample_obj
  117. @classmethod
  118. @DB.connection_context()
  119. def insert(cls, **kwargs):
  120. """Insert a new record with automatic ID and timestamps.
  121. This method creates a new record with automatically generated ID and timestamp fields.
  122. It handles the creation of create_time, create_date, update_time, and update_date fields.
  123. Args:
  124. **kwargs: Record field values as keyword arguments.
  125. Returns:
  126. Model instance: The newly created record object.
  127. """
  128. if "id" not in kwargs:
  129. kwargs["id"] = get_uuid()
  130. kwargs["create_time"] = current_timestamp()
  131. kwargs["create_date"] = datetime_format(datetime.now())
  132. kwargs["update_time"] = current_timestamp()
  133. kwargs["update_date"] = datetime_format(datetime.now())
  134. sample_obj = cls.model(**kwargs).save(force_insert=True)
  135. return sample_obj
  136. @classmethod
  137. @DB.connection_context()
  138. def insert_many(cls, data_list, batch_size=100):
  139. """Insert multiple records in batches.
  140. This method efficiently inserts multiple records into the database using batch processing.
  141. It automatically sets creation timestamps for all records.
  142. Args:
  143. data_list (list): List of dictionaries containing record data to insert.
  144. batch_size (int, optional): Number of records to insert in each batch. Defaults to 100.
  145. """
  146. with DB.atomic():
  147. for d in data_list:
  148. d["create_time"] = current_timestamp()
  149. d["create_date"] = datetime_format(datetime.now())
  150. for i in range(0, len(data_list), batch_size):
  151. cls.model.insert_many(data_list[i:i + batch_size]).execute()
  152. @classmethod
  153. @DB.connection_context()
  154. def update_many_by_id(cls, data_list):
  155. """Update multiple records by their IDs.
  156. This method updates multiple records in the database, identified by their IDs.
  157. It automatically updates the update_time and update_date fields for each record.
  158. Args:
  159. data_list (list): List of dictionaries containing record data to update.
  160. Each dictionary must include an 'id' field.
  161. """
  162. with DB.atomic():
  163. for data in data_list:
  164. data["update_time"] = current_timestamp()
  165. data["update_date"] = datetime_format(datetime.now())
  166. cls.model.update(data).where(
  167. cls.model.id == data["id"]).execute()
  168. @classmethod
  169. @DB.connection_context()
  170. def update_by_id(cls, pid, data):
  171. # Update a single record by ID
  172. # Args:
  173. # pid: Record ID
  174. # data: Updated field values
  175. # Returns:
  176. # Number of records updated
  177. data["update_time"] = current_timestamp()
  178. data["update_date"] = datetime_format(datetime.now())
  179. num = cls.model.update(data).where(cls.model.id == pid).execute()
  180. return num
  181. @classmethod
  182. @DB.connection_context()
  183. def get_by_id(cls, pid):
  184. # Get a record by ID
  185. # Args:
  186. # pid: Record ID
  187. # Returns:
  188. # Tuple of (success, record)
  189. try:
  190. obj = cls.model.get_or_none(cls.model.id == pid)
  191. if obj:
  192. return True, obj
  193. except Exception:
  194. pass
  195. return False, None
  196. @classmethod
  197. @DB.connection_context()
  198. def get_by_ids(cls, pids, cols=None):
  199. # Get multiple records by their IDs
  200. # Args:
  201. # pids: List of record IDs
  202. # cols: List of columns to select
  203. # Returns:
  204. # Query of matching records
  205. if cols:
  206. objs = cls.model.select(*cols)
  207. else:
  208. objs = cls.model.select()
  209. return objs.where(cls.model.id.in_(pids))
  210. @classmethod
  211. @DB.connection_context()
  212. def delete_by_id(cls, pid):
  213. # Delete a record by ID
  214. # Args:
  215. # pid: Record ID
  216. # Returns:
  217. # Number of records deleted
  218. return cls.model.delete().where(cls.model.id == pid).execute()
  219. @classmethod
  220. @DB.connection_context()
  221. def filter_delete(cls, filters):
  222. # Delete records matching given filters
  223. # Args:
  224. # filters: List of filter conditions
  225. # Returns:
  226. # Number of records deleted
  227. with DB.atomic():
  228. num = cls.model.delete().where(*filters).execute()
  229. return num
  230. @classmethod
  231. @DB.connection_context()
  232. def filter_update(cls, filters, update_data):
  233. # Update records matching given filters
  234. # Args:
  235. # filters: List of filter conditions
  236. # update_data: Updated field values
  237. # Returns:
  238. # Number of records updated
  239. with DB.atomic():
  240. return cls.model.update(update_data).where(*filters).execute()
  241. @staticmethod
  242. def cut_list(tar_list, n):
  243. # Split a list into chunks of size n
  244. # Args:
  245. # tar_list: List to split
  246. # n: Chunk size
  247. # Returns:
  248. # List of tuples containing chunks
  249. length = len(tar_list)
  250. arr = range(length)
  251. result = [tuple(tar_list[x:(x + n)]) for x in arr[::n]]
  252. return result
  253. @classmethod
  254. @DB.connection_context()
  255. def filter_scope_list(cls, in_key, in_filters_list,
  256. filters=None, cols=None):
  257. # Get records matching IN clause filters with optional column selection
  258. # Args:
  259. # in_key: Field name for IN clause
  260. # in_filters_list: List of values for IN clause
  261. # filters: Additional filter conditions
  262. # cols: List of columns to select
  263. # Returns:
  264. # List of matching records
  265. in_filters_tuple_list = cls.cut_list(in_filters_list, 20)
  266. if not filters:
  267. filters = []
  268. res_list = []
  269. if cols:
  270. for i in in_filters_tuple_list:
  271. query_records = cls.model.select(
  272. *
  273. cols).where(
  274. getattr(
  275. cls.model,
  276. in_key).in_(i),
  277. *
  278. filters)
  279. if query_records:
  280. res_list.extend(
  281. [query_record for query_record in query_records])
  282. else:
  283. for i in in_filters_tuple_list:
  284. query_records = cls.model.select().where(
  285. getattr(cls.model, in_key).in_(i), *filters)
  286. if query_records:
  287. res_list.extend(
  288. [query_record for query_record in query_records])
  289. return res_list