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,39 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 03/18/2024 3:48 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: authorize.py
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
class EClinicalUser:
|
12
|
+
|
13
|
+
def __init__(self, username=None, password=None, sponsor=None, study=None, test_env=None, app_env=None, app=None,
|
14
|
+
company=None, role=None, external=False):
|
15
|
+
# username 用户名
|
16
|
+
# password 密码
|
17
|
+
# app 访问的系统
|
18
|
+
# sponsor 访问sponsor 如果不需要,则传None
|
19
|
+
# study 访问sponsor 如果不需要,则传None
|
20
|
+
# test_env 访问的服务器环境
|
21
|
+
self.username = username
|
22
|
+
self.password = password
|
23
|
+
self.app = app
|
24
|
+
self.sponsor = sponsor
|
25
|
+
self.study = study
|
26
|
+
self.app_env = app_env
|
27
|
+
self.test_env = str(test_env)
|
28
|
+
self.company = company
|
29
|
+
self.role = role
|
30
|
+
self.external = external
|
31
|
+
self.company_level_login = False
|
32
|
+
|
33
|
+
def __repr__(self):
|
34
|
+
attributes = self.__dict__
|
35
|
+
info = list()
|
36
|
+
for item in ["test_env", "username", "role", "company", "sponsor", "study"]:
|
37
|
+
if attributes.get(item) is not None:
|
38
|
+
info.append(str(attributes.get(item)))
|
39
|
+
return "/".join(info)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 7/24/2023 3:48 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: app_url.py
|
8
|
+
"""
|
9
|
+
from api_requester.http.gateway import Gateway
|
10
|
+
from api_requester.utils.constant import AppEnum
|
11
|
+
|
12
|
+
|
13
|
+
class AppUrl(object):
|
14
|
+
def __init__(self, app, test_env):
|
15
|
+
self.app = app
|
16
|
+
self.test_env = test_env
|
17
|
+
|
18
|
+
def _app_url(self, api, app, external=False, **kwargs):
|
19
|
+
return Gateway(self.test_env).url(app, api, external).format(**kwargs)
|
20
|
+
|
21
|
+
def app_url(self, api, **kwargs):
|
22
|
+
return self._app_url(api, self.app, **kwargs)
|
23
|
+
|
24
|
+
def portal_url(self, api, **kwargs):
|
25
|
+
return self._app_url(api, AppEnum.ADMIN.code, **kwargs)
|
26
|
+
|
27
|
+
def external_url(self, api, **kwargs):
|
28
|
+
return self._app_url(api, self.app, True, **kwargs)
|
29
|
+
|
30
|
+
def which_url(self, app):
|
31
|
+
return self.portal_url if app == AppEnum.ADMIN.code else self.app_url
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 7/24/2023 3:48 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: authorize.py
|
8
|
+
"""
|
9
|
+
import time
|
10
|
+
|
11
|
+
from api_requester.core.admin.service.auth_service import AdminAuthService
|
12
|
+
from api_requester.core.admin.service.impl.system_env_service_impl import CommonSystemEnvServiceImpl
|
13
|
+
from api_requester.core.admin.service.impl.user_on_board_application_service_impl import \
|
14
|
+
AdminUserOnBoardApplicationServiceImpl
|
15
|
+
from api_requester.dto.admin.cross_user_user_on_board_dto import CrossUserUserOnBoardDto
|
16
|
+
from api_requester.dto.admin.jwt_authentication_request import JwtAuthenticationRequest
|
17
|
+
from api_requester.dto.admin.user_on_board_dto import UserOnBoardDto
|
18
|
+
from api_requester.dto.biz_base import BizBase
|
19
|
+
from api_requester.dto.user import EClinicalUser
|
20
|
+
from api_requester.http.sample_headers import SampleHeaders
|
21
|
+
from api_requester.utils.constant import AppEnum, UserType
|
22
|
+
from api_requester.utils.log import Logger
|
23
|
+
from api_requester.utils.rsa import encrypt_password
|
24
|
+
|
25
|
+
|
26
|
+
class Authorize(BizBase, SampleHeaders, AdminAuthService, AdminUserOnBoardApplicationServiceImpl,
|
27
|
+
CommonSystemEnvServiceImpl):
|
28
|
+
|
29
|
+
def __init__(self, user: EClinicalUser):
|
30
|
+
BizBase.__init__(self)
|
31
|
+
self.user = user
|
32
|
+
self.login_app = self.user.app
|
33
|
+
self.user.app = AppEnum.ADMIN.code
|
34
|
+
self.time_mills = time.time()
|
35
|
+
self.user_onboard_dto = UserOnBoardDto(-1)
|
36
|
+
SampleHeaders.__init__(self)
|
37
|
+
AdminAuthService.__init__(self)
|
38
|
+
AdminUserOnBoardApplicationServiceImpl.__init__(self)
|
39
|
+
CommonSystemEnvServiceImpl.__init__(self)
|
40
|
+
|
41
|
+
def login(self):
|
42
|
+
if self.login_app == AppEnum.CODING.code:
|
43
|
+
login_app_tip = "{0}({1})".format(self.login_app,
|
44
|
+
self.user.company_level_login is True and "admin" or "study")
|
45
|
+
else:
|
46
|
+
login_app_tip = self.login_app
|
47
|
+
try:
|
48
|
+
user_type = self._auth()
|
49
|
+
if user_type == UserType.account.type_name:
|
50
|
+
return
|
51
|
+
company_id = None
|
52
|
+
if self.user.company:
|
53
|
+
company_id = self.set_work_for_company_id()
|
54
|
+
if self.login_app != AppEnum.ADMIN.code:
|
55
|
+
self._user_onboard(company_id)
|
56
|
+
self.user.app = self.login_app
|
57
|
+
self._app_auth()
|
58
|
+
if self.user.role is not None:
|
59
|
+
self.switch_role(self.user.role)
|
60
|
+
else:
|
61
|
+
self._entry_portal(company_id)
|
62
|
+
Logger().info("The user({0}) logs in to {1} successfully.".format(self.user, login_app_tip))
|
63
|
+
except Exception as e:
|
64
|
+
Logger().error("The user({0}) failed to log in to {1}.".format(self.user, login_app_tip))
|
65
|
+
raise Exception(f"Authorize Failed: {e}")
|
66
|
+
|
67
|
+
def _auth(self):
|
68
|
+
self.time_mills = time.time()
|
69
|
+
encrypt_pwd = encrypt_password(self.user.password)
|
70
|
+
jwt_authentication_request_dto = JwtAuthenticationRequest(self.user.username, encrypt_pwd)
|
71
|
+
response = self.create_authentication_token(jwt_authentication_request_dto)
|
72
|
+
if response is None:
|
73
|
+
raise Exception(f"LoginParams failed: {response}")
|
74
|
+
if response.get("jwtAuthenticationResponse") is None:
|
75
|
+
token = response.get("token")
|
76
|
+
else:
|
77
|
+
token = response.get("jwtAuthenticationResponse").get("token")
|
78
|
+
self.add_authorization(token)
|
79
|
+
self.time_mills = time.time()
|
80
|
+
return response.get("type")
|
81
|
+
|
82
|
+
def _user_onboard(self, company_id):
|
83
|
+
user_onboard_dto = UserOnBoardDto(-1)
|
84
|
+
user_onboard_dto.workForCompanyId = company_id
|
85
|
+
self._build_user_onboard_dto(user_onboard_dto)
|
86
|
+
self.user_onboard_dto = user_onboard_dto
|
87
|
+
response = self.on_board(user_onboard_dto)
|
88
|
+
self.add_authorization(response.get("token"))
|
89
|
+
|
90
|
+
def _build_user_onboard_dto(self, dto: UserOnBoardDto):
|
91
|
+
onboard_system_list = self.get_user_onboard_application_list(dto.workForCompanyId)
|
92
|
+
if not onboard_system_list:
|
93
|
+
raise Exception("Failed to obtain onboardSystemList info.")
|
94
|
+
for onboard_system in onboard_system_list:
|
95
|
+
if onboard_system.get("systemName").lower() == self.login_app:
|
96
|
+
dto.applicationId = onboard_system.get("systemId")
|
97
|
+
self._handle_onboard_envs(onboard_system, dto)
|
98
|
+
if self.user.company_level_login is True:
|
99
|
+
dto.companyLevelLogin = True
|
100
|
+
continue
|
101
|
+
onboard_sponsor_list = onboard_system.get("onboardSponsorList")
|
102
|
+
if not onboard_sponsor_list:
|
103
|
+
continue
|
104
|
+
for onboard_sponsor in onboard_sponsor_list:
|
105
|
+
if onboard_sponsor.get("name") == self.user.sponsor:
|
106
|
+
dto.sponsorId = onboard_sponsor.get("sponsorId")
|
107
|
+
self._handle_onboard_envs(onboard_sponsor, dto)
|
108
|
+
onboard_study_list = onboard_sponsor.get("onboardStudyList")
|
109
|
+
if not onboard_study_list:
|
110
|
+
continue
|
111
|
+
for onboard_study in onboard_study_list:
|
112
|
+
if onboard_study.get("studyName") == self.user.study:
|
113
|
+
dto.studyId = onboard_study.get("studyId")
|
114
|
+
self._handle_onboard_envs(onboard_study, dto)
|
115
|
+
self._assert(self.login_app, dto.applicationId, "app")
|
116
|
+
if self.login_app in [AppEnum.EDC.code, AppEnum.DESIGN.code, AppEnum.IWRS.code, AppEnum.CODING.code]:
|
117
|
+
if dto.companyLevelLogin is False:
|
118
|
+
self._assert(self.user.study, dto.studyId, "study")
|
119
|
+
else:
|
120
|
+
self._assert(self.user.company, dto.workForCompanyId, "company")
|
121
|
+
elif self.login_app != AppEnum.ADMIN.code:
|
122
|
+
self._assert(self.user.sponsor, dto.sponsorId, "sponsor")
|
123
|
+
self._assert(self.user.app_env, dto.envId, "app env")
|
124
|
+
|
125
|
+
@staticmethod
|
126
|
+
def _assert(attr, att_id, k):
|
127
|
+
assert (not attr) or (False if att_id is None else True), f"Failed to obtain {k} id. {k}::{attr}."
|
128
|
+
|
129
|
+
def _handle_onboard_envs(self, onboard_infos, dto: UserOnBoardDto):
|
130
|
+
onboard_envs = onboard_infos.get("onboardEnvs") or list()
|
131
|
+
for onboardEnv in onboard_envs:
|
132
|
+
if onboardEnv.get("name") == self.user.app_env:
|
133
|
+
dto.envId = onboardEnv.get("id")
|
134
|
+
|
135
|
+
def set_work_for_company_id(self):
|
136
|
+
company_id = self.get_company_id(name=self.user.company)
|
137
|
+
if self.user.company and not company_id:
|
138
|
+
raise Exception("The company {0} was not found.".format(self.user.company))
|
139
|
+
return company_id
|
140
|
+
|
141
|
+
def _app_auth(self):
|
142
|
+
response = self.create_authentication_token()
|
143
|
+
if response.get("jwtAuthenticationResponse") is None:
|
144
|
+
token = response.get("token")
|
145
|
+
else:
|
146
|
+
token = response.get("jwtAuthenticationResponse").get("token")
|
147
|
+
self.add_authorization(token)
|
148
|
+
self.time_mills = time.time()
|
149
|
+
|
150
|
+
def _entry_portal(self, company_id):
|
151
|
+
response = self.login_portal(CrossUserUserOnBoardDto(companyId=company_id))
|
152
|
+
try:
|
153
|
+
token = response.get("token")
|
154
|
+
self.add_authorization(token)
|
155
|
+
except BaseException as e:
|
156
|
+
print(e)
|
@@ -0,0 +1,264 @@
|
|
1
|
+
"""
|
2
|
+
@Author: xiaodong.li
|
3
|
+
@Date: 2023-07-25 13:33:15
|
4
|
+
LastEditors: xiaodong.li
|
5
|
+
LastEditTime: 2023-07-25 13:33:15
|
6
|
+
@Description: eclinical_requests.py
|
7
|
+
"""
|
8
|
+
import os
|
9
|
+
import time
|
10
|
+
from functools import wraps
|
11
|
+
|
12
|
+
import requests
|
13
|
+
from requests import Response
|
14
|
+
from requests_toolbelt import MultipartEncoder
|
15
|
+
|
16
|
+
from api_requester.dto.base_dto import BaseDto, build_file_dto
|
17
|
+
from api_requester.dto.biz_base import BizBase
|
18
|
+
from api_requester.http.app_url import AppUrl
|
19
|
+
from api_requester.http.exceptions import ApiResponseException
|
20
|
+
|
21
|
+
|
22
|
+
def get(api):
|
23
|
+
def __wrapper__(func):
|
24
|
+
@url(api)
|
25
|
+
@build_request_data()
|
26
|
+
@init_content_type()
|
27
|
+
@refresh_token()
|
28
|
+
@get_request()
|
29
|
+
@http_ok
|
30
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
31
|
+
return func(instance, *args, **kwargs)
|
32
|
+
|
33
|
+
return __inner__
|
34
|
+
|
35
|
+
return __wrapper__
|
36
|
+
|
37
|
+
|
38
|
+
def post(api):
|
39
|
+
def __wrapper__(func):
|
40
|
+
@url(api)
|
41
|
+
@build_request_data()
|
42
|
+
@init_content_type()
|
43
|
+
@refresh_token()
|
44
|
+
@post_request()
|
45
|
+
@http_ok
|
46
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
47
|
+
return func(instance, *args, **kwargs)
|
48
|
+
|
49
|
+
return __inner__
|
50
|
+
|
51
|
+
return __wrapper__
|
52
|
+
|
53
|
+
|
54
|
+
def delete(api):
|
55
|
+
def __wrapper__(func):
|
56
|
+
@url(api)
|
57
|
+
@build_request_data()
|
58
|
+
@init_content_type()
|
59
|
+
@refresh_token()
|
60
|
+
@delete_request()
|
61
|
+
@http_ok
|
62
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
63
|
+
return func(instance, *args, **kwargs)
|
64
|
+
|
65
|
+
return __inner__
|
66
|
+
|
67
|
+
return __wrapper__
|
68
|
+
|
69
|
+
|
70
|
+
def put(api):
|
71
|
+
def __wrapper__(func):
|
72
|
+
@url(api)
|
73
|
+
@build_request_data()
|
74
|
+
@init_content_type()
|
75
|
+
@refresh_token()
|
76
|
+
@put_request()
|
77
|
+
@http_ok
|
78
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
79
|
+
return func(instance, *args, **kwargs)
|
80
|
+
|
81
|
+
return __inner__
|
82
|
+
|
83
|
+
return __wrapper__
|
84
|
+
|
85
|
+
|
86
|
+
def get_request():
|
87
|
+
def __wrapper__(func):
|
88
|
+
def __inner__(instance: BizBase, **kwargs):
|
89
|
+
app_url = kwargs.pop("app_url")
|
90
|
+
rsp = requests.get(app_url, headers=instance.headers, **kwargs)
|
91
|
+
kwargs.update(rsp=rsp)
|
92
|
+
return func(instance, **kwargs)
|
93
|
+
|
94
|
+
return __inner__
|
95
|
+
|
96
|
+
return __wrapper__
|
97
|
+
|
98
|
+
|
99
|
+
def post_request():
|
100
|
+
def __wrapper__(func):
|
101
|
+
def __inner__(instance: BizBase, **kwargs):
|
102
|
+
app_url = kwargs.pop("app_url")
|
103
|
+
rsp = requests.post(app_url, headers=instance.headers, **kwargs)
|
104
|
+
kwargs.update(rsp=rsp)
|
105
|
+
return func(instance, **kwargs)
|
106
|
+
|
107
|
+
return __inner__
|
108
|
+
|
109
|
+
return __wrapper__
|
110
|
+
|
111
|
+
|
112
|
+
def delete_request():
|
113
|
+
def __wrapper__(func):
|
114
|
+
def __inner__(instance: BizBase, **kwargs):
|
115
|
+
app_url = kwargs.pop("app_url")
|
116
|
+
rsp = requests.delete(app_url, headers=instance.headers, **kwargs)
|
117
|
+
kwargs.update(rsp=rsp)
|
118
|
+
return func(instance, **kwargs)
|
119
|
+
|
120
|
+
return __inner__
|
121
|
+
|
122
|
+
return __wrapper__
|
123
|
+
|
124
|
+
|
125
|
+
def put_request():
|
126
|
+
def __wrapper__(func):
|
127
|
+
def __inner__(instance: BizBase, **kwargs):
|
128
|
+
app_url = kwargs.pop("app_url")
|
129
|
+
rsp = requests.put(app_url, headers=instance.headers, **kwargs)
|
130
|
+
kwargs.update(rsp=rsp)
|
131
|
+
return func(instance, **kwargs)
|
132
|
+
|
133
|
+
return __inner__
|
134
|
+
|
135
|
+
return __wrapper__
|
136
|
+
|
137
|
+
|
138
|
+
def url(api):
|
139
|
+
def __wrapper__(func):
|
140
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
141
|
+
url_kwargs = kwargs.pop("url_kwargs", {})
|
142
|
+
if instance.user.external is False:
|
143
|
+
app_url = AppUrl(instance.user.app, instance.user.test_env).which_url(
|
144
|
+
instance.user.app)(api, **url_kwargs)
|
145
|
+
else:
|
146
|
+
app_url = AppUrl(instance.user.app, instance.user.test_env).external_url(api, **url_kwargs)
|
147
|
+
if app_url is None:
|
148
|
+
raise Exception("The url is null.")
|
149
|
+
kwargs.update(dict(app_url=app_url))
|
150
|
+
return func(instance, *args, **kwargs)
|
151
|
+
|
152
|
+
return __inner__
|
153
|
+
|
154
|
+
return __wrapper__
|
155
|
+
|
156
|
+
|
157
|
+
def build_request_data():
|
158
|
+
def __wrapper__(func):
|
159
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
160
|
+
for key in ["json", "params"]:
|
161
|
+
value = kwargs.get(key)
|
162
|
+
if value is not None:
|
163
|
+
if issubclass(value.__class__, BaseDto):
|
164
|
+
kwargs[key] = value.to_dict()
|
165
|
+
elif isinstance(value, list):
|
166
|
+
kwargs[key] = [i.to_dict() if issubclass(i.__class__, BaseDto) else i for i in value]
|
167
|
+
elif isinstance(value, dict):
|
168
|
+
if key == "params":
|
169
|
+
base_dto_keys = [k for k, v in value.items() if issubclass(v.__class__, BaseDto)]
|
170
|
+
if len(base_dto_keys) > 0:
|
171
|
+
base_dto_dict = dict()
|
172
|
+
for k in base_dto_keys:
|
173
|
+
dto_dict = value.pop(k).to_dict()
|
174
|
+
if dto_dict.keys() & base_dto_dict:
|
175
|
+
raise Exception("Duplicate keys found in BaseDto dictionaries")
|
176
|
+
base_dto_dict.update(dto_dict)
|
177
|
+
if base_dto_dict.keys() & value:
|
178
|
+
raise Exception("BaseDto keys overlap with non-BaseDto keys")
|
179
|
+
value.update(base_dto_dict)
|
180
|
+
else:
|
181
|
+
kwargs[key] = {k: v.to_dict() if issubclass(v.__class__, BaseDto) else v for k, v in
|
182
|
+
value.items()}
|
183
|
+
data = kwargs.get("data")
|
184
|
+
if data is None and "multipart_encoder_kwargs" in kwargs:
|
185
|
+
multipart_encoder_kwargs = kwargs.pop("multipart_encoder_kwargs")
|
186
|
+
if multipart_encoder_kwargs is not None:
|
187
|
+
multipart_encoder_data = list()
|
188
|
+
for k, v in multipart_encoder_kwargs.items():
|
189
|
+
if issubclass(v.__class__, BaseDto):
|
190
|
+
multipart_encoder_data.extend(v.build_multipart_fields(values_as_str=True, has_none=False))
|
191
|
+
elif isinstance(v, list) and all(
|
192
|
+
isinstance(file, str) and os.path.exists(file) and os.path.isfile(file) for file in v):
|
193
|
+
multipart_encoder_data.extend([(k, build_file_dto(file)) for file in v])
|
194
|
+
elif isinstance(v, str) and os.path.exists(v) and os.path.isfile(v):
|
195
|
+
multipart_encoder_data.append((k, build_file_dto(v)))
|
196
|
+
else:
|
197
|
+
multipart_encoder_data.append((k, v))
|
198
|
+
if len(multipart_encoder_data) > 0:
|
199
|
+
data = MultipartEncoder(fields=multipart_encoder_data)
|
200
|
+
kwargs.update(dict(data=data))
|
201
|
+
return func(instance, *args, **kwargs)
|
202
|
+
|
203
|
+
return __inner__
|
204
|
+
|
205
|
+
return __wrapper__
|
206
|
+
|
207
|
+
|
208
|
+
def init_content_type():
|
209
|
+
def __wrapper__(func):
|
210
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
211
|
+
if instance.refresh_content_type is True:
|
212
|
+
instance.headers.update({"Content-Type": "application/json"})
|
213
|
+
data = kwargs.get("data")
|
214
|
+
if isinstance(data, MultipartEncoder):
|
215
|
+
instance.headers.update({"Content-Type": data.content_type})
|
216
|
+
return func(instance, *args, **kwargs)
|
217
|
+
|
218
|
+
return __inner__
|
219
|
+
|
220
|
+
return __wrapper__
|
221
|
+
|
222
|
+
|
223
|
+
def refresh_token():
|
224
|
+
def __wrapper__(func):
|
225
|
+
def __inner__(instance: BizBase, *args, **kwargs):
|
226
|
+
instance.user.external is False and (
|
227
|
+
int(time.time() - instance.time_mills) < 60 * 30 - 300 or instance.login())
|
228
|
+
return func(instance, *args, **kwargs)
|
229
|
+
|
230
|
+
return __inner__
|
231
|
+
|
232
|
+
return __wrapper__
|
233
|
+
|
234
|
+
|
235
|
+
def http_ok(func):
|
236
|
+
@wraps(func)
|
237
|
+
def _http_ok(instance: BizBase, **kwargs):
|
238
|
+
try:
|
239
|
+
rsp: Response = kwargs.pop("rsp")
|
240
|
+
instance.last_kwargs = kwargs
|
241
|
+
instance.last_result = rsp
|
242
|
+
content_type = rsp.headers.get("content-type")
|
243
|
+
if rsp.status_code not in [200, 201]:
|
244
|
+
raise ApiResponseException(rsp)
|
245
|
+
if not content_type:
|
246
|
+
raise ApiResponseException(rsp, message="The content-type in the response header is empty.")
|
247
|
+
if "application/json" in content_type:
|
248
|
+
proc_code = rsp.json().get("procCode")
|
249
|
+
if proc_code not in [200, 201]:
|
250
|
+
raise ApiResponseException(rsp)
|
251
|
+
else:
|
252
|
+
return rsp.json().get("payload")
|
253
|
+
elif any(item in content_type for item in ["application/octet-stream", "application/vnd.ms-excel"]):
|
254
|
+
return rsp.content
|
255
|
+
else:
|
256
|
+
raise ApiResponseException(rsp, message="Please handle the {0} request.".format(content_type))
|
257
|
+
except Exception as e:
|
258
|
+
if instance.raises_exception is True:
|
259
|
+
raise
|
260
|
+
else:
|
261
|
+
# Handle non-exception case here if necessary
|
262
|
+
print(f"Exception caught but not re-raised: {e}")
|
263
|
+
|
264
|
+
return _http_ok
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 8/5/2024 4:35 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: exceptions.py
|
8
|
+
"""
|
9
|
+
from urllib.parse import urlparse
|
10
|
+
|
11
|
+
from requests import Response
|
12
|
+
|
13
|
+
|
14
|
+
class ApiResponseException(Exception):
|
15
|
+
def __init__(self, response: Response, message=None, exception=None, request_payload=None):
|
16
|
+
self.status_code = response.status_code
|
17
|
+
self.api = urlparse(response.url).path
|
18
|
+
self.method = response.request.method
|
19
|
+
self.message = message
|
20
|
+
self.exception = exception
|
21
|
+
self.request_payload = request_payload
|
22
|
+
self.proc_code = None
|
23
|
+
content_type = response.headers.get("content-type")
|
24
|
+
if not content_type:
|
25
|
+
...
|
26
|
+
elif "application/json" in content_type:
|
27
|
+
self.proc_code = response.json().get("procCode")
|
28
|
+
self.message = response.json().get("message") or message
|
29
|
+
self.exception = response.json().get("exception") or exception
|
30
|
+
else:
|
31
|
+
self.message = "\n".join([message, response.text]) if message else response.text
|
32
|
+
|
33
|
+
def __str__(self):
|
34
|
+
messages = [
|
35
|
+
"\nRequest response exception:",
|
36
|
+
"Status code: {0}".format(self.status_code),
|
37
|
+
"URL: {0} {1}".format(self.method, self.api),
|
38
|
+
]
|
39
|
+
if self.request_payload:
|
40
|
+
if isinstance(self.request_payload, dict):
|
41
|
+
payload = "\n".join(["{0}: {1}".format(k, v) for k, v in self.request_payload.items()])
|
42
|
+
else:
|
43
|
+
payload = str(self.request_payload)
|
44
|
+
messages.append("Request payload: {0}".format(payload))
|
45
|
+
if self.proc_code:
|
46
|
+
messages.append("Proc code: {0}".format(self.proc_code))
|
47
|
+
if self.message:
|
48
|
+
messages.append("Message: {0}".format(self.message))
|
49
|
+
if self.exception:
|
50
|
+
messages.append("Exception: {0}".format(self.exception))
|
51
|
+
return "\n".join(messages)
|
52
|
+
|
53
|
+
|
54
|
+
class ApiValidationError(ApiResponseException):
|
55
|
+
"""自定义异常类,用于验证接口失败时引发错误"""
|
56
|
+
|
57
|
+
def __init__(self, response: Response, message=None, exception=None, request_payload=None):
|
58
|
+
super().__init__(response, message, exception, request_payload)
|
59
|
+
|
60
|
+
|
61
|
+
class DatabaseValidationError(Exception):
|
62
|
+
"""自定义异常类,用于数据库验证失败时引发错误"""
|
63
|
+
|
64
|
+
def __init__(self, message):
|
65
|
+
super().__init__(message)
|
66
|
+
|
67
|
+
|
68
|
+
class FileValidationError(Exception):
|
69
|
+
"""自定义异常类,用于文件验证失败时引发错误"""
|
70
|
+
|
71
|
+
def __init__(self, message):
|
72
|
+
super().__init__(message)
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 7/24/2023 3:48 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: gateway.py
|
8
|
+
"""
|
9
|
+
import os.path
|
10
|
+
import re
|
11
|
+
from urllib.parse import urlparse
|
12
|
+
|
13
|
+
from requests.structures import LookupDict
|
14
|
+
|
15
|
+
from api_requester.utils.constant import AppEnum
|
16
|
+
from api_requester.utils.path import docs_path
|
17
|
+
from api_requester.utils.read_file import connect_to
|
18
|
+
|
19
|
+
_api = {
|
20
|
+
AppEnum.ADMIN.code: (AppEnum.ADMIN.code,),
|
21
|
+
AppEnum.EDC.code: (AppEnum.EDC.code,),
|
22
|
+
AppEnum.CTMS.code: (AppEnum.CTMS.code,),
|
23
|
+
AppEnum.ETMF.code: (AppEnum.ETMF.code,),
|
24
|
+
AppEnum.DESIGN.code: ('designer', AppEnum.DESIGN.code),
|
25
|
+
AppEnum.IWRS.code: (AppEnum.IWRS.code,),
|
26
|
+
"external": ('external',),
|
27
|
+
AppEnum.CODING.code: (AppEnum.CODING.code,),
|
28
|
+
AppEnum.IMAGING.code: (AppEnum.IMAGING.code,),
|
29
|
+
AppEnum.PV.code: (AppEnum.PV.code,),
|
30
|
+
}
|
31
|
+
apis = LookupDict(name='api')
|
32
|
+
|
33
|
+
|
34
|
+
def _init():
|
35
|
+
for app_api, apps in _api.items():
|
36
|
+
for app in apps:
|
37
|
+
setattr(apis, app, app_api)
|
38
|
+
|
39
|
+
|
40
|
+
_init()
|
41
|
+
|
42
|
+
|
43
|
+
class Gateway(object):
|
44
|
+
def __init__(self, test_env):
|
45
|
+
self.test_env = test_env
|
46
|
+
yaml_path = os.path.join(docs_path(), "application.yaml")
|
47
|
+
self.api_yaml = connect_to(yaml_path).data
|
48
|
+
|
49
|
+
def _url(self, app_api, api, external=False):
|
50
|
+
server_url = self._server_url(app_api)
|
51
|
+
api_url = self._ex_domain(app_api, api) or self._in_domain(app_api, api, external)
|
52
|
+
return "{0}{1}".format(server_url, api_url)
|
53
|
+
|
54
|
+
def _server_url(self, app_api):
|
55
|
+
server_url_mapping = self.api_yaml["serverUrl"]
|
56
|
+
server_url = server_url_mapping.get(self.test_env)
|
57
|
+
if server_url is not None:
|
58
|
+
return server_url
|
59
|
+
else:
|
60
|
+
postfix = dict(portal_api=".admin", ctms_api=".ctms", designer_api=".designer",
|
61
|
+
etmf_api=".etmf", edc_api=".edc", iwrs_api=".iwrs").get(app_api)
|
62
|
+
return server_url_mapping.get("{0}{1}".format(self.test_env, postfix))
|
63
|
+
|
64
|
+
def _in_domain(self, app_api, api, external=False):
|
65
|
+
domain = self.api_yaml.get(app_api).get("urlPrefix")
|
66
|
+
api_url = self._disposed(app_api, api) or self._not_disposed(api)
|
67
|
+
return api_url if (external and domain in api_url) else "{0}{1}".format(domain, api_url)
|
68
|
+
|
69
|
+
def _ex_domain(self, app_api, api):
|
70
|
+
api_url = self._disposed(app_api, api) or self._not_disposed(api)
|
71
|
+
if "local" in self.test_env:
|
72
|
+
return api_url
|
73
|
+
return None
|
74
|
+
|
75
|
+
def _disposed(self, app_api, api):
|
76
|
+
return self.api_yaml.get(app_api).get(api)
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def _not_disposed(api):
|
80
|
+
return api
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
def get_app_api(app):
|
84
|
+
return eval(f"apis.{app.lower()}")
|
85
|
+
|
86
|
+
def url(self, app, api, external=False):
|
87
|
+
# if external: # s3前端迁移
|
88
|
+
# self.test_env = f"{self.test_env}.external"
|
89
|
+
tmp_url = self._url(self.get_app_api(external is False and app or "external"), api, external)
|
90
|
+
return self.rewrite_path(app, tmp_url)
|
91
|
+
|
92
|
+
def netloc(self, app):
|
93
|
+
server_url = self._server_url(self.get_app_api(app))
|
94
|
+
return urlparse(server_url).netloc
|
95
|
+
|
96
|
+
def rewrite_path(self, app, api):
|
97
|
+
app_config = self.api_yaml.get(app)
|
98
|
+
filters = app_config.get("filters")
|
99
|
+
if filters is not None:
|
100
|
+
for item in filters:
|
101
|
+
if item.startswith("RewritePath="):
|
102
|
+
item = item.replace("RewritePath=", "")
|
103
|
+
rewrite_rule, target = item.split(",")
|
104
|
+
api = re.sub(rewrite_rule, target, api)
|
105
|
+
return api
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# !/usr/bin/python3
|
2
|
+
# -*- coding:utf-8 -*-
|
3
|
+
"""
|
4
|
+
@Author: xiaodong.li
|
5
|
+
@Time: 7/24/2023 3:48 PM
|
6
|
+
@Description: Description
|
7
|
+
@File: sample_headers.py
|
8
|
+
"""
|
9
|
+
|
10
|
+
|
11
|
+
class SampleHeaders(object):
|
12
|
+
def __init__(self):
|
13
|
+
self.headers = {"Content-Type": "application/json", "Connection": "close"}
|
14
|
+
|
15
|
+
def add_header(self, **kwargs):
|
16
|
+
self.headers.update(kwargs)
|
17
|
+
return self
|
18
|
+
|
19
|
+
def to_h(self):
|
20
|
+
return self.headers
|
21
|
+
|
22
|
+
def add_authorization(self, token):
|
23
|
+
return self.add_header(Authorization=token)
|