eclinical-requester 1.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 (38) hide show
  1. api_requester/__init__.py +9 -0
  2. api_requester/core/admin/api/auth_api.py +34 -0
  3. api_requester/core/admin/api/user_on_board_application_api.py +31 -0
  4. api_requester/core/admin/service/auth_service.py +34 -0
  5. api_requester/core/admin/service/impl/system_env_service_impl.py +39 -0
  6. api_requester/core/admin/service/impl/user_on_board_application_service_impl.py +23 -0
  7. api_requester/core/admin/service/user_on_board_application_service.py +35 -0
  8. api_requester/core/call_api.py +64 -0
  9. api_requester/core/common/api/system_env_api.py +24 -0
  10. api_requester/core/common/service/system_env_service.py +27 -0
  11. api_requester/docs/application.yaml +46 -0
  12. api_requester/dto/admin/cross_user_user_on_board_dto.py +20 -0
  13. api_requester/dto/admin/jwt_authentication_request.py +20 -0
  14. api_requester/dto/admin/user_on_board_dto.py +29 -0
  15. api_requester/dto/base_dto.py +167 -0
  16. api_requester/dto/biz_base.py +31 -0
  17. api_requester/dto/user.py +39 -0
  18. api_requester/http/app_url.py +31 -0
  19. api_requester/http/authorize.py +156 -0
  20. api_requester/http/eclinical_requests.py +264 -0
  21. api_requester/http/exceptions.py +72 -0
  22. api_requester/http/gateway.py +105 -0
  23. api_requester/http/sample_headers.py +23 -0
  24. api_requester/http/token_manager.py +30 -0
  25. api_requester/utils/constant.py +108 -0
  26. api_requester/utils/json_utils.py +83 -0
  27. api_requester/utils/lib.py +456 -0
  28. api_requester/utils/log.py +91 -0
  29. api_requester/utils/path.py +21 -0
  30. api_requester/utils/placeholder_replacer.py +22 -0
  31. api_requester/utils/read_file.py +94 -0
  32. api_requester/utils/rsa.py +36 -0
  33. api_requester/utils/time_utils.py +27 -0
  34. eclinical_requester-1.0.0.dist-info/LICENSE +19 -0
  35. eclinical_requester-1.0.0.dist-info/METADATA +19 -0
  36. eclinical_requester-1.0.0.dist-info/RECORD +38 -0
  37. eclinical_requester-1.0.0.dist-info/WHEEL +5 -0
  38. eclinical_requester-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,30 @@
1
+ # !/usr/bin/python3
2
+ # -*- coding:utf-8 -*-
3
+ """
4
+ @Author: xiaodong.li
5
+ @Time: 9/13/2024 5:45 PM
6
+ @Description: Description
7
+ @File: token_manager.py
8
+ """
9
+ from api_requester.dto.biz_base import BizBase
10
+ from api_requester.http.sample_headers import SampleHeaders
11
+
12
+
13
+ def apply_token_to_headers(instance):
14
+ """
15
+ 从实例的响应头中提取 token,并更新到实例的请求头中。
16
+
17
+ :param instance: 继承自 BizBase 的实例,包含响应结果和请求头信息。
18
+ :return: 无返回值。若存在 token,则更新实例的请求头。
19
+ :return:
20
+ """
21
+ if not isinstance(instance, BizBase):
22
+ return
23
+ instance: BizBase
24
+ if instance.last_result is None:
25
+ return
26
+ token = instance.last_result.headers.get("token")
27
+ if token:
28
+ headers = SampleHeaders()
29
+ headers.add_authorization(token)
30
+ instance.headers = headers.to_h()
@@ -0,0 +1,108 @@
1
+ # !/usr/bin/python3
2
+ # -*- coding:utf-8 -*-
3
+ """
4
+ @Author: xiaodong.li
5
+ @Time: 3/18/2024 4:59 PM
6
+ @Description: Description
7
+ @File: constant.py
8
+ """
9
+ from enum import Enum, unique
10
+
11
+
12
+ @unique
13
+ class AppEnum(Enum):
14
+
15
+ def __init__(self, system_id, code, description):
16
+ self.id = system_id
17
+ self.code = code
18
+ self.description = description
19
+
20
+ ADMIN = (1, "admin", "ADMIN")
21
+ CTMS = (2, "ctms", "CTMS")
22
+ ETMF = (3, "etmf", "eTMF")
23
+ DESIGN = (4, "design", "DESIGN")
24
+ EDC = (5, "edc", "EDC")
25
+ IWRS = (6, "iwrs", "IWRS")
26
+ E_CONSENT = (7, "econsent", "eConsent")
27
+ PV = (8, "pv", "PV")
28
+ CODING = (10, "coding", "CODING")
29
+ IMAGING = (11, "imaging", "Eclinical IMAGING System")
30
+ CMD = (12, "cmd", "Eclinical CMD System")
31
+ IRC = (13, "irc", "Eclinical IRC System")
32
+
33
+
34
+ class UserType(Enum):
35
+
36
+ def __init__(self, code, type_name):
37
+ self.code = code
38
+ self.type_name = type_name
39
+
40
+ user = (1, "User")
41
+ account = (2, "Account")
42
+
43
+
44
+ @unique
45
+ class AppEnvEnum(Enum):
46
+
47
+ def __init__(self, code, description):
48
+ self.code = code
49
+ self.description = description
50
+
51
+ DEV = ("dev", "DEV")
52
+ UAT = ("uat", "UAT")
53
+ PROD = ("prod", "PROD")
54
+
55
+
56
+ @unique
57
+ class BizSqlType(Enum):
58
+
59
+ def __init__(self, code, description):
60
+ self.code = code
61
+ self.description = description
62
+
63
+ INITIAL = (1, "initial")
64
+ INCREMENTAL = (2, "incremental")
65
+
66
+
67
+ class TestEnvType(str):
68
+ local = "local"
69
+ us_dev = "us.dev"
70
+ us_demo = "us.demo"
71
+
72
+
73
+ @unique
74
+ class TestEnv(Enum):
75
+
76
+ def __init__(self, code, ttype):
77
+ self.code = code
78
+ self.ttype = ttype
79
+
80
+ us_dev = ("us.dev", TestEnvType.us_dev)
81
+ us_demo = ("us.demo", TestEnvType.us_demo)
82
+ dev03 = ("dev03", TestEnvType.local)
83
+ dev04 = ("dev04", TestEnvType.local)
84
+ dev01 = ("dev01", TestEnvType.local)
85
+ dev02 = ("dev02", TestEnvType.local)
86
+ test01 = ("test01", TestEnvType.local)
87
+
88
+ @classmethod
89
+ def from_code(cls, code):
90
+ for member in cls:
91
+ if member.code == code:
92
+ return member
93
+ return None
94
+
95
+ def __str__(self):
96
+ return self.code
97
+
98
+
99
+ @unique
100
+ class RoleEnum(Enum):
101
+
102
+ def __init__(self, code, ttype=None):
103
+ self.code = code
104
+ self.ttype = ttype
105
+
106
+ DM = ("DM",)
107
+ CRC = ("CRC",)
108
+ CRA = ("CRA",)
@@ -0,0 +1,83 @@
1
+ # !/usr/bin/python3
2
+ # -*- coding:utf-8 -*-
3
+ """
4
+ @Author: xiaodong.li
5
+ @Time: 8/22/2024 11:44 AM
6
+ @Description: Description
7
+ @File: run.py
8
+ """
9
+ import ast
10
+
11
+ import datetime
12
+ import json
13
+ import os
14
+ from typing import Any
15
+
16
+
17
+ def sort_json_data(data: Any) -> Any:
18
+ """
19
+ Recursively sort JSON-like data (dictionaries and lists) and return the sorted data.
20
+
21
+ :param data: JSON-like data (dict, list, or primitive).
22
+ :return: Sorted JSON-like data.
23
+ """
24
+
25
+ def sort_key(item: Any) -> Any:
26
+ if isinstance(item, dict):
27
+ return tuple(sorted(item.items()))
28
+ elif isinstance(item, list):
29
+ return tuple(sort_key(subitem) for subitem in item)
30
+ else:
31
+ return item
32
+
33
+ if isinstance(data, dict):
34
+ # 对字典进行递归排序
35
+ return {k: sort_json_data(v) for k, v in sorted(data.items())}
36
+ elif isinstance(data, list):
37
+ # 对列表进行排序,列表中的每一项也是递归排序的
38
+ return sorted([sort_json_data(item) for item in data], key=sort_key)
39
+ elif isinstance(data, str):
40
+ try:
41
+ parsed_data = ast.literal_eval(data)
42
+ return sort_json_data(parsed_data)
43
+ except (ValueError, SyntaxError):
44
+ return data
45
+ else:
46
+ return data
47
+
48
+
49
+ def are_json_equal(str1, str2):
50
+ """
51
+ Compare two JSON strings by parsing, sorting, and then comparing the data.
52
+
53
+ :param str1: First JSON string.
54
+ :param str2: Second JSON string.
55
+ :return: True if both JSON strings represent equivalent data, False otherwise.
56
+ """
57
+ try:
58
+ data1 = json.loads(str1)
59
+ data2 = json.loads(str2)
60
+ except (json.JSONDecodeError, TypeError):
61
+ raise ValueError("Invalid JSON string provided.")
62
+ return sort_json_data(data1) == sort_json_data(data2)
63
+
64
+
65
+ class ComplexEncoder(json.JSONEncoder):
66
+ def default(self, obj):
67
+ if isinstance(obj, datetime.datetime):
68
+ return obj.strftime('%Y-%m-%d %H:%M:%S')
69
+ if isinstance(obj, datetime.date):
70
+ return obj.strftime('%Y-%m-%d')
71
+ if isinstance(obj, bytes):
72
+ # return str(obj, encoding='utf-8')
73
+ return bytes.decode(obj)
74
+ return json.JSONEncoder.default(self, obj)
75
+
76
+
77
+ def to_json_file(base_path, file_name, obj, sort_keys=True, indent=4):
78
+ if not obj:
79
+ return
80
+ os.makedirs(base_path, exist_ok=True)
81
+ path = f"{base_path}/{file_name}.json"
82
+ with open(path, "w") as fp:
83
+ json.dump(obj, fp, indent=indent, sort_keys=sort_keys, cls=ComplexEncoder)
@@ -0,0 +1,456 @@
1
+ # !/usr/bin/python3
2
+ # -*- coding:utf-8 -*-
3
+ """
4
+ @Author: xiaodong.li
5
+ @Time: 3/18/2024 4:59 PM
6
+ @Description: Description
7
+ @File: lib.py
8
+ """
9
+ import os
10
+ from typing import Any, List, Dict, Optional
11
+
12
+ import numpy as np
13
+ from dateutil import parser
14
+
15
+ from api_requester.utils.json_utils import are_json_equal
16
+
17
+
18
+ def is_na_no(sth) -> bool:
19
+ """
20
+ NaN、None或者空字符串返回True,其他情况返回False
21
+ """
22
+ if sth == 0:
23
+ return False
24
+ if not sth:
25
+ return True
26
+ if isinstance(sth, float):
27
+ if np.isnan(sth):
28
+ return True
29
+ return False
30
+
31
+
32
+ def check_file_path():
33
+ def __wrapper__(func):
34
+ def __inner__(*args, **kwargs):
35
+ _path = func(*args, **kwargs)
36
+ return (_path and os.path.exists(_path)) and _path or None
37
+
38
+ return __inner__
39
+
40
+ return __wrapper__
41
+
42
+
43
+ def param_is_digit(param):
44
+ return (type(param) is str and param.isdigit()) or type(param) is int
45
+
46
+
47
+ def get_class_attr_values(obj: object):
48
+ result = list()
49
+ for var_name, var_value in vars(obj).items():
50
+ if not var_name.startswith('__') and not callable(var_value):
51
+ result.append(var_value)
52
+ return result
53
+
54
+
55
+ def split_list(lst, n):
56
+ """将一组数字拆分成n个组,形成一个列表"""
57
+ quotient = len(lst) // n
58
+ remainder = len(lst) % n
59
+ result = []
60
+ start = 0
61
+ for i in range(n):
62
+ if i < remainder:
63
+ end = start + quotient + 1
64
+ else:
65
+ end = start + quotient
66
+ result.append(lst[start:end])
67
+ start = end
68
+ return result
69
+
70
+
71
+ def _get_val_from_list(items: list, k1, v1, k2, compare_json=False):
72
+ """
73
+ Traverse each item in the list, and obtain the value of k2 through
74
+ the actual value of k1 and the expected value v1.
75
+ :param items: List of dictionaries to search within.
76
+ :param k1: The key to look up in each dictionary.
77
+ :param v1: The value to match with the dictionary's value for the given key.
78
+ :param k2: The key whose value should be returned if a match is found.
79
+ :return: The value associated with k2 if a match is found, otherwise None.
80
+ """
81
+ if items and isinstance(items, list):
82
+ for item in items:
83
+ item_value = item.get(k1)
84
+ if compare_json:
85
+ if are_json_equal(v1, item_value):
86
+ return item.get(k2)
87
+ if item_value == v1:
88
+ return item.get(k2)
89
+ return None
90
+
91
+
92
+ def get_key_from_dict(items: dict, k, v):
93
+ if items and isinstance(items, dict):
94
+ for key, value in items.items():
95
+ if value.get(k) == v:
96
+ return key
97
+ return None
98
+
99
+
100
+ def _get_val_from_dict(items: dict, k1, k2):
101
+ """
102
+ Traverse each item in the dictionary and find the value of k2 in the value of k1.
103
+ @param items:
104
+ @param k1: A key in items.
105
+ @param k2:
106
+ @return: None, val
107
+ """
108
+ if items and isinstance(items, dict):
109
+ for key, value in items.items():
110
+ if key == k1:
111
+ return value.get(k2)
112
+ return None
113
+
114
+
115
+ def _find_and_validate_single_item(items, k1, v1, compare_json=False, validate=True):
116
+ """
117
+ Traverse each item in the list, and return the item through the actual value of k1 and the expected value v1.
118
+ :param items: List of dictionaries to search within.
119
+ :param k1: The key to look up in each dictionary.
120
+ :param v1: The value to match with the dictionary's value for the given key.
121
+ :param compare_json: If True, compare the value to the JSON representation of the item. Default is False.
122
+ :param validate: If True, validates that exactly one item matches the criteria.
123
+ If False, returns all matched items without validation.
124
+ :return: A single matching item if validate is True and exactly one match is found;
125
+ otherwise, returns the list of matched items or None if no match is found.
126
+ :raises ValueError: If validate is True and multiple items match the criteria.
127
+ """
128
+ matched_items = list()
129
+ if items and isinstance(items, list):
130
+ for item in items:
131
+ item_value = item.get(k1)
132
+ if compare_json:
133
+ if are_json_equal(v1, item_value):
134
+ matched_items.append(item)
135
+ continue
136
+ if v1 == item_value:
137
+ matched_items.append(item)
138
+ if not validate:
139
+ return matched_items
140
+ if len(matched_items) == 1:
141
+ return matched_items[0]
142
+ elif len(matched_items) == 0:
143
+ return None
144
+ else:
145
+ raise ValueError("Multiple items match the criteria.")
146
+
147
+
148
+ def _get_first_item_from_list(items, k1=None, v1=None, sort_by=None):
149
+ """
150
+ @param items:
151
+ @param k1:
152
+ @param v1:
153
+ @return: None, item
154
+ """
155
+ if items and isinstance(items, list):
156
+ if sort_by is not None:
157
+ items = sorted(items, key=lambda x: format_value(x[sort_by]), reverse=True)
158
+ for item in items:
159
+ if v1 is None or k1 is None:
160
+ return item
161
+ if v1 == item.get(k1):
162
+ return item
163
+ return None
164
+
165
+
166
+ def _get_last_item_from_list(items, k1=None, v1=None, sort_by=None):
167
+ if items and isinstance(items, list):
168
+ # 先排序,如果提供了排序键
169
+ if sort_by is not None:
170
+ items = sorted(items, key=lambda x: format_value(x.get(sort_by)),
171
+ reverse=True if sort_by.startswith('-') else False)
172
+
173
+ # 遍历列表,找到最后一个符合条件的项
174
+ last_item = None
175
+ for item in items:
176
+ if v1 is None or k1 is None or v1 == item.get(k1):
177
+ last_item = item
178
+ return last_item
179
+ return None
180
+
181
+
182
+ def is_included(items, k, v):
183
+ """
184
+ Traverse each item in the list to determine whether the value of k is consistent with the actual value v.
185
+ @param items:
186
+ @param v:
187
+ @param k:
188
+ @return: True or False.
189
+ """
190
+ if items and isinstance(items, list):
191
+ for i in items:
192
+ if i.get(k, None) == v:
193
+ return True
194
+ return False
195
+
196
+
197
+ def apply_is_included(key="name"):
198
+ def __wrapper__(func):
199
+ def __inner__(*args, **kwargs):
200
+ items = func(*args, **kwargs)
201
+ return is_included(items, key, kwargs.get("name"))
202
+
203
+ return __inner__
204
+
205
+ return __wrapper__
206
+
207
+
208
+ def get_val_from_list(k1="name", k2="id", v1=None, compare_json=False):
209
+ def __wrapper__(func):
210
+ def __inner__(*args, **kwargs):
211
+ res = func(*args, **kwargs)
212
+ v = kwargs.get("name") if v1 is None else v1
213
+ return _get_val_from_list(res, k1, v, k2, compare_json)
214
+
215
+ return __inner__
216
+
217
+ return __wrapper__
218
+
219
+
220
+ def get_item_from_list(k1="name", compare_json=False):
221
+ def __wrapper__(func):
222
+ def __inner__(*args, **kwargs):
223
+ res = func(*args, **kwargs)
224
+ return _find_and_validate_single_item(res, k1, kwargs.get("name"), compare_json)
225
+
226
+ return __inner__
227
+
228
+ return __wrapper__
229
+
230
+
231
+ def get_first_item_from_list(k1=None, sort_by=None):
232
+ def __wrapper__(func):
233
+ def __inner__(*args, **kwargs):
234
+ res = func(*args, **kwargs)
235
+ return _get_first_item_from_list(res, k1, kwargs.get("name"), sort_by)
236
+
237
+ return __inner__
238
+
239
+ return __wrapper__
240
+
241
+
242
+ def get_first_val_from_list(extract_key, k1=None, sort_by=None):
243
+ def __wrapper__(func):
244
+ def __inner__(*args, **kwargs):
245
+ res = func(*args, **kwargs)
246
+ item = _get_first_item_from_list(res, k1, kwargs.get("name"), sort_by)
247
+ return item.get(extract_key)
248
+
249
+ return __inner__
250
+
251
+ return __wrapper__
252
+
253
+
254
+ def get_last_val_from_list(extract_key, k1=None, sort_by=None):
255
+ def __wrapper__(func):
256
+ def __inner__(*args, **kwargs):
257
+ res = func(*args, **kwargs)
258
+ item = _get_last_item_from_list(res, k1, kwargs.get("name"), sort_by)
259
+ return item.get(extract_key)
260
+
261
+ return __inner__
262
+
263
+ return __wrapper__
264
+
265
+
266
+ def get_val_from_dict(k2):
267
+ def __wrapper__(func):
268
+ def __inner__(*args, **kwargs):
269
+ res = func(*args, **kwargs)
270
+ return _get_val_from_dict(res, kwargs.get("key"), k2)
271
+
272
+ return __inner__
273
+
274
+ return __wrapper__
275
+
276
+
277
+ def get_value_from_nested_dict_by_list(data, path):
278
+ """
279
+ 从嵌套的字典中按路径获取值
280
+
281
+ 参数:
282
+ data (dict):
283
+ path (list): 包含键的列表,表示路径
284
+
285
+ 返回:
286
+ 获取到的值,如果路径无效则返回 None
287
+ """
288
+ if not path:
289
+ return data
290
+ if not data:
291
+ return data
292
+ key = path[0]
293
+ if key in data:
294
+ return get_value_from_nested_dict_by_list(data[key], path[1:])
295
+ else:
296
+ return None
297
+
298
+
299
+ def get_value_from_nested_dict(data, path):
300
+ path_list = path.split(".")
301
+ return get_value_from_nested_dict_by_list(data, path_list)
302
+
303
+
304
+ def mkdirs(func):
305
+ def __wrapper__(*args, **kwargs):
306
+ dir_path = func(*args, **kwargs)
307
+ if not os.path.exists(dir_path):
308
+ os.makedirs(dir_path, exist_ok=True)
309
+ return dir_path
310
+
311
+ return __wrapper__
312
+
313
+
314
+ def truncate_string(s, max_length):
315
+ if type(s) is str and type(max_length) is int:
316
+ return s[:max_length if max_length < len(s) else len(s)]
317
+ return s
318
+
319
+
320
+ def handle_construction(construction_func, error_message):
321
+ """
322
+ 处理 YAML 标签构建的异常。
323
+ """
324
+ try:
325
+ return construction_func()
326
+ except Exception as e:
327
+ print("{0}: {1}".format(error_message, e))
328
+ return str()
329
+
330
+
331
+ def is_datetime_string(date_str):
332
+ try:
333
+ parser.parse(date_str)
334
+ return True
335
+ except (ValueError, TypeError):
336
+ return False
337
+
338
+
339
+ def format_value(value):
340
+ if value is None:
341
+ raise Exception("None value exists.")
342
+ if isinstance(value, int):
343
+ return value
344
+ elif isinstance(value, str):
345
+ if is_datetime_string(value):
346
+ return parser.parse(value)
347
+ else:
348
+ return value
349
+
350
+
351
+ def retrieve_item_by_key_value(items, key, value, compare_json=False, validate=True):
352
+ """
353
+ Retrieve a single item from a list of dictionaries based on a key-value match.
354
+
355
+ :param items: List of dictionaries to search within.
356
+ :param key: The key to look up in each dictionary.
357
+ :param value: The value to match with the dictionary's value for the given key.
358
+ :param compare_json: If True, compare the value to the JSON representation of the item. Default is False.
359
+ :param validate: If True, validates that exactly one item matches the criteria.
360
+ If False, returns all matched items without validation.
361
+ :return: The matching item or None if no match is found. Raises an exception if multiple matches are found.
362
+ :raises ValueError: If multiple items match the criteria.
363
+ """
364
+ return _find_and_validate_single_item(items, key, value, compare_json, validate)
365
+
366
+
367
+ def are_none_or_empty(*values: Any) -> bool:
368
+ """
369
+ 检查多个值是否都为 None 或空字符串。
370
+
371
+ :param values: 需要检查的值。
372
+ :return: 如果所有值都是 None 或空字符串,返回 True;否则返回 False。
373
+ """
374
+ processed_values = []
375
+ for value in values:
376
+ if isinstance(value, str):
377
+ processed_values.append(value.strip())
378
+ elif value is None:
379
+ processed_values.append(value)
380
+ else:
381
+ return False
382
+ return all(value in (None, "") for value in processed_values)
383
+
384
+
385
+ def first_unassociated_perms(trees, key):
386
+ """
387
+ 遍历树,返回第一分支为key=false的从根节点到子节点id的列表,并保留所有key=true的数据。
388
+
389
+ :param trees: 树列表
390
+ :param key: 属性名称
391
+ :return: 包含ID的列表
392
+ """
393
+ flag = "__false_value_flag__"
394
+
395
+ def traverse(node, paths, results):
396
+ if paths is None:
397
+ paths = []
398
+ if node.get("id") is not None:
399
+ paths.append(node["id"])
400
+ for child in node.get("children", []):
401
+ traverse(child, paths.copy(), results)
402
+ else:
403
+ if node.get(key, True):
404
+ results.extend(paths)
405
+ elif flag in results:
406
+ results.extend(paths)
407
+ results.remove(flag)
408
+
409
+ all_results = [flag]
410
+ for tree in trees:
411
+ traverse(tree, [], all_results)
412
+ return list(set(all_results))
413
+
414
+
415
+ def apply_first_unassociated_perms(key):
416
+ def __wrapper__(func):
417
+ def __inner__(*args, **kwargs):
418
+ trees = func(*args, **kwargs)
419
+ return first_unassociated_perms(trees, key)
420
+
421
+ return __inner__
422
+
423
+ return __wrapper__
424
+
425
+
426
+ def extract_values(records: List[Dict], key: Optional[str] = None, first_value: bool = False) -> List[Any]:
427
+ """
428
+ 从字典列表中提取每个字典的指定键的值,或者根据参数返回第一个值。
429
+
430
+ 该方法假设列表中的每个字典都包含至少一个键值对。
431
+ 如果 `first_value` 为 True,则返回每个字典的第一个值;
432
+ 如果提供了 `key`,则返回每个字典中对应键的值;
433
+ 如果既未提供 `key` 也未设置 `first_value` 为 True,则返回空列表。
434
+
435
+ :param records: 一个字典列表,每个字典至少包含一个键值对。
436
+ :param key: (可选)指定键来提取对应的值。如果未提供且 `first_value` 为 False,则返回空列表。
437
+ :param first_value: (可选)是否返回每个字典的第一个值。如果为 True,将忽略 `key` 参数。
438
+ :return: 包含提取值的列表。
439
+ """
440
+ if first_value:
441
+ return [next(iter(record.values())) for record in records]
442
+ elif key:
443
+ return [record.get(key) for record in records]
444
+ else:
445
+ return []
446
+
447
+
448
+ def apply_extract_values(key=None, first_value=False):
449
+ def __wrapper__(func):
450
+ def __inner__(*args, **kwargs):
451
+ records = func(*args, **kwargs)
452
+ return extract_values(records, key, first_value)
453
+
454
+ return __inner__
455
+
456
+ return __wrapper__