rquote 0.4.1__py3-none-any.whl → 0.4.3__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.
- rquote/cache/persistent.py +58 -57
- rquote/utils/logging.py +27 -7
- {rquote-0.4.1.dist-info → rquote-0.4.3.dist-info}/METADATA +46 -2
- {rquote-0.4.1.dist-info → rquote-0.4.3.dist-info}/RECORD +6 -6
- {rquote-0.4.1.dist-info → rquote-0.4.3.dist-info}/WHEEL +0 -0
- {rquote-0.4.1.dist-info → rquote-0.4.3.dist-info}/top_level.txt +0 -0
rquote/cache/persistent.py
CHANGED
|
@@ -232,7 +232,7 @@ class PersistentCache(Cache):
|
|
|
232
232
|
freq: str, fq: str) -> Optional[Tuple[str, str, pd.DataFrame]]:
|
|
233
233
|
"""从 duckdb 获取数据"""
|
|
234
234
|
result = self.conn.execute("""
|
|
235
|
-
SELECT name, data,
|
|
235
|
+
SELECT name, data, expire_at
|
|
236
236
|
FROM cache_data
|
|
237
237
|
WHERE cache_key = ?
|
|
238
238
|
""", [base_key]).fetchone()
|
|
@@ -240,7 +240,7 @@ class PersistentCache(Cache):
|
|
|
240
240
|
if not result:
|
|
241
241
|
return None
|
|
242
242
|
|
|
243
|
-
name, data_blob,
|
|
243
|
+
name, data_blob, expire_at = result
|
|
244
244
|
|
|
245
245
|
# 检查过期
|
|
246
246
|
if self.ttl and expire_at:
|
|
@@ -253,44 +253,44 @@ class PersistentCache(Cache):
|
|
|
253
253
|
import pickle
|
|
254
254
|
df = pickle.loads(data_blob)
|
|
255
255
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
# 确保索引是 DatetimeIndex
|
|
257
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
258
|
+
try:
|
|
259
|
+
df.index = pd.to_datetime(df.index)
|
|
260
|
+
except (ValueError, TypeError):
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
if df.empty:
|
|
264
|
+
return None
|
|
259
265
|
|
|
260
|
-
#
|
|
266
|
+
# 直接从 DataFrame 索引获取实际的日期范围
|
|
267
|
+
cached_earliest = df.index.min()
|
|
268
|
+
cached_latest = df.index.max()
|
|
269
|
+
|
|
270
|
+
# 解析请求的日期范围
|
|
261
271
|
request_sdate = self._parse_date(sdate) if sdate else None
|
|
262
272
|
request_edate = self._parse_date(edate) if edate else None
|
|
263
273
|
|
|
264
|
-
#
|
|
265
|
-
|
|
274
|
+
# 检查是否有重叠:如果请求的日期范围与缓存数据有重叠,就返回过滤后的数据
|
|
275
|
+
# 注意:即使缓存中有部分数据,也应该返回(让上层决定是否需要扩展)
|
|
276
|
+
has_overlap = True
|
|
277
|
+
if request_edate and request_edate < cached_earliest:
|
|
266
278
|
# 请求的结束日期早于缓存的最早日期,无重叠
|
|
267
|
-
|
|
268
|
-
if request_sdate and
|
|
279
|
+
has_overlap = False
|
|
280
|
+
if request_sdate and request_sdate > cached_latest:
|
|
269
281
|
# 请求的开始日期晚于缓存的最晚日期,无重叠
|
|
270
|
-
|
|
282
|
+
has_overlap = False
|
|
271
283
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
actual_sdate = max(request_sdate, cached_earliest) if request_sdate and cached_earliest else (request_sdate or cached_earliest)
|
|
275
|
-
actual_edate = min(request_edate, cached_latest) if request_edate and cached_latest else (request_edate or cached_latest)
|
|
284
|
+
if not has_overlap:
|
|
285
|
+
return None
|
|
276
286
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
actual_sdate.strftime('%Y-%m-%d') if actual_sdate else None,
|
|
281
|
-
actual_edate.strftime('%Y-%m-%d') if actual_edate else None
|
|
282
|
-
)
|
|
287
|
+
# 按照请求的日期范围过滤数据(即使缓存中有更多数据,也只返回请求范围内的)
|
|
288
|
+
# 重要:必须按照 edate 截取,和从网络获取的行为一致
|
|
289
|
+
filtered_df = self._filter_dataframe_by_date(df, sdate, edate)
|
|
283
290
|
|
|
284
291
|
if filtered_df.empty:
|
|
285
292
|
return None
|
|
286
293
|
|
|
287
|
-
# 确保索引是 DatetimeIndex
|
|
288
|
-
if not isinstance(filtered_df.index, pd.DatetimeIndex):
|
|
289
|
-
try:
|
|
290
|
-
filtered_df.index = pd.to_datetime(filtered_df.index)
|
|
291
|
-
except (ValueError, TypeError):
|
|
292
|
-
pass # 如果转换失败,保持原样
|
|
293
|
-
|
|
294
294
|
return (symbol, name, filtered_df)
|
|
295
295
|
|
|
296
296
|
def _get_pickle(self, base_key: str, symbol: str, sdate: str, edate: str,
|
|
@@ -311,47 +311,45 @@ class PersistentCache(Cache):
|
|
|
311
311
|
|
|
312
312
|
df = cache_entry['data']
|
|
313
313
|
name = cache_entry.get('name', '')
|
|
314
|
-
earliest_date = cache_entry.get('earliest_date')
|
|
315
|
-
latest_date = cache_entry.get('latest_date')
|
|
316
314
|
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
|
|
315
|
+
# 确保索引是 DatetimeIndex
|
|
316
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
317
|
+
try:
|
|
318
|
+
df.index = pd.to_datetime(df.index)
|
|
319
|
+
except (ValueError, TypeError):
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
if df.empty:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
# 直接从 DataFrame 索引获取实际的日期范围
|
|
326
|
+
cached_earliest = df.index.min()
|
|
327
|
+
cached_latest = df.index.max()
|
|
320
328
|
|
|
321
|
-
#
|
|
329
|
+
# 解析请求的日期范围
|
|
322
330
|
request_sdate = self._parse_date(sdate) if sdate else None
|
|
323
331
|
request_edate = self._parse_date(edate) if edate else None
|
|
324
332
|
|
|
325
|
-
#
|
|
326
|
-
|
|
333
|
+
# 检查是否有重叠:如果请求的日期范围与缓存数据有重叠,就返回过滤后的数据
|
|
334
|
+
# 注意:即使缓存中有部分数据,也应该返回(让上层决定是否需要扩展)
|
|
335
|
+
has_overlap = True
|
|
336
|
+
if request_edate and request_edate < cached_earliest:
|
|
327
337
|
# 请求的结束日期早于缓存的最早日期,无重叠
|
|
328
|
-
|
|
329
|
-
if request_sdate and
|
|
338
|
+
has_overlap = False
|
|
339
|
+
if request_sdate and request_sdate > cached_latest:
|
|
330
340
|
# 请求的开始日期晚于缓存的最晚日期,无重叠
|
|
331
|
-
|
|
341
|
+
has_overlap = False
|
|
332
342
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
actual_sdate = max(request_sdate, cached_earliest) if request_sdate and cached_earliest else (request_sdate or cached_earliest)
|
|
336
|
-
actual_edate = min(request_edate, cached_latest) if request_edate and cached_latest else (request_edate or cached_latest)
|
|
343
|
+
if not has_overlap:
|
|
344
|
+
return None
|
|
337
345
|
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
actual_sdate.strftime('%Y-%m-%d') if actual_sdate else None,
|
|
342
|
-
actual_edate.strftime('%Y-%m-%d') if actual_edate else None
|
|
343
|
-
)
|
|
346
|
+
# 按照请求的日期范围过滤数据(即使缓存中有更多数据,也只返回请求范围内的)
|
|
347
|
+
# 重要:必须按照 edate 截取,和从网络获取的行为一致
|
|
348
|
+
filtered_df = self._filter_dataframe_by_date(df, sdate, edate)
|
|
344
349
|
|
|
345
350
|
if filtered_df.empty:
|
|
346
351
|
return None
|
|
347
352
|
|
|
348
|
-
# 确保索引是 DatetimeIndex
|
|
349
|
-
if not isinstance(filtered_df.index, pd.DatetimeIndex):
|
|
350
|
-
try:
|
|
351
|
-
filtered_df.index = pd.to_datetime(filtered_df.index)
|
|
352
|
-
except (ValueError, TypeError):
|
|
353
|
-
pass # 如果转换失败,保持原样
|
|
354
|
-
|
|
355
353
|
return (symbol, name, filtered_df)
|
|
356
354
|
|
|
357
355
|
def put(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
@@ -449,7 +447,10 @@ class PersistentCache(Cache):
|
|
|
449
447
|
def _put_duckdb(self, base_key: str, symbol: str, name: str, df: pd.DataFrame,
|
|
450
448
|
earliest_date: Optional[str], latest_date: Optional[str],
|
|
451
449
|
freq: str, fq: str, expire_at: Optional[pd.Timestamp]):
|
|
452
|
-
"""存储到 duckdb
|
|
450
|
+
"""存储到 duckdb
|
|
451
|
+
|
|
452
|
+
注意:earliest_date 和 latest_date 仅用于记录,实际查询时从 DataFrame 索引获取
|
|
453
|
+
"""
|
|
453
454
|
import pickle
|
|
454
455
|
data_blob = pickle.dumps(df)
|
|
455
456
|
|
rquote/utils/logging.py
CHANGED
|
@@ -3,20 +3,40 @@
|
|
|
3
3
|
日志工具
|
|
4
4
|
"""
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def setup_logger():
|
|
9
10
|
"""设置日志记录器"""
|
|
10
11
|
logger = logging.getLogger('rquote')
|
|
11
12
|
if not logger.handlers:
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
# 默认关闭日志,通过环境变量 RQUOTE_LOG_LEVEL 控制
|
|
14
|
+
log_level = os.getenv('RQUOTE_LOG_LEVEL', '').upper()
|
|
15
|
+
log_file = os.getenv('RQUOTE_LOG_FILE', '/tmp/rquote.log')
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# 如果设置了有效的日志级别,则启用日志
|
|
18
|
+
if log_level in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
|
|
19
|
+
# 将字符串级别转换为logging级别
|
|
20
|
+
level_map = {
|
|
21
|
+
'DEBUG': logging.DEBUG,
|
|
22
|
+
'INFO': logging.INFO,
|
|
23
|
+
'WARNING': logging.WARNING,
|
|
24
|
+
'ERROR': logging.ERROR,
|
|
25
|
+
'CRITICAL': logging.CRITICAL,
|
|
26
|
+
}
|
|
27
|
+
logger.setLevel(level_map[log_level])
|
|
28
|
+
|
|
29
|
+
# 添加文件handler
|
|
30
|
+
file_handler = logging.FileHandler(log_file)
|
|
31
|
+
formatter = logging.Formatter('%(asctime)-15s:%(lineno)s %(message)s')
|
|
32
|
+
file_handler.setFormatter(formatter)
|
|
33
|
+
logger.addHandler(file_handler)
|
|
34
|
+
|
|
35
|
+
# 添加控制台handler
|
|
36
|
+
logger.addHandler(logging.StreamHandler())
|
|
37
|
+
else:
|
|
38
|
+
# 默认关闭日志:设置为CRITICAL级别,不添加handler
|
|
39
|
+
logger.setLevel(logging.CRITICAL)
|
|
20
40
|
|
|
21
41
|
return logger
|
|
22
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rquote
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch
|
|
5
5
|
Requires-Python: >=3.9.0
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -18,7 +18,7 @@ Requires-Dist: duckdb>=0.9.0; extra == "persistent"
|
|
|
18
18
|
|
|
19
19
|
## 版本信息
|
|
20
20
|
|
|
21
|
-
当前版本:**0.4.
|
|
21
|
+
当前版本:**0.4.3**
|
|
22
22
|
|
|
23
23
|
## 主要特性
|
|
24
24
|
|
|
@@ -346,6 +346,50 @@ os.environ['RQUOTE_HTTP_TIMEOUT'] = '20'
|
|
|
346
346
|
config_from_env = config.Config.from_env()
|
|
347
347
|
```
|
|
348
348
|
|
|
349
|
+
### 日志配置
|
|
350
|
+
|
|
351
|
+
**默认情况下,日志功能是关闭的。** 如果需要启用日志,可以通过环境变量手动开启:
|
|
352
|
+
|
|
353
|
+
#### 通过环境变量开启日志
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# 设置日志级别为 INFO(会同时输出到文件和控制台)
|
|
357
|
+
export RQUOTE_LOG_LEVEL=INFO
|
|
358
|
+
|
|
359
|
+
# 可选:自定义日志文件路径(默认为 /tmp/rquote.log)
|
|
360
|
+
export RQUOTE_LOG_FILE=/path/to/your/logfile.log
|
|
361
|
+
|
|
362
|
+
# 然后运行你的Python脚本
|
|
363
|
+
python your_script.py
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### 支持的日志级别
|
|
367
|
+
|
|
368
|
+
- `DEBUG`: 详细的调试信息
|
|
369
|
+
- `INFO`: 一般信息(推荐)
|
|
370
|
+
- `WARNING`: 警告信息
|
|
371
|
+
- `ERROR`: 错误信息
|
|
372
|
+
- `CRITICAL`: 严重错误
|
|
373
|
+
|
|
374
|
+
#### 在Python代码中开启日志
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
import os
|
|
378
|
+
|
|
379
|
+
# 在导入 rquote 之前设置环境变量
|
|
380
|
+
os.environ['RQUOTE_LOG_LEVEL'] = 'INFO'
|
|
381
|
+
os.environ['RQUOTE_LOG_FILE'] = '/tmp/rquote.log' # 可选
|
|
382
|
+
|
|
383
|
+
from rquote import get_price
|
|
384
|
+
|
|
385
|
+
# 现在日志已启用
|
|
386
|
+
sid, name, df = get_price('sh000001')
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### 关闭日志
|
|
390
|
+
|
|
391
|
+
如果不设置 `RQUOTE_LOG_LEVEL` 环境变量,或者设置为空值,日志功能将保持关闭状态(默认行为)。
|
|
392
|
+
|
|
349
393
|
### 使用改进的HTTP客户端
|
|
350
394
|
|
|
351
395
|
```python
|
|
@@ -11,7 +11,7 @@ rquote/api/tick.py,sha256=nEcjuAjtBHUaD8KPRLg643piVa21PhKDQvkVWNwvvME,1431
|
|
|
11
11
|
rquote/cache/__init__.py,sha256=S393I5Wmp0QooaRka9n7bvDUdEbg3jUhm6u815T86rM,317
|
|
12
12
|
rquote/cache/base.py,sha256=orzG4Yo-6gzVG027j1-LTZPT718JohnCdLDnOLoLUQ4,515
|
|
13
13
|
rquote/cache/memory.py,sha256=7z4keb3q91pzI4ASQWy1MU8T5nbWLCEUjJcStv_3hvk,1933
|
|
14
|
-
rquote/cache/persistent.py,sha256=
|
|
14
|
+
rquote/cache/persistent.py,sha256=dgzH_rKVwUzuYTxsIWEHX2orixFesWd-sIUJ7TwbS7Y,19369
|
|
15
15
|
rquote/data_sources/__init__.py,sha256=WCe1aam4677jM5G6wP4a-dQFTeBzcU5PJqsKieAVMBo,215
|
|
16
16
|
rquote/data_sources/base.py,sha256=JuKsTMxH7y8yRxHg3JbLzQwXPr43rS4pnwc5625u2U4,443
|
|
17
17
|
rquote/data_sources/sina.py,sha256=T_3Dl0Mwlhx8CKRJll_UKobYecRWltGaIOiGkpHS43Q,3300
|
|
@@ -31,9 +31,9 @@ rquote/utils/__init__.py,sha256=-ZHABqFHQeJrCCsgnqEYWR57jl7GduCKn2V3hpFi-pE,348
|
|
|
31
31
|
rquote/utils/date.py,sha256=nhK3xQ2kFvKhdkPw-2HR2V0PSzBfXmX8L7laG7VmG2E,913
|
|
32
32
|
rquote/utils/helpers.py,sha256=V07n9BtRS8bEJH023Kca78-unk7iD3B9hn2UjELetYs,354
|
|
33
33
|
rquote/utils/http.py,sha256=X0Alhnu0CNqyQeOt6ivUWmh2XwrWxXd2lSpQOKDdnzw,3249
|
|
34
|
-
rquote/utils/logging.py,sha256=
|
|
34
|
+
rquote/utils/logging.py,sha256=fs2YF1Srux4LLTdk_Grjm5g1f4mzewI38VVSAI82goA,1471
|
|
35
35
|
rquote/utils/web.py,sha256=I8_pcThW6VUvahuRHdtp32iZwr85hEt1hB6TgznMy_U,3854
|
|
36
|
-
rquote-0.4.
|
|
37
|
-
rquote-0.4.
|
|
38
|
-
rquote-0.4.
|
|
39
|
-
rquote-0.4.
|
|
36
|
+
rquote-0.4.3.dist-info/METADATA,sha256=R9zS71G3lxp8C43qrY67DBn2Y2t_1P7rTtCztfj7Kq8,14344
|
|
37
|
+
rquote-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
rquote-0.4.3.dist-info/top_level.txt,sha256=CehAiaZx7Fo8HGoV2zd5GhILUW1jQEN8YS-cWMlrK9Y,7
|
|
39
|
+
rquote-0.4.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|