libfinance 0.1.3__tar.gz → 0.1.4__tar.gz
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.
- {libfinance-0.1.3 → libfinance-0.1.4}/PKG-INFO +2 -1
- {libfinance-0.1.3 → libfinance-0.1.4}/README.md +1 -0
- libfinance-0.1.4/VERSION +1 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/shares.py +37 -1
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/client.py +29 -7
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance.egg-info/PKG-INFO +2 -1
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_client_api_contract.py +28 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_client_reconnect.py +74 -0
- libfinance-0.1.3/VERSION +0 -1
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/__init__.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/__init__.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/calendar.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/corporate_actions.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/exfactor.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/factors.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/financials.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/get_price.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/index_components.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/industry.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/instrument.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/live_quote.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/api/theme.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/subscribe/__init__.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/subscribe/md_protocol.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/subscribe/quote_api.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/__init__.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/cache.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/datetime_func.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/decorators.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/utils.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance/utils/validators.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance.egg-info/SOURCES.txt +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance.egg-info/dependency_links.txt +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance.egg-info/requires.txt +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/libfinance.egg-info/top_level.txt +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/pyproject.toml +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/setup.cfg +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/setup.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_contract.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_docs_contract.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_rpc_contract.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_subscribe_live.py +0 -0
- {libfinance-0.1.3 → libfinance-0.1.4}/test/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: libfinance
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: libfinance is a modern finance API
|
|
5
5
|
Description-Content-Type: text/markdown
|
|
6
6
|
Requires-Dist: lz4
|
|
@@ -176,6 +176,7 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
|
|
|
176
176
|
| 日频行情 | CN · US | `get_price` | 开高低收、成交量额、涨跌停价,可复权 |
|
|
177
177
|
| 复权因子 | CN · US | `get_ex_factor` | 逐除权日的单次及累计因子 |
|
|
178
178
|
| 股本 | CN · US | `get_shares` | 逐交易日的总股本与流通股本 |
|
|
179
|
+
| 换手率 | CN、US | `get_turnover_rate` | 逐交易日的换手率(未复权成交量 ÷ A 股自由流通股本或美股已发行股数) |
|
|
179
180
|
| 分红 / 拆股 / 配股 | CN · US | `get_dividends`、`get_splits`、`get_allotments` | 除权事件;配股只有 A 股 |
|
|
180
181
|
| 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
|
|
181
182
|
| 财务报表(PIT) | CN · US | `get_pit_financials_ex` | 按季度取,带修订历史 |
|
|
@@ -163,6 +163,7 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
|
|
|
163
163
|
| 日频行情 | CN · US | `get_price` | 开高低收、成交量额、涨跌停价,可复权 |
|
|
164
164
|
| 复权因子 | CN · US | `get_ex_factor` | 逐除权日的单次及累计因子 |
|
|
165
165
|
| 股本 | CN · US | `get_shares` | 逐交易日的总股本与流通股本 |
|
|
166
|
+
| 换手率 | CN、US | `get_turnover_rate` | 逐交易日的换手率(未复权成交量 ÷ A 股自由流通股本或美股已发行股数) |
|
|
166
167
|
| 分红 / 拆股 / 配股 | CN · US | `get_dividends`、`get_splits`、`get_allotments` | 除权事件;配股只有 A 股 |
|
|
167
168
|
| 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
|
|
168
169
|
| 财务报表(PIT) | CN · US | `get_pit_financials_ex` | 按季度取,带修订历史 |
|
libfinance-0.1.4/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""股本:统一字段,CN 与 US 同一套(后端 shares 数据族的 get_shares_panel
|
|
3
|
+
"""股本:统一字段,CN 与 US 同一套(后端 shares 数据族的 get_shares_panel);以及由它算出的日频换手率。"""
|
|
4
4
|
from typing import List, Optional, Union
|
|
5
5
|
|
|
6
6
|
import pandas as pd
|
|
@@ -71,6 +71,42 @@ def get_shares(
|
|
|
71
71
|
return _indexed(frame)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
@export_as_api
|
|
75
|
+
def get_turnover_rate(
|
|
76
|
+
order_book_ids: Union[str, List[str]],
|
|
77
|
+
start_date=None,
|
|
78
|
+
end_date=None,
|
|
79
|
+
) -> pd.DataFrame:
|
|
80
|
+
r"""获取日频换手率。
|
|
81
|
+
|
|
82
|
+
A 股换手率 = 当日未复权成交量 ÷ 当日生效的自由流通股本(没有时用流通股本);
|
|
83
|
+
美股换手率 = 当日未复权成交量 ÷ 当日已发行股数。结果是\ **小数**\ (0.01 即 1%)。
|
|
84
|
+
A 股结果与 factors-daybar 的因子(如 ``system/broker-daybar/TURN20``\ )读取的换手率同一口径。
|
|
85
|
+
|
|
86
|
+
:param order_book_ids: 单个代码或代码列表,如 ``"600000.XSHG"``、``"AAPL.US"``
|
|
87
|
+
:param start_date: 开始日期;只给一个日期时取那一天
|
|
88
|
+
:param end_date: 结束日期;两个都省略时各市场分别取最近两周内最后一个有行情的交易日
|
|
89
|
+
|
|
90
|
+
:returns: pandas.DataFrame,以 (order_book_id, date) 为索引,一列 ``turnover_rate``\ 。停牌日没有行;
|
|
91
|
+
没有股本数据的证券或日子为 NaN。
|
|
92
|
+
"""
|
|
93
|
+
order_book_ids = ensure_list_of_string(order_book_ids, "order_book_ids")
|
|
94
|
+
if not order_book_ids:
|
|
95
|
+
raise ValueError("order_book_ids: at least one order book id expected")
|
|
96
|
+
if start_date is not None:
|
|
97
|
+
start_date = to_date_str(start_date)
|
|
98
|
+
if end_date is not None:
|
|
99
|
+
end_date = to_date_str(end_date)
|
|
100
|
+
if start_date is not None and end_date is not None and start_date > end_date:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"invalid date range: [{!r}, {!r}]".format(start_date, end_date)
|
|
103
|
+
)
|
|
104
|
+
frame = get_client().get_turnover_rate(
|
|
105
|
+
order_book_ids=order_book_ids, start_date=start_date, end_date=end_date,
|
|
106
|
+
)
|
|
107
|
+
return _indexed(frame)
|
|
108
|
+
|
|
109
|
+
|
|
74
110
|
def _indexed(frame):
|
|
75
111
|
"""服务端的扁平表(order_book_id, date, 字段...)-> (order_book_id, date) 索引;已带索引的原样返回。"""
|
|
76
112
|
if not isinstance(frame, pd.DataFrame) or not {"order_book_id", "date"} <= set(frame.columns):
|
|
@@ -3,6 +3,7 @@ import socket
|
|
|
3
3
|
import struct
|
|
4
4
|
import threading
|
|
5
5
|
import sys
|
|
6
|
+
from collections import deque
|
|
6
7
|
from concurrent.futures import Future
|
|
7
8
|
import time
|
|
8
9
|
|
|
@@ -142,6 +143,7 @@ class RpcClient:
|
|
|
142
143
|
"""
|
|
143
144
|
request_timeout = 300
|
|
144
145
|
request_attempt_count = 3
|
|
146
|
+
_expired_request_limit = 1024
|
|
145
147
|
|
|
146
148
|
def __init__(self, host='127.0.0.1', port=8080):
|
|
147
149
|
self.host = host
|
|
@@ -153,6 +155,11 @@ class RpcClient:
|
|
|
153
155
|
self._id_lock = threading.Lock()
|
|
154
156
|
|
|
155
157
|
self._pending = {}
|
|
158
|
+
# A timed-out request keeps running on the server: the wire protocol has no cancellation.
|
|
159
|
+
# Remember a bounded number of those ids so their eventual answers are recognized as late,
|
|
160
|
+
# rather than reported as a protocol error ("unknown request_id").
|
|
161
|
+
self._expired_requests = set()
|
|
162
|
+
self._expired_request_order = deque()
|
|
156
163
|
self._pending_lock = threading.Lock()
|
|
157
164
|
|
|
158
165
|
self._reader_thread = None
|
|
@@ -191,6 +198,8 @@ class RpcClient:
|
|
|
191
198
|
for fut in self._pending.values():
|
|
192
199
|
fut.set_exception(ConnectionError("Connection closed"))
|
|
193
200
|
self._pending.clear()
|
|
201
|
+
self._expired_requests.clear()
|
|
202
|
+
self._expired_request_order.clear()
|
|
194
203
|
|
|
195
204
|
if self._reader_thread and self._reader_thread.is_alive():
|
|
196
205
|
self._reader_thread.join(timeout=2)
|
|
@@ -209,10 +218,12 @@ class RpcClient:
|
|
|
209
218
|
raise ConnectionError(
|
|
210
219
|
"Not connected to server {}:{}: {}".format(self.host, self.port, error)) from error
|
|
211
220
|
|
|
212
|
-
def call_raw(self, function_name, args=None, timeout=
|
|
221
|
+
def call_raw(self, function_name, args=None, timeout=None):
|
|
213
222
|
"""Send an RPC request and wait synchronously for the raw envelope."""
|
|
214
223
|
if args is None:
|
|
215
224
|
args = {}
|
|
225
|
+
if timeout is None:
|
|
226
|
+
timeout = self.request_timeout
|
|
216
227
|
self._ensure_connected()
|
|
217
228
|
|
|
218
229
|
req_id = self._alloc_id()
|
|
@@ -233,12 +244,18 @@ class RpcClient:
|
|
|
233
244
|
return future.result(timeout=timeout)
|
|
234
245
|
except TimeoutError:
|
|
235
246
|
with self._pending_lock:
|
|
236
|
-
|
|
247
|
+
# If the reader won the race and already consumed the answer, there will be no
|
|
248
|
+
# late frame to classify. Only remember ids actually removed here.
|
|
249
|
+
if self._pending.pop(req_id, None) is not None:
|
|
250
|
+
self._expired_requests.add(req_id)
|
|
251
|
+
self._expired_request_order.append(req_id)
|
|
252
|
+
while len(self._expired_request_order) > self._expired_request_limit:
|
|
253
|
+
self._expired_requests.discard(self._expired_request_order.popleft())
|
|
237
254
|
raise TimeoutError(
|
|
238
255
|
f"RPC '{function_name}' timeout after {timeout}s (req_id={req_id})"
|
|
239
256
|
)
|
|
240
257
|
|
|
241
|
-
def call(self, function_name, args=None, timeout=
|
|
258
|
+
def call(self, function_name, args=None, timeout=None):
|
|
242
259
|
"""Send an RPC request, auto-unwrap envelope, return result or raise RpcError."""
|
|
243
260
|
resp = self.call_raw(function_name, args, timeout)
|
|
244
261
|
return self._unwrap_envelope(resp)
|
|
@@ -388,10 +405,13 @@ class RpcClient:
|
|
|
388
405
|
|
|
389
406
|
with self._pending_lock:
|
|
390
407
|
future = self._pending.pop(request_id, None)
|
|
408
|
+
expired = request_id in self._expired_requests
|
|
409
|
+
if expired:
|
|
410
|
+
self._expired_requests.remove(request_id)
|
|
391
411
|
|
|
392
412
|
if future:
|
|
393
413
|
future.set_result(response_data)
|
|
394
|
-
|
|
414
|
+
elif not expired:
|
|
395
415
|
print(
|
|
396
416
|
f"Received response for unknown request_id={request_id}",
|
|
397
417
|
file=sys.stderr,
|
|
@@ -420,12 +440,14 @@ class RpcClient:
|
|
|
420
440
|
try:
|
|
421
441
|
response = self.call(api_name, kwargs)
|
|
422
442
|
break
|
|
443
|
+
except TimeoutError:
|
|
444
|
+
# The server keeps executing a timed-out request, so retrying it immediately only
|
|
445
|
+
# duplicates expensive work. Connection failures still use the retry path below.
|
|
446
|
+
raise
|
|
423
447
|
except Exception as ex:
|
|
424
448
|
err = ex
|
|
425
449
|
if attempt_index < self.request_attempt_count - 1:
|
|
426
450
|
time.sleep(0.6)
|
|
427
|
-
except ResponseError as ex:
|
|
428
|
-
err = ex
|
|
429
451
|
|
|
430
452
|
if response is None and isinstance(err, Exception):
|
|
431
453
|
if "TSocket read 0 bytes" in str(err):
|
|
@@ -472,4 +494,4 @@ def get_client():
|
|
|
472
494
|
f"Client auto-connect to {_DEFAULT_HOST}:{_DEFAULT_PORT} failed: {e}. "
|
|
473
495
|
"Call init_client(host=..., port=...) explicitly before using libfinance.api.*"
|
|
474
496
|
) from e
|
|
475
|
-
return _CLIENT
|
|
497
|
+
return _CLIENT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: libfinance
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: libfinance is a modern finance API
|
|
5
5
|
Description-Content-Type: text/markdown
|
|
6
6
|
Requires-Dist: lz4
|
|
@@ -176,6 +176,7 @@ get_trading_dates("2024-01-01", "2024-01-31", market="us") # 不涉及具体
|
|
|
176
176
|
| 日频行情 | CN · US | `get_price` | 开高低收、成交量额、涨跌停价,可复权 |
|
|
177
177
|
| 复权因子 | CN · US | `get_ex_factor` | 逐除权日的单次及累计因子 |
|
|
178
178
|
| 股本 | CN · US | `get_shares` | 逐交易日的总股本与流通股本 |
|
|
179
|
+
| 换手率 | CN、US | `get_turnover_rate` | 逐交易日的换手率(未复权成交量 ÷ A 股自由流通股本或美股已发行股数) |
|
|
179
180
|
| 分红 / 拆股 / 配股 | CN · US | `get_dividends`、`get_splits`、`get_allotments` | 除权事件;配股只有 A 股 |
|
|
180
181
|
| 分拆 | US | `get_spinoffs` | A 股不产生这类事件 |
|
|
181
182
|
| 财务报表(PIT) | CN · US | `get_pit_financials_ex` | 按季度取,带修订历史 |
|
|
@@ -344,6 +344,34 @@ def test_get_shares_takes_the_unified_fields_and_as_of(monkeypatch):
|
|
|
344
344
|
shares.get_shares("600000.XSHG", fields=["total"])
|
|
345
345
|
|
|
346
346
|
|
|
347
|
+
def test_get_turnover_rate_is_indexed_and_checks_its_dates(monkeypatch):
|
|
348
|
+
"""financialmetrics answers a flat table; the client indexes it by (order_book_id, date) and sends dates as ISO."""
|
|
349
|
+
from datetime import date
|
|
350
|
+
import pandas as pd
|
|
351
|
+
from libfinance.api import shares
|
|
352
|
+
|
|
353
|
+
calls = []
|
|
354
|
+
|
|
355
|
+
class Client:
|
|
356
|
+
def get_turnover_rate(self, **kwargs):
|
|
357
|
+
calls.append(kwargs)
|
|
358
|
+
return pd.DataFrame([{"order_book_id": "600000.XSHG", "date": "2025-06-03", "turnover_rate": 0.0012},
|
|
359
|
+
{"order_book_id": "000001.XSHE", "date": "2025-06-03", "turnover_rate": None}])
|
|
360
|
+
|
|
361
|
+
monkeypatch.setattr(shares, "get_client", lambda: Client())
|
|
362
|
+
out = shares.get_turnover_rate(["600000.XSHG", "000001.XSHE"], date(2025, 6, 1), "2025-06-30")
|
|
363
|
+
assert calls == [{"order_book_ids": ["600000.XSHG", "000001.XSHE"], "start_date": "2025-06-01",
|
|
364
|
+
"end_date": "2025-06-30"}]
|
|
365
|
+
assert list(out.index.names) == ["order_book_id", "date"] and list(out.columns) == ["turnover_rate"]
|
|
366
|
+
assert out.loc[("600000.XSHG", pd.Timestamp("2025-06-03")), "turnover_rate"] == 0.0012
|
|
367
|
+
shares.get_turnover_rate("600000.XSHG") # no dates: the server picks the latest trading day
|
|
368
|
+
assert calls[-1] == {"order_book_ids": ["600000.XSHG"], "start_date": None, "end_date": None}
|
|
369
|
+
with pytest.raises(ValueError):
|
|
370
|
+
shares.get_turnover_rate("600000.XSHG", "2025-06-30", "2025-06-01")
|
|
371
|
+
with pytest.raises(ValueError):
|
|
372
|
+
shares.get_turnover_rate([])
|
|
373
|
+
|
|
374
|
+
|
|
347
375
|
def test_as_of_keeps_a_timestamp(monkeypatch):
|
|
348
376
|
import datetime
|
|
349
377
|
from libfinance.utils.utils import as_of_text
|
|
@@ -8,9 +8,11 @@ connection is cut. Seen 2026-10-04 against deploy-libfinanceserver: every call a
|
|
|
8
8
|
import socket
|
|
9
9
|
import struct
|
|
10
10
|
import threading
|
|
11
|
+
import time
|
|
11
12
|
|
|
12
13
|
import msgpack
|
|
13
14
|
|
|
15
|
+
import libfinance.client as client_module
|
|
14
16
|
from libfinance.client import RpcClient
|
|
15
17
|
|
|
16
18
|
|
|
@@ -61,6 +63,35 @@ class ClosingServer:
|
|
|
61
63
|
self.listener.close()
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
class DelayedServer(ClosingServer):
|
|
67
|
+
"""Answers every call after a delay and records how many requests reached the server."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, delay):
|
|
70
|
+
self.delay = delay
|
|
71
|
+
self.requests = 0
|
|
72
|
+
super().__init__(answers_per_connection=0)
|
|
73
|
+
|
|
74
|
+
def _serve(self):
|
|
75
|
+
try:
|
|
76
|
+
sock, _ = self.listener.accept()
|
|
77
|
+
except OSError:
|
|
78
|
+
return
|
|
79
|
+
self.connections += 1
|
|
80
|
+
try:
|
|
81
|
+
while True:
|
|
82
|
+
(length,) = struct.unpack("!I", self._recv(sock, 4))
|
|
83
|
+
meta_and_payload = self._recv(sock, length)
|
|
84
|
+
request_id = struct.unpack("!I", meta_and_payload[1:5])[0]
|
|
85
|
+
self.requests += 1
|
|
86
|
+
time.sleep(self.delay)
|
|
87
|
+
body = msgpack.packb({"status": "ok", "code": 0, "result": {"message": "pong"}})
|
|
88
|
+
sock.sendall(struct.pack("!I", 6 + len(body)) + bytes([0x02]) + struct.pack("!I", request_id)
|
|
89
|
+
+ bytes([0x00]) + body)
|
|
90
|
+
except (ConnectionError, OSError):
|
|
91
|
+
pass
|
|
92
|
+
sock.close()
|
|
93
|
+
|
|
94
|
+
|
|
64
95
|
def test_a_call_after_the_server_closed_the_connection_reconnects():
|
|
65
96
|
server = ClosingServer(answers_per_connection=1)
|
|
66
97
|
client = RpcClient("127.0.0.1", server.port)
|
|
@@ -100,3 +131,46 @@ def test_an_unreachable_server_is_a_connection_error_not_a_hang():
|
|
|
100
131
|
assert "Not connected to server" in str(error)
|
|
101
132
|
else:
|
|
102
133
|
raise AssertionError("expected ConnectionError")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_default_timeout_uses_the_configured_request_timeout(monkeypatch):
|
|
137
|
+
observed = []
|
|
138
|
+
|
|
139
|
+
class ConnectedSocket:
|
|
140
|
+
def sendall(self, _frame):
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def result(_future, timeout):
|
|
144
|
+
observed.append(timeout)
|
|
145
|
+
return {"status": "ok"}
|
|
146
|
+
|
|
147
|
+
monkeypatch.setattr(client_module.Future, "result", result)
|
|
148
|
+
client = RpcClient()
|
|
149
|
+
client.running = True
|
|
150
|
+
client.sock = ConnectedSocket()
|
|
151
|
+
client.request_timeout = 17.5
|
|
152
|
+
assert client.call_raw("ping") == {"status": "ok"}
|
|
153
|
+
assert observed == [17.5]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_api_timeout_is_not_retried_and_late_answer_is_not_unknown(capsys):
|
|
157
|
+
server = DelayedServer(delay=0.08)
|
|
158
|
+
client = RpcClient("127.0.0.1", server.port)
|
|
159
|
+
client.request_timeout = 0.02
|
|
160
|
+
client.connect()
|
|
161
|
+
try:
|
|
162
|
+
client.ping()
|
|
163
|
+
except TimeoutError as error:
|
|
164
|
+
assert "req_id=1" in str(error)
|
|
165
|
+
else:
|
|
166
|
+
raise AssertionError("expected TimeoutError")
|
|
167
|
+
|
|
168
|
+
# Let the reader consume the answer that the server completed after the caller timed out.
|
|
169
|
+
for _ in range(100):
|
|
170
|
+
if not client._expired_requests:
|
|
171
|
+
break
|
|
172
|
+
threading.Event().wait(0.01)
|
|
173
|
+
assert server.requests == 1
|
|
174
|
+
assert "unknown request_id" not in capsys.readouterr().err
|
|
175
|
+
client.close()
|
|
176
|
+
server.close()
|
libfinance-0.1.3/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.1.3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|