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.
- api_requester/__init__.py +9 -0
- api_requester/core/admin/api/auth_api.py +34 -0
- api_requester/core/admin/api/user_on_board_application_api.py +31 -0
- api_requester/core/admin/service/auth_service.py +34 -0
- api_requester/core/admin/service/impl/system_env_service_impl.py +39 -0
- api_requester/core/admin/service/impl/user_on_board_application_service_impl.py +23 -0
- api_requester/core/admin/service/user_on_board_application_service.py +35 -0
- api_requester/core/call_api.py +64 -0
- api_requester/core/common/api/system_env_api.py +24 -0
- api_requester/core/common/service/system_env_service.py +27 -0
- api_requester/docs/application.yaml +46 -0
- api_requester/dto/admin/cross_user_user_on_board_dto.py +20 -0
- api_requester/dto/admin/jwt_authentication_request.py +20 -0
- api_requester/dto/admin/user_on_board_dto.py +29 -0
- api_requester/dto/base_dto.py +167 -0
- api_requester/dto/biz_base.py +31 -0
- api_requester/dto/user.py +39 -0
- api_requester/http/app_url.py +31 -0
- api_requester/http/authorize.py +156 -0
- api_requester/http/eclinical_requests.py +264 -0
- api_requester/http/exceptions.py +72 -0
- api_requester/http/gateway.py +105 -0
- api_requester/http/sample_headers.py +23 -0
- api_requester/http/token_manager.py +30 -0
- api_requester/utils/constant.py +108 -0
- api_requester/utils/json_utils.py +83 -0
- api_requester/utils/lib.py +456 -0
- api_requester/utils/log.py +91 -0
- api_requester/utils/path.py +21 -0
- api_requester/utils/placeholder_replacer.py +22 -0
- api_requester/utils/read_file.py +94 -0
- api_requester/utils/rsa.py +36 -0
- api_requester/utils/time_utils.py +27 -0
- eclinical_requester-1.0.0.dist-info/LICENSE +19 -0
- eclinical_requester-1.0.0.dist-info/METADATA +19 -0
- eclinical_requester-1.0.0.dist-info/RECORD +38 -0
- eclinical_requester-1.0.0.dist-info/WHEEL +5 -0
- 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__
|