Skip to content

Commit 1eed854

Browse files
committed
Merge branch 'dev' into 'master'
Dev See merge request server/openapi/openapi-python-sdk!162
2 parents 3ba1ade + 33dfd20 commit 1eed854

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4004
-464
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
## 2.3.2 (2023-03-03)
2+
### New
3+
- PushClient长链接支持Protobuf,当前版本默认不开启,可通过在 PushClient 初始化时传人 `user_protobuf=True ` 开启,
4+
未来版本将默认使用Protobuf。
5+
Protobuf方式的订阅方式与之前版本基本兼容一致;回调方法的参数改用Protobuf对象,与之前不兼容,
6+
17
## 2.3.1 (2023-02-23)
28
### New
39
- 支持2FA token自定义刷新间隔,可通过 client_config.token_refresh_duration = 0 关闭自动刷新
410
### Fix
511
- tick数据推送字段异常问题
612
- 若开启token,刷新线程不随主线程退出的问题
713

14+
815
## 2.3.0 (2023-02-16)
916
### New
1017
- 支持配置文件

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ stomp.py
99
getmac
1010
cryptography
1111
backoff
12-
jproperties
12+
jproperties
13+
protobuf==4.21.12
14+
google-cloud==0.34.0

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.3.1'
7+
__VERSION__ = '2.3.2'

tigeropen/common/consts/push_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class ResponseType(Enum):
5858
GET_KLINE_END = 108
5959
GET_TRADING_TICK_END = 109
6060
GET_QUOTE_CHANGE_END = 110
61+
6162
GET_SUB_SYMBOLS_END = 111
6263
GET_SUBSCRIBE_END = 112
6364
GET_CANCEL_SUBSCRIBE_END = 113

tigeropen/fundamental/response/corporate_dividend_response.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ def parse_response_content(self, response_content):
2626
for symbol, dividend_items in self.data.items():
2727
for item in dividend_items:
2828
item['symbol'] = symbol
29+
item['announcedDate'] = item.get('announcedDate', None)
2930
items.append(item)
30-
self.corporate_dividend = pd.DataFrame(items).rename(columns=DIVIDEND_FIELD_MAPPINGS)[COLUMNS]
31+
self.corporate_dividend = pd.DataFrame(items).rename(columns=DIVIDEND_FIELD_MAPPINGS)

tigeropen/push/network/__init__.py

Whitespace-only changes.

tigeropen/push/network/connect.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from .protocal import *
2+
from .transport import *
3+
4+
5+
class BaseConnection(Publisher):
6+
"""
7+
Base class for all connection classes.
8+
"""
9+
10+
def __init__(self, transport):
11+
"""
12+
:param Transport transport:
13+
"""
14+
self.transport = transport
15+
16+
def disconnect(self): pass
17+
18+
def set_listener(self, name, listener):
19+
self.transport.set_listener(name, listener)
20+
21+
def remove_listener(self, name):
22+
"""
23+
:param str name:
24+
"""
25+
self.transport.remove_listener(name)
26+
27+
def get_listener(self, name):
28+
"""
29+
:param str name:
30+
31+
:rtype: ConnectionListener
32+
"""
33+
return self.transport.get_listener(name)
34+
35+
def is_connected(self):
36+
"""
37+
:rtype: bool
38+
"""
39+
return self.transport.is_connected()
40+
41+
def set_ssl(self, *args, **kwargs):
42+
self.transport.set_ssl(*args, **kwargs)
43+
44+
def get_ssl(self, host_and_port=None):
45+
return self.transport.get_ssl(host_and_port)
46+
47+
48+
class PushConnection(BaseConnection, Protocol):
49+
"""
50+
"""
51+
52+
def __init__(self,
53+
host_and_ports=None,
54+
prefer_localhost=True,
55+
try_loopback_connect=True,
56+
reconnect_sleep_initial=0.1,
57+
reconnect_sleep_increase=0.5,
58+
reconnect_sleep_jitter=0.1,
59+
reconnect_sleep_max=60.0,
60+
reconnect_attempts_max=3,
61+
timeout=None,
62+
heartbeats=(0, 0),
63+
keepalive=None,
64+
vhost=None,
65+
auto_decode=True,
66+
encoding="utf-8",
67+
heart_beat_receive_scale=1.5,
68+
bind_host_port=None):
69+
transport = Transport(host_and_ports, prefer_localhost, try_loopback_connect,
70+
reconnect_sleep_initial, reconnect_sleep_increase, reconnect_sleep_jitter,
71+
reconnect_sleep_max, reconnect_attempts_max, timeout,
72+
keepalive, vhost, auto_decode, encoding, bind_host_port=bind_host_port)
73+
BaseConnection.__init__(self, transport)
74+
Protocol.__init__(self, transport, heartbeats,
75+
heart_beat_receive_scale=heart_beat_receive_scale)
76+
77+
def connect(self, *args, **kwargs):
78+
self.transport.start()
79+
Protocol.connect(self, *args, **kwargs)
80+
81+
def disconnect(self):
82+
Protocol.disconnect(self)
83+
self.transport.stop()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
class PushException(Exception):
3+
pass
4+
5+
6+
class ConnectionClosedException(PushException):
7+
pass
8+
9+
10+
class NotConnectedException(PushException):
11+
"""
12+
Raised when there is currently no server connection.
13+
"""
14+
15+
16+
class ConnectFailedException(PushException):
17+
"""
18+
Raised by Connection.attempt_connection when reconnection attempts
19+
have exceeded Connection.__reconnect_attempts_max.
20+
"""
21+
22+
23+
class InterruptedException(PushException):
24+
"""
25+
Raised by receive when data read is interrupted.
26+
"""

tigeropen/push/network/listener.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import logging
2+
import sys
3+
import threading
4+
import time
5+
from time import monotonic
6+
7+
from . import exception
8+
from . import utils
9+
from ..pb.util import ProtoMessageUtil
10+
11+
12+
class Publisher(object):
13+
"""
14+
Simply a registry of listeners.
15+
"""
16+
17+
def set_listener(self, name, listener):
18+
"""
19+
Set a named listener to use with this connection. See :py:class:`stomp.listener.ConnectionListener`
20+
21+
:param str name: the name of the listener
22+
:param ConnectionListener listener: the listener object
23+
"""
24+
pass
25+
26+
def remove_listener(self, name):
27+
"""
28+
Remove a listener.
29+
30+
:param str name: the name of the listener to remove
31+
"""
32+
pass
33+
34+
def get_listener(self, name):
35+
"""
36+
Return the named listener.
37+
38+
:param str name: the listener to return
39+
40+
:rtype: ConnectionListener
41+
"""
42+
return None
43+
44+
45+
class ConnectionListener(object):
46+
"""
47+
This class should be used as a base class for objects registered
48+
using Connection.set_listener().
49+
"""
50+
51+
def on_connecting(self, host_and_port):
52+
pass
53+
54+
def on_connected(self, frame):
55+
pass
56+
57+
def on_disconnecting(self):
58+
pass
59+
60+
def on_disconnected(self):
61+
pass
62+
63+
def on_message(self, frame):
64+
pass
65+
66+
def on_error(self, frame):
67+
pass
68+
69+
def on_send(self, frame):
70+
pass
71+
72+
def on_heartbeat(self, frame):
73+
pass
74+
75+
def on_receiver_loop_completed(self, frame):
76+
pass
77+
78+
79+
class HeartbeatListener(ConnectionListener):
80+
"""
81+
Listener used to handle heartbeating.
82+
"""
83+
def __init__(self, transport, heartbeats, heart_beat_receive_scale=1.5):
84+
self.running = False
85+
self.transport = transport
86+
self.heartbeats = heartbeats
87+
self.received_heartbeat = None
88+
self.heartbeat_thread = None
89+
self.next_outbound_heartbeat = None
90+
self.heart_beat_receive_scale = heart_beat_receive_scale
91+
self.heartbeat_terminate_event = threading.Event()
92+
self.disconnecting = False
93+
94+
def on_connected(self, frame):
95+
"""
96+
Once the connection is established, and 'heart-beat' is found in the headers, we calculate the real
97+
heartbeat numbers (based on what the server sent and what was specified by the client) - if the heartbeats
98+
are not 0, we start up the heartbeat loop accordingly.
99+
100+
:param Frame frame: the frame
101+
"""
102+
self.disconnecting = False
103+
heartbeat = ProtoMessageUtil.extract_heart_beat(frame)
104+
if heartbeat:
105+
self.heartbeats = heartbeat
106+
logging.debug("heartbeats calculated %s", str(self.heartbeats))
107+
if self.heartbeats != (0, 0):
108+
self.send_sleep = self.heartbeats[0] / 1000
109+
110+
# by default, receive gets an additional grace of 50%
111+
# set a different heart-beat-receive-scale when creating the connection to override that
112+
self.receive_sleep = (self.heartbeats[1] / 1000) * self.heart_beat_receive_scale
113+
114+
logging.debug("set receive_sleep to %s, send_sleep to %s", self.receive_sleep, self.send_sleep)
115+
116+
# Give grace of receiving the first heartbeat
117+
self.received_heartbeat = monotonic() + self.receive_sleep
118+
119+
self.running = True
120+
if self.heartbeat_thread is None:
121+
self.heartbeat_thread = utils.default_create_thread(
122+
self.__heartbeat_loop)
123+
self.heartbeat_thread.name = "Heartbeat%s" % \
124+
getattr(self.heartbeat_thread, "name", "Thread")
125+
126+
def on_disconnected(self):
127+
self.running = False
128+
self.heartbeat_terminate_event.set()
129+
130+
def on_disconnecting(self):
131+
self.disconnecting = True
132+
133+
def on_message(self, frame):
134+
"""
135+
Reset the last received time whenever a message is received.
136+
137+
:param Frame frame: the frame
138+
"""
139+
self.__update_heartbeat()
140+
141+
def on_error(self, *_):
142+
"""
143+
Reset the last received time whenever an error is received.
144+
"""
145+
self.__update_heartbeat()
146+
147+
def on_heartbeat(self, frame):
148+
"""
149+
Reset the last received time whenever a heartbeat message is received.
150+
"""
151+
self.__update_heartbeat()
152+
153+
def on_send(self, frame):
154+
"""
155+
Add the heartbeat header to the frame when connecting, and bump
156+
next outbound heartbeat timestamp.
157+
158+
:param Frame frame: the Frame object
159+
"""
160+
if self.next_outbound_heartbeat is not None:
161+
self.next_outbound_heartbeat = monotonic() + self.send_sleep
162+
163+
def __update_heartbeat(self):
164+
if self.received_heartbeat is None:
165+
return
166+
now = monotonic()
167+
if now > self.received_heartbeat:
168+
self.received_heartbeat = now
169+
170+
def __heartbeat_loop(self):
171+
"""
172+
Main loop for sending (and monitoring received) heartbeats.
173+
"""
174+
logging.debug("starting heartbeat loop")
175+
now = monotonic()
176+
177+
# Setup the initial due time for the outbound heartbeat
178+
if self.send_sleep != 0:
179+
self.next_outbound_heartbeat = now + self.send_sleep
180+
logging.debug("calculated next outbound heartbeat as %s", self.next_outbound_heartbeat)
181+
182+
while self.running:
183+
now = monotonic()
184+
185+
next_events = []
186+
if self.next_outbound_heartbeat is not None:
187+
next_events.append(self.next_outbound_heartbeat - now)
188+
if self.receive_sleep != 0:
189+
t = self.received_heartbeat + self.receive_sleep - now
190+
if t > 0:
191+
next_events.append(t)
192+
sleep_time = min(next_events) if next_events else 0
193+
if sleep_time > 0:
194+
terminate = self.heartbeat_terminate_event.wait(sleep_time)
195+
if terminate:
196+
break
197+
198+
now = monotonic()
199+
200+
if not self.transport.is_connected() or self.disconnecting:
201+
time.sleep(self.send_sleep)
202+
continue
203+
204+
if self.send_sleep != 0 and now > self.next_outbound_heartbeat:
205+
logging.debug("sending a heartbeat message at %s", now)
206+
try:
207+
msg = ProtoMessageUtil.build_heart_beat_message()
208+
self.transport.transmit(msg)
209+
except exception.NotConnectedException:
210+
logging.debug("lost connection, unable to send heartbeat")
211+
except Exception:
212+
_, e, _ = sys.exc_info()
213+
logging.debug("unable to send heartbeat, due to: %s", e)
214+
215+
if self.receive_sleep != 0:
216+
diff_receive = now - self.received_heartbeat
217+
218+
if diff_receive > self.receive_sleep:
219+
# heartbeat timeout
220+
logging.warning("heartbeat timeout: diff_receive=%s, time=%s, lastrec=%s",
221+
diff_receive, now, self.received_heartbeat)
222+
self.transport.set_connected(False)
223+
self.transport.disconnect_socket()
224+
self.transport.stop()
225+
for listener in self.transport.listeners.values():
226+
listener.on_heartbeat_timeout()
227+
self.heartbeat_thread = None
228+
self.heartbeat_terminate_event.clear()
229+
if self.heartbeats != (0, 0):
230+
# don't bother logging this if heartbeats weren't setup to start with
231+
logging.debug("heartbeat loop ended")

0 commit comments

Comments
 (0)