cfquant 0.2.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.
- LTtx/__init__.py +2 -0
- LTtx/tx/Config.txt +8 -0
- LTtx/tx/LTtx_server.py +1177 -0
- LTtx/tx/__init__.py +5 -0
- LTtx/tx/tx.py +1935 -0
- cfquant/__init__.py +26 -0
- cfquant/account_routing.py +119 -0
- cfquant/channels.py +76 -0
- cfquant/cli.py +266 -0
- cfquant/client.py +365 -0
- cfquant/config.py +86 -0
- cfquant/hub.py +304 -0
- cfquant/logging_i18n.py +348 -0
- cfquant/normal_bridge.py +676 -0
- cfquant/pipe_bridge.py +249 -0
- cfquant/pipe_client.py +223 -0
- cfquant/pipe_hub.py +602 -0
- cfquant/pipe_transport.py +493 -0
- cfquant/protocol.py +238 -0
- cfquant/qmt_bridge.py +1081 -0
- cfquant/runtime_report.py +334 -0
- cfquant/tx.py +1898 -0
- cfquant/tx_trade_bridge.py +1905 -0
- cfquant/version.py +5 -0
- cfquant/xtconstant.py +262 -0
- cfquant/xtdata.py +462 -0
- cfquant/xttrader.py +955 -0
- cfquant/xttype.py +586 -0
- cfquant-0.2.0.dist-info/LICENSE +21 -0
- cfquant-0.2.0.dist-info/METADATA +144 -0
- cfquant-0.2.0.dist-info/RECORD +90 -0
- cfquant-0.2.0.dist-info/WHEEL +5 -0
- cfquant-0.2.0.dist-info/entry_points.txt +4 -0
- cfquant-0.2.0.dist-info/top_level.txt +6 -0
- cfquant_pipe_hub.py +45 -0
- cfquant_web_server.py +12137 -0
- qmt_scripts/CFQUANT.py +507 -0
- qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +830 -0
- qmt_scripts/CFQUANT_LITE.py +4928 -0
- qmt_scripts/CFQUANT_TRADE_LOWLAT.py +382 -0
- qmt_scripts/__init__.py +1 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SH.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SH.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SZ.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SH.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SZ.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/readme.md +95 -0
- web_dashboard/__init__.py +1 -0
- web_dashboard/app.js +9847 -0
- web_dashboard/avatars/amber-pulse.svg +15 -0
- web_dashboard/avatars/copper-grid.svg +15 -0
- web_dashboard/avatars/market-blue.svg +15 -0
- web_dashboard/avatars/rose-circuit.svg +15 -0
- web_dashboard/avatars/signal-green.svg +15 -0
- web_dashboard/avatars/slate-wave.svg +15 -0
- web_dashboard/avatars/teal-orbit.svg +16 -0
- web_dashboard/avatars/violet-node.svg +15 -0
- web_dashboard/favicon.svg +5 -0
- web_dashboard/image/qmt_deploy/image-10.png +0 -0
- web_dashboard/image/qmt_deploy/image-11.png +0 -0
- web_dashboard/image/qmt_deploy/image-12.png +0 -0
- web_dashboard/image/qmt_deploy/image-13.png +0 -0
- web_dashboard/image/qmt_deploy/image-14.png +0 -0
- web_dashboard/image/qmt_deploy/image-15.png +0 -0
- web_dashboard/image/qmt_deploy/image-16.png +0 -0
- web_dashboard/image/qmt_deploy/image-17.png +0 -0
- web_dashboard/image/qmt_deploy/image-18.png +0 -0
- web_dashboard/image/qmt_deploy/image-19.png +0 -0
- web_dashboard/image/qmt_deploy/image-20.png +0 -0
- web_dashboard/image/qmt_deploy/image-21.png +0 -0
- web_dashboard/image/qmt_deploy/image-4.png +0 -0
- web_dashboard/image/qmt_deploy/image-5.png +0 -0
- web_dashboard/image/qmt_deploy/image-6.png +0 -0
- web_dashboard/image/qmt_deploy/image-7.png +0 -0
- web_dashboard/image/qmt_deploy/image-8.png +0 -0
- web_dashboard/image/qmt_deploy/image-9.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-1.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-2.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-3.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479325179.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479745262.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480178700.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480428759.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783489807217.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783490503491.png +0 -0
- web_dashboard/index.html +2317 -0
- web_dashboard/styles.css +7817 -0
- web_dashboard/vendor/mermaid.min.js +2029 -0
cfquant/client.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
import queue
|
|
4
|
+
import socket
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .config import get_config
|
|
9
|
+
from .protocol import decode_value, dumps_message, loads_message, new_id, pack_request
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CfquantError(RuntimeError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CfquantTimeout(TimeoutError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
PIPE_TRANSPORTS = ("pipe", "ctypes", "named_pipe", "named-pipe")
|
|
21
|
+
WEB_LTTX_TRANSPORTS = ("web", "web_lttx", "lttx_web", "web-lttx", "lttx-web")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_txl():
|
|
25
|
+
errors = []
|
|
26
|
+
try:
|
|
27
|
+
from .tx import txl
|
|
28
|
+
return txl
|
|
29
|
+
except Exception as e:
|
|
30
|
+
errors.append("cfquant.tx: %s" % e)
|
|
31
|
+
try:
|
|
32
|
+
from tx import txl
|
|
33
|
+
return txl
|
|
34
|
+
except Exception as e:
|
|
35
|
+
errors.append("tx.py: %s" % e)
|
|
36
|
+
try:
|
|
37
|
+
from LTtx.tx import txl
|
|
38
|
+
return txl
|
|
39
|
+
except Exception as e:
|
|
40
|
+
errors.append("LTtx.tx: %s" % e)
|
|
41
|
+
raise CfquantError("failed to import LTtx txl; tried %s" % "; ".join(errors))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _tcp_reachable(host, port, timeout=0.35):
|
|
45
|
+
try:
|
|
46
|
+
sock = socket.create_connection((host, int(port)), timeout=float(timeout))
|
|
47
|
+
sock.close()
|
|
48
|
+
return True
|
|
49
|
+
except Exception:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _normalize_registry(value):
|
|
54
|
+
if isinstance(value, str):
|
|
55
|
+
text = value.strip()
|
|
56
|
+
if not text:
|
|
57
|
+
return {}
|
|
58
|
+
try:
|
|
59
|
+
value = json.loads(text)
|
|
60
|
+
except Exception:
|
|
61
|
+
return {}
|
|
62
|
+
if not isinstance(value, dict):
|
|
63
|
+
return {}
|
|
64
|
+
if value.get("schema") != "cfquant.lttx.registry":
|
|
65
|
+
return {}
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def load_lttx_registry(cfg=None):
|
|
70
|
+
cfg = cfg or get_config()
|
|
71
|
+
host = cfg.get("host") or "127.0.0.1"
|
|
72
|
+
port = int(cfg.get("port") or 2049)
|
|
73
|
+
if not _tcp_reachable(host, port):
|
|
74
|
+
return {}
|
|
75
|
+
tx = None
|
|
76
|
+
try:
|
|
77
|
+
txl = _load_txl()
|
|
78
|
+
tx = txl(host, port, cfg.get("token") or "LTtx", show=False)
|
|
79
|
+
tx.start_tx()
|
|
80
|
+
value = tx.get(cfg.get("discovery_key") or "cfquant.runtime")
|
|
81
|
+
return _normalize_registry(value)
|
|
82
|
+
except Exception:
|
|
83
|
+
return {}
|
|
84
|
+
finally:
|
|
85
|
+
if tx is not None:
|
|
86
|
+
try:
|
|
87
|
+
tx.close()
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class LTtxRpcClient(object):
|
|
93
|
+
"""
|
|
94
|
+
cfquant 外部端 LTtx/TX RPC 客户端。
|
|
95
|
+
|
|
96
|
+
- 通过 start_tx() 向大 QMT 的固定请求频道发送 request。
|
|
97
|
+
- 通过 start_txg(client_id) 订阅自己的专属回包频道。
|
|
98
|
+
- 大 QMT 按请求里的 client_id 原路 push response/event。
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, host=None, port=None, token=None, request_channel=None, timeout=None, client_id=None):
|
|
102
|
+
cfg = get_config()
|
|
103
|
+
self.host = host or cfg["host"]
|
|
104
|
+
self.port = int(port or cfg["port"])
|
|
105
|
+
self.token = token or cfg["token"]
|
|
106
|
+
self.request_channel = request_channel or cfg["request_channel"]
|
|
107
|
+
self.timeout = float(timeout or cfg["timeout"])
|
|
108
|
+
self.client_id = client_id or cfg.get("client_id") or new_id("client")
|
|
109
|
+
self.reply_channel = self.client_id
|
|
110
|
+
self._tx = None
|
|
111
|
+
self._pending = {}
|
|
112
|
+
self._callbacks = {}
|
|
113
|
+
self._lock = threading.RLock()
|
|
114
|
+
self._pending_lock = threading.RLock()
|
|
115
|
+
self._started = False
|
|
116
|
+
self._recv_thread = None
|
|
117
|
+
|
|
118
|
+
def start(self):
|
|
119
|
+
with self._lock:
|
|
120
|
+
if self._started:
|
|
121
|
+
return
|
|
122
|
+
txl = self._load_txl()
|
|
123
|
+
self._tx = txl(self.host, self.port, self.token)
|
|
124
|
+
self._tx.start_tx()
|
|
125
|
+
self._tx.start_txg(self.client_id)
|
|
126
|
+
self._started = True
|
|
127
|
+
self._recv_thread = threading.Thread(target=self._recv_loop)
|
|
128
|
+
self._recv_thread.daemon = True
|
|
129
|
+
self._recv_thread.start()
|
|
130
|
+
|
|
131
|
+
def close(self):
|
|
132
|
+
with self._lock:
|
|
133
|
+
self._started = False
|
|
134
|
+
tx = self._tx
|
|
135
|
+
self._tx = None
|
|
136
|
+
if tx is not None:
|
|
137
|
+
try:
|
|
138
|
+
tx.Q.put(None)
|
|
139
|
+
except Exception:
|
|
140
|
+
pass
|
|
141
|
+
try:
|
|
142
|
+
tx.close()
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
with self._pending_lock:
|
|
146
|
+
for q in list(self._pending.values()):
|
|
147
|
+
try:
|
|
148
|
+
q.put_nowait({"ok": False, "error": {"message": "cfquant client closed"}})
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
self._pending.clear()
|
|
152
|
+
|
|
153
|
+
def request(self, action, params=None, timeout=None, request_channel=None):
|
|
154
|
+
self.start()
|
|
155
|
+
effective_timeout = float(timeout or self.timeout)
|
|
156
|
+
request_id = new_id("req")
|
|
157
|
+
q = queue.Queue(maxsize=1)
|
|
158
|
+
with self._pending_lock:
|
|
159
|
+
self._pending[request_id] = q
|
|
160
|
+
raw = pack_request(
|
|
161
|
+
action,
|
|
162
|
+
params=params or {},
|
|
163
|
+
reply_channel=self.reply_channel,
|
|
164
|
+
client_id=self.client_id,
|
|
165
|
+
request_id=request_id,
|
|
166
|
+
timeout=effective_timeout,
|
|
167
|
+
)
|
|
168
|
+
self._push("request", raw, request_channel or self.request_channel)
|
|
169
|
+
try:
|
|
170
|
+
msg = q.get(timeout=effective_timeout)
|
|
171
|
+
except queue.Empty:
|
|
172
|
+
with self._pending_lock:
|
|
173
|
+
self._pending.pop(request_id, None)
|
|
174
|
+
raise CfquantTimeout("cfquant request timeout: %s" % action)
|
|
175
|
+
if not msg.get("ok"):
|
|
176
|
+
err = msg.get("error") or {}
|
|
177
|
+
raise CfquantError(err.get("message") or str(err))
|
|
178
|
+
return decode_value(msg.get("result"))
|
|
179
|
+
|
|
180
|
+
def publish_event(self, channel, payload):
|
|
181
|
+
self.start()
|
|
182
|
+
self._push("event", dumps_message(payload), channel)
|
|
183
|
+
|
|
184
|
+
def add_callback(self, event, callback):
|
|
185
|
+
if callback is None:
|
|
186
|
+
return
|
|
187
|
+
self._callbacks.setdefault(event, []).append(callback)
|
|
188
|
+
|
|
189
|
+
def remove_callback(self, event, callback):
|
|
190
|
+
callbacks = self._callbacks.get(event) or []
|
|
191
|
+
if callback in callbacks:
|
|
192
|
+
callbacks.remove(callback)
|
|
193
|
+
|
|
194
|
+
def _recv_loop(self):
|
|
195
|
+
while self._started:
|
|
196
|
+
try:
|
|
197
|
+
tx = self._tx
|
|
198
|
+
if tx is None:
|
|
199
|
+
break
|
|
200
|
+
raw = tx.Q.get()
|
|
201
|
+
if raw is None:
|
|
202
|
+
break
|
|
203
|
+
msg = loads_message(raw)
|
|
204
|
+
if not msg:
|
|
205
|
+
continue
|
|
206
|
+
msg_type = msg.get("type")
|
|
207
|
+
if msg_type == "response":
|
|
208
|
+
with self._pending_lock:
|
|
209
|
+
q = self._pending.pop(msg.get("id"), None)
|
|
210
|
+
if q:
|
|
211
|
+
q.put(msg)
|
|
212
|
+
elif msg_type == "event":
|
|
213
|
+
self._dispatch_event(msg)
|
|
214
|
+
except Exception:
|
|
215
|
+
time.sleep(0.05)
|
|
216
|
+
if not self._started:
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
def _dispatch_event(self, msg):
|
|
220
|
+
event = msg.get("event")
|
|
221
|
+
data = decode_value(msg.get("data"))
|
|
222
|
+
full_msg = dict(msg)
|
|
223
|
+
full_msg["data"] = data
|
|
224
|
+
for callback in list(self._callbacks.get("__event__", [])):
|
|
225
|
+
try:
|
|
226
|
+
callback(full_msg)
|
|
227
|
+
except Exception:
|
|
228
|
+
pass
|
|
229
|
+
for callback in list(self._callbacks.get(event, [])):
|
|
230
|
+
try:
|
|
231
|
+
callback(data)
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
if event and event.startswith("quote:"):
|
|
235
|
+
quote_msg = dict(msg)
|
|
236
|
+
quote_msg["data"] = data
|
|
237
|
+
if quote_msg.get("subscription_id") is not None and quote_msg.get("subscribe_id") is None:
|
|
238
|
+
quote_msg["subscribe_id"] = quote_msg.get("subscription_id")
|
|
239
|
+
for callback in list(self._callbacks.get("quote", [])):
|
|
240
|
+
try:
|
|
241
|
+
callback(quote_msg)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
def _push(self, key, payload, channel):
|
|
246
|
+
tx = self._tx
|
|
247
|
+
if tx is None:
|
|
248
|
+
raise CfquantError("cfquant LTtx client not started")
|
|
249
|
+
result = tx.push(key, payload, channel)
|
|
250
|
+
if isinstance(result, dict) and result.get("code", 0) != 0:
|
|
251
|
+
raise CfquantError(result.get("msg") or "LTtx push failed")
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
def _load_txl(self):
|
|
255
|
+
return _load_txl()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class WebLttxRpcClient(LTtxRpcClient):
|
|
259
|
+
"""
|
|
260
|
+
外部 Python 默认路由客户端。
|
|
261
|
+
|
|
262
|
+
请求仍然通过 LTtx 发送,但目标频道是 Web 服务注册的统一路由频道。
|
|
263
|
+
Web 服务再根据账号配置选择 ctypes 通用桥或 LTtx 高级桥。
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(
|
|
267
|
+
self,
|
|
268
|
+
host=None,
|
|
269
|
+
port=None,
|
|
270
|
+
token=None,
|
|
271
|
+
request_channel=None,
|
|
272
|
+
timeout=None,
|
|
273
|
+
client_id=None,
|
|
274
|
+
registry=None,
|
|
275
|
+
):
|
|
276
|
+
cfg = get_config()
|
|
277
|
+
self.registry = registry or load_lttx_registry(cfg)
|
|
278
|
+
channel = (
|
|
279
|
+
request_channel
|
|
280
|
+
or self.registry.get("web_request_channel")
|
|
281
|
+
or cfg.get("web_request_channel")
|
|
282
|
+
or "cfquant.web.request"
|
|
283
|
+
)
|
|
284
|
+
super(WebLttxRpcClient, self).__init__(
|
|
285
|
+
host=host,
|
|
286
|
+
port=port,
|
|
287
|
+
token=token,
|
|
288
|
+
request_channel=channel,
|
|
289
|
+
timeout=timeout,
|
|
290
|
+
client_id=client_id,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _registry_has_web_route(registry):
|
|
295
|
+
if not isinstance(registry, dict):
|
|
296
|
+
return False
|
|
297
|
+
web_route = registry.get("web_route") if isinstance(registry.get("web_route"), dict) else {}
|
|
298
|
+
return bool(registry.get("web_request_channel") and web_route.get("enabled", True))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def create_rpc_client(request_channel=None, timeout=None, client_id=None, transport=None, bridge_id=None):
|
|
302
|
+
cfg = get_config()
|
|
303
|
+
mode = str(transport or cfg.get("transport") or "auto").lower()
|
|
304
|
+
registry = {}
|
|
305
|
+
if mode == "auto":
|
|
306
|
+
registry = load_lttx_registry(cfg)
|
|
307
|
+
if _registry_has_web_route(registry):
|
|
308
|
+
return WebLttxRpcClient(
|
|
309
|
+
timeout=timeout or cfg.get("timeout"),
|
|
310
|
+
client_id=client_id,
|
|
311
|
+
registry=registry,
|
|
312
|
+
)
|
|
313
|
+
mode = str(registry.get("direct_fallback_transport") or "ctypes").lower()
|
|
314
|
+
if mode in WEB_LTTX_TRANSPORTS:
|
|
315
|
+
return WebLttxRpcClient(
|
|
316
|
+
timeout=timeout or cfg.get("timeout"),
|
|
317
|
+
client_id=client_id,
|
|
318
|
+
registry=registry or None,
|
|
319
|
+
)
|
|
320
|
+
if mode in PIPE_TRANSPORTS:
|
|
321
|
+
from .pipe_client import PipeRpcClient
|
|
322
|
+
|
|
323
|
+
return PipeRpcClient(
|
|
324
|
+
pipe_name=cfg.get("pipe_name"),
|
|
325
|
+
request_channel=request_channel or cfg.get("request_channel"),
|
|
326
|
+
timeout=timeout or cfg.get("timeout"),
|
|
327
|
+
client_id=client_id,
|
|
328
|
+
connect_timeout_ms=cfg.get("pipe_connect_timeout_ms"),
|
|
329
|
+
)
|
|
330
|
+
return LTtxRpcClient(
|
|
331
|
+
host=cfg.get("host"),
|
|
332
|
+
port=cfg.get("port"),
|
|
333
|
+
token=cfg.get("token"),
|
|
334
|
+
request_channel=request_channel or cfg.get("request_channel"),
|
|
335
|
+
timeout=timeout or cfg.get("timeout"),
|
|
336
|
+
client_id=client_id,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
_default_client = None
|
|
341
|
+
_client_lock = threading.Lock()
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def get_client():
|
|
345
|
+
global _default_client
|
|
346
|
+
with _client_lock:
|
|
347
|
+
if _default_client is None:
|
|
348
|
+
cfg = get_config()
|
|
349
|
+
_default_client = create_rpc_client(
|
|
350
|
+
request_channel=cfg.get("request_channel"),
|
|
351
|
+
timeout=cfg.get("timeout"),
|
|
352
|
+
client_id=cfg.get("client_id"),
|
|
353
|
+
)
|
|
354
|
+
return _default_client
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def configure(**kwargs):
|
|
358
|
+
from .config import configure as configure_config
|
|
359
|
+
|
|
360
|
+
configure_config(**kwargs)
|
|
361
|
+
global _default_client
|
|
362
|
+
with _client_lock:
|
|
363
|
+
if _default_client is not None:
|
|
364
|
+
_default_client.close()
|
|
365
|
+
_default_client = None
|
cfquant/config.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from .channels import bridge_id_from_env, channels_for_bridge
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DEFAULT_HOST = os.environ.get("CFQUANT_LTTX_HOST", os.environ.get("CFQUANT_SOCKET_HOST", "127.0.0.1"))
|
|
8
|
+
DEFAULT_PORT = int(os.environ.get("CFQUANT_LTTX_PORT", os.environ.get("CFQUANT_SOCKET_PORT", "2049")))
|
|
9
|
+
DEFAULT_TOKEN = os.environ.get("CFQUANT_LTTX_TOKEN", os.environ.get("CFQUANT_SOCKET_TOKEN", "LTtx"))
|
|
10
|
+
DEFAULT_TRANSPORT = os.environ.get("CFQUANT_TRANSPORT", "auto")
|
|
11
|
+
DEFAULT_PIPE_NAME = os.environ.get("CFQUANT_PIPE_NAME", r"\\.\pipe\cfquant_pipe_hub")
|
|
12
|
+
DEFAULT_PIPE_CONNECT_TIMEOUT_MS = int(os.environ.get("CFQUANT_PIPE_CONNECT_TIMEOUT_MS", "3000"))
|
|
13
|
+
DEFAULT_DISCOVERY_KEY = os.environ.get("CFQUANT_DISCOVERY_KEY", "cfquant.runtime")
|
|
14
|
+
DEFAULT_WEB_REQUEST_CHANNEL = os.environ.get("CFQUANT_WEB_REQUEST_CHANNEL", "cfquant.web.request")
|
|
15
|
+
DEFAULT_BRIDGE_ID = bridge_id_from_env()
|
|
16
|
+
DEFAULT_REQUEST_CHANNEL = os.environ.get(
|
|
17
|
+
"CFQUANT_REQUEST_CHANNEL",
|
|
18
|
+
channels_for_bridge(DEFAULT_BRIDGE_ID)["normal"],
|
|
19
|
+
)
|
|
20
|
+
DEFAULT_TIMEOUT = float(os.environ.get("CFQUANT_TIMEOUT", "15"))
|
|
21
|
+
DEFAULT_CLIENT_ID = os.environ.get("CFQUANT_CLIENT_ID")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_config = {
|
|
25
|
+
"host": DEFAULT_HOST,
|
|
26
|
+
"port": DEFAULT_PORT,
|
|
27
|
+
"token": DEFAULT_TOKEN,
|
|
28
|
+
"bridge_id": DEFAULT_BRIDGE_ID,
|
|
29
|
+
"request_channel": DEFAULT_REQUEST_CHANNEL,
|
|
30
|
+
"timeout": DEFAULT_TIMEOUT,
|
|
31
|
+
"client_id": DEFAULT_CLIENT_ID,
|
|
32
|
+
"transport": DEFAULT_TRANSPORT,
|
|
33
|
+
"pipe_name": DEFAULT_PIPE_NAME,
|
|
34
|
+
"pipe_connect_timeout_ms": DEFAULT_PIPE_CONNECT_TIMEOUT_MS,
|
|
35
|
+
"discovery_key": DEFAULT_DISCOVERY_KEY,
|
|
36
|
+
"web_request_channel": DEFAULT_WEB_REQUEST_CHANNEL,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def configure(
|
|
41
|
+
host=None,
|
|
42
|
+
port=None,
|
|
43
|
+
token=None,
|
|
44
|
+
request_channel=None,
|
|
45
|
+
timeout=None,
|
|
46
|
+
client_id=None,
|
|
47
|
+
bridge_id=None,
|
|
48
|
+
transport=None,
|
|
49
|
+
pipe_name=None,
|
|
50
|
+
pipe_connect_timeout_ms=None,
|
|
51
|
+
discovery_key=None,
|
|
52
|
+
web_request_channel=None,
|
|
53
|
+
):
|
|
54
|
+
"""配置 cfquant 连接到 LTtx/TX 桥接端的参数。"""
|
|
55
|
+
if host is not None:
|
|
56
|
+
_config["host"] = host
|
|
57
|
+
if port is not None:
|
|
58
|
+
_config["port"] = int(port)
|
|
59
|
+
if token is not None:
|
|
60
|
+
_config["token"] = token
|
|
61
|
+
if bridge_id is not None:
|
|
62
|
+
from .channels import normalize_bridge_id
|
|
63
|
+
|
|
64
|
+
_config["bridge_id"] = normalize_bridge_id(bridge_id)
|
|
65
|
+
if request_channel is None:
|
|
66
|
+
_config["request_channel"] = channels_for_bridge(_config["bridge_id"])["normal"]
|
|
67
|
+
if request_channel is not None:
|
|
68
|
+
_config["request_channel"] = request_channel
|
|
69
|
+
if timeout is not None:
|
|
70
|
+
_config["timeout"] = float(timeout)
|
|
71
|
+
if client_id is not None:
|
|
72
|
+
_config["client_id"] = client_id
|
|
73
|
+
if transport is not None:
|
|
74
|
+
_config["transport"] = str(transport or "ctypes").lower()
|
|
75
|
+
if pipe_name is not None:
|
|
76
|
+
_config["pipe_name"] = pipe_name
|
|
77
|
+
if pipe_connect_timeout_ms is not None:
|
|
78
|
+
_config["pipe_connect_timeout_ms"] = int(pipe_connect_timeout_ms)
|
|
79
|
+
if discovery_key is not None:
|
|
80
|
+
_config["discovery_key"] = str(discovery_key or DEFAULT_DISCOVERY_KEY)
|
|
81
|
+
if web_request_channel is not None:
|
|
82
|
+
_config["web_request_channel"] = str(web_request_channel or DEFAULT_WEB_REQUEST_CHANNEL)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_config():
|
|
86
|
+
return dict(_config)
|