Skip to content

Commit 10a2d2e

Browse files
committed
Merge branch 'dev' into 'master'
Dev See merge request server/openapi/openapi-python-sdk!156
2 parents df53ee9 + b4e0a2e commit 10a2d2e

File tree

8 files changed

+160
-16
lines changed

8 files changed

+160
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,4 @@ cython_debug/
139139

140140
.idea
141141
.DS_Store
142+
*.properties

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.3.0 (2023-02-16)
2+
### New
3+
- 支持配置文件
4+
- 支持2FA token
5+
6+
17
## 2.2.9 (2023-02-09)
28
### Fix
39
- 修复tick数据推送报错的问题

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ rsa
88
stomp.py
99
getmac
1010
cryptography
11-
backoff
11+
backoff
12+
jproperties

tigeropen/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
55
@author: gaoan
66
"""
7-
__VERSION__ = '2.2.9'
7+
__VERSION__ = '2.3.0'

tigeropen/common/consts/service_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
55
@author: gaoan
66
"""
7+
USER_TOKEN_REFRESH = "user_token_refresh"
78

9+
# 订单
810
ORDER_NO = "order_no"
911
PREVIEW_ORDER = "preview_order"
1012
PLACE_ORDER = "place_order"

tigeropen/common/util/web_utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,13 @@ def do_request(method, url, query_string=None, headers=None, params=None, timeou
6969
connection.connect()
7070
except Exception as e:
7171
raise RequestException('[' + THREAD_LOCAL.uuid + ']' + method + ' connect failed. url: ' + url
72+
+ ' headers: ' + str(headers)
7273
+ ' params: ' + str(params) + ' detail: ' + str(e))
7374
try:
7475
connection.request(method, url, body=json.dumps(params), headers=headers)
7576
except Exception as e:
7677
raise RequestException('[' + THREAD_LOCAL.uuid + ']' + method + ' request failed. url: ' + url
78+
+ ' headers: ' + str(headers)
7779
+ ' params: ' + str(params) + ' detail: ' + str(e))
7880
response = connection.getresponse()
7981
result = response.read()
@@ -82,7 +84,8 @@ def do_request(method, url, query_string=None, headers=None, params=None, timeou
8284
if charset:
8385
result = result.decode(charset)
8486
raise ResponseException('[' + THREAD_LOCAL.uuid + ']invalid http status ' + str(response.status) +
85-
',detail body:' + result + ' params: ' + str(params))
87+
' headers: ' + str(headers) +
88+
' detail body:' + result + ' params: ' + str(params))
8689
try:
8790
response.close()
8891
connection.close()

tigeropen/tiger_open_client.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88
import json
99
import logging
1010
import uuid
11+
from threading import Timer
1112

1213
import backoff
1314

1415
from tigeropen import __VERSION__
1516
from tigeropen.common.consts import OPEN_API_SERVICE_VERSION, THREAD_LOCAL
1617
from tigeropen.common.consts.params import P_TIMESTAMP, P_TIGER_ID, P_METHOD, P_CHARSET, P_VERSION, P_SIGN_TYPE, \
1718
P_DEVICE_ID, P_NOTIFY_URL, COMMON_PARAM_KEYS, P_SIGN
18-
from tigeropen.common.consts.service_types import USER_LICENSE, PLACE_ORDER, CANCEL_ORDER, MODIFY_ORDER
19+
from tigeropen.common.consts.service_types import USER_LICENSE, PLACE_ORDER, CANCEL_ORDER, MODIFY_ORDER, \
20+
USER_TOKEN_REFRESH
1921
from tigeropen.common.exceptions import ResponseException, RequestException
2022
from tigeropen.common.request import OpenApiRequest
2123
from tigeropen.common.response import TigerResponse
@@ -33,14 +35,21 @@ def get_mac_address():
3335
SKIP_RETRY_SERVICES = {PLACE_ORDER, CANCEL_ORDER, MODIFY_ORDER}
3436

3537

38+
_SCHEDULE_STATE = {'is_running': False}
39+
TOKEN_CHECK_INTERVAL = 300
40+
41+
3642
class TigerOpenClient:
3743
"""
3844
client_config:客户端配置,包含tiger_id、应用私钥、老虎公钥等
3945
logger:日志对象,客户端执行信息会通过此日志对象输出
4046
"""
41-
4247
def __init__(self, client_config, logger=None):
4348
self.__config = client_config
49+
if not client_config.private_key:
50+
raise Exception('private key can not be empty')
51+
if not client_config.tiger_id:
52+
raise Exception('tiger id can not be empty')
4453
if logger:
4554
if client_config.log_level:
4655
logger.setLevel(logging.getLevelName(client_config.log_level))
@@ -58,6 +67,8 @@ def __init__(self, client_config, logger=None):
5867
self.__device_id = self.__get_device_id()
5968
self.__init_license()
6069
self.__refresh_server_info()
70+
if self.__config.token and self.__config.license:
71+
self.__schedule_thread()
6172

6273
def __init_license(self):
6374
if self.__config.license is None and self.__config.enable_dynamic_domain:
@@ -156,6 +167,9 @@ def __parse_response(self, response_str, timestamp=None):
156167

157168
return response_content
158169

170+
def _update_header(self):
171+
self.__headers['Authorization'] = self.__config.token
172+
159173
def _get_retry_deco(self, service):
160174
if service not in SKIP_RETRY_SERVICES and self.__config.retry_max_tries > 0:
161175
return backoff.on_exception(backoff.fibo,
@@ -169,6 +183,8 @@ def _get_retry_deco(self, service):
169183
执行接口请求
170184
"""
171185
def execute(self, request, url=None):
186+
if self.__config.token:
187+
self._update_header()
172188
if url is None:
173189
url = self.__config.server_url
174190
THREAD_LOCAL.uuid = str(uuid.uuid1())
@@ -198,3 +214,42 @@ def query_license(self):
198214
if response.is_success():
199215
return json.loads(response.data).get('license')
200216
self.__logger.error(f"failed to query license, response: {response_content}")
217+
218+
def refresh_token(self):
219+
request = OpenApiRequest(method=USER_TOKEN_REFRESH)
220+
221+
response_content = None
222+
try:
223+
response_content = self.execute(request)
224+
except Exception as e:
225+
self.__logger.error(e, exc_info=True)
226+
if response_content:
227+
response = TigerResponse()
228+
response.parse_response_content(response_content)
229+
if response.is_success():
230+
return json.loads(response.data).get('token')
231+
self.__logger.error(f"failed to refresh token, response: {response_content}")
232+
return None
233+
234+
def token_refresh_task(self):
235+
try:
236+
if self.__config.should_token_refresh():
237+
new_token = self.refresh_token()
238+
if new_token:
239+
self.__logger.info(f"refresh token, old:{self.__config.token}, new:{new_token}")
240+
self.__config.load_or_store_token(new_token)
241+
except Exception as e:
242+
self.__logger.error(e, exc_info=True)
243+
244+
def __schedule_thread(self):
245+
if not _SCHEDULE_STATE['is_running']:
246+
_SCHEDULE_STATE['is_running'] = True
247+
self.__logger.info('Starting schedule thread...')
248+
daemon = RepeatTimer(TOKEN_CHECK_INTERVAL, self.token_refresh_task)
249+
daemon.start()
250+
251+
252+
class RepeatTimer(Timer):
253+
def run(self):
254+
while not self.finished.wait(self.interval):
255+
self.function(*self.args, **self.kwargs)

tigeropen/tiger_open_config.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
55
@author: gaoan
66
"""
7+
import base64
78
import json
89
import logging
10+
import os
11+
import time
912

13+
from jproperties import Properties
1014
from pytz import timezone
1115
from tigeropen.common.consts import Language, ServiceType
1216
from tigeropen.common.util.account_util import AccountUtil
@@ -47,9 +51,13 @@
4751
'MidihTvHHf+tJ0PYD0o3PruI0hl3qhEjHTAxb75T5YD3SGK4IBhHn/Rk6mhqlGgI+bBrBVYaXixm' \
4852
'HfRo75RpUUuWACyeqQkZckgR0McxuW9xRMIa2cXZOoL1E4SL4lXKGhKoWbwIDAQAB'
4953

54+
DEFAULT_PROPS_FILE = 'tiger_openapi_config.properties'
55+
DEFAULT_TOKEN_FILE = 'tiger_openapi_token.properties'
56+
TOKEN_REFRESH_DURATION = 24 * 60 * 60 # seconds
57+
5058

5159
class TigerOpenClientConfig:
52-
def __init__(self, sandbox_debug=False, enable_dynamic_domain=True):
60+
def __init__(self, sandbox_debug=None, enable_dynamic_domain=True, props_path='.'):
5361
# 开发者应用id
5462
self._tiger_id = ''
5563
# 授权账户
@@ -80,23 +88,27 @@ def __init__(self, sandbox_debug=False, enable_dynamic_domain=True):
8088
self._server_url = SERVER_URL
8189
self._quote_server_url = SERVER_URL
8290
self._socket_host_port = SOCKET_HOST_PORT
83-
if sandbox_debug:
91+
92+
self.log_level = None
93+
self.log_path = None
94+
self.retry_max_time = 60
95+
self.retry_max_tries = 5
96+
self.props_path = props_path
97+
self.token = None
98+
self._load_props()
99+
self.load_or_store_token()
100+
101+
self.domain_conf = dict()
102+
self.enable_dynamic_domain = enable_dynamic_domain
103+
if self._sandbox_debug:
84104
self._tiger_public_key = SANDBOX_TIGER_PUBLIC_KEY
85105
self._server_url = SANDBOX_SERVER_URL
86106
self._quote_server_url = SANDBOX_SERVER_URL
87107
self._socket_host_port = SANDBOX_SOCKET_HOST_PORT
88-
89-
self.domain_conf = dict()
90-
self.enable_dynamic_domain = enable_dynamic_domain
91-
if enable_dynamic_domain:
108+
if self.enable_dynamic_domain:
92109
self.domain_conf = self.query_domains()
93110
self.refresh_server_info()
94111

95-
self.log_level = None
96-
self.log_path = None
97-
self.retry_max_time = 60
98-
self.retry_max_tries = 5
99-
100112
@property
101113
def tiger_id(self):
102114
return self._tiger_id
@@ -221,6 +233,70 @@ def secret_key(self):
221233
def secret_key(self, value):
222234
self._secret_key = value
223235

236+
@property
237+
def token(self):
238+
return self._token
239+
240+
@token.setter
241+
def token(self, value):
242+
self._token = value
243+
244+
def _get_props_path(self, filename):
245+
if self.props_path is not None:
246+
if os.path.isdir(self.props_path):
247+
full_path = os.path.join(self.props_path, filename)
248+
else:
249+
dirname = os.path.dirname(self.props_path)
250+
full_path = os.path.join(dirname, filename)
251+
return full_path
252+
return None
253+
254+
def _load_props(self):
255+
full_path = self._get_props_path(DEFAULT_PROPS_FILE)
256+
if full_path and os.path.exists(full_path):
257+
try:
258+
p = Properties()
259+
with open(full_path, "rb") as f:
260+
p.load(f, "utf-8")
261+
if not self.tiger_id:
262+
self.tiger_id = getattr(p.get('tiger_id'), 'data', '')
263+
if not self.private_key:
264+
self.private_key = getattr(p.get('private_key_pk1'), 'data', '')
265+
if not self.account:
266+
self.account = getattr(p.get('account'), 'data', '')
267+
if not self.license:
268+
self.license = getattr(p.get('license'), 'data', '')
269+
if not self._sandbox_debug:
270+
is_sandbox_env = getattr(p.get('env'), 'data', '').upper() == 'SANDBOX'
271+
self._sandbox_debug = is_sandbox_env
272+
except Exception as e:
273+
logging.error(e, exc_info=True)
274+
275+
def load_or_store_token(self, token=None):
276+
full_path = self._get_props_path(DEFAULT_TOKEN_FILE)
277+
if full_path and os.path.exists(full_path):
278+
try:
279+
p = Properties()
280+
with open(full_path, "r+b") as f:
281+
if token:
282+
self.token = token
283+
p['token'] = token
284+
p.store(f, encoding='utf-8')
285+
else:
286+
p.load(f, "utf-8")
287+
if not self.token:
288+
self.token = getattr(p.get('token'), 'data', '')
289+
except Exception as e:
290+
logging.error(e, exc_info=True)
291+
292+
def should_token_refresh(self, duration=TOKEN_REFRESH_DURATION):
293+
if self.token:
294+
tokeninfo = base64.b64decode(self.token)
295+
gen_ts, expire_ts = tokeninfo[:27].decode('utf-8').split(',')
296+
if (int(time.time()) - int(gen_ts) // 1000) > duration:
297+
return True
298+
return False
299+
224300
def refresh_server_info(self):
225301
if self.enable_dynamic_domain and self.domain_conf:
226302
if self.license:

0 commit comments

Comments
 (0)