rquote 0.4.1__tar.gz → 0.4.3__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.
Files changed (50) hide show
  1. {rquote-0.4.1 → rquote-0.4.3}/PKG-INFO +46 -2
  2. {rquote-0.4.1 → rquote-0.4.3}/README.md +45 -1
  3. {rquote-0.4.1 → rquote-0.4.3}/pyproject.toml +1 -1
  4. {rquote-0.4.1 → rquote-0.4.3}/rquote/cache/persistent.py +58 -57
  5. rquote-0.4.3/rquote/utils/logging.py +45 -0
  6. {rquote-0.4.1 → rquote-0.4.3}/rquote.egg-info/PKG-INFO +46 -2
  7. rquote-0.4.1/rquote/utils/logging.py +0 -25
  8. {rquote-0.4.1 → rquote-0.4.3}/rquote/__init__.py +0 -0
  9. {rquote-0.4.1 → rquote-0.4.3}/rquote/api/__init__.py +0 -0
  10. {rquote-0.4.1 → rquote-0.4.3}/rquote/api/lists.py +0 -0
  11. {rquote-0.4.1 → rquote-0.4.3}/rquote/api/price.py +0 -0
  12. {rquote-0.4.1 → rquote-0.4.3}/rquote/api/stock_info.py +0 -0
  13. {rquote-0.4.1 → rquote-0.4.3}/rquote/api/tick.py +0 -0
  14. {rquote-0.4.1 → rquote-0.4.3}/rquote/cache/__init__.py +0 -0
  15. {rquote-0.4.1 → rquote-0.4.3}/rquote/cache/base.py +0 -0
  16. {rquote-0.4.1 → rquote-0.4.3}/rquote/cache/memory.py +0 -0
  17. {rquote-0.4.1 → rquote-0.4.3}/rquote/config.py +0 -0
  18. {rquote-0.4.1 → rquote-0.4.3}/rquote/data_sources/__init__.py +0 -0
  19. {rquote-0.4.1 → rquote-0.4.3}/rquote/data_sources/base.py +0 -0
  20. {rquote-0.4.1 → rquote-0.4.3}/rquote/data_sources/sina.py +0 -0
  21. {rquote-0.4.1 → rquote-0.4.3}/rquote/data_sources/tencent.py +0 -0
  22. {rquote-0.4.1 → rquote-0.4.3}/rquote/exceptions.py +0 -0
  23. {rquote-0.4.1 → rquote-0.4.3}/rquote/factors/__init__.py +0 -0
  24. {rquote-0.4.1 → rquote-0.4.3}/rquote/factors/technical.py +0 -0
  25. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/__init__.py +0 -0
  26. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/base.py +0 -0
  27. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/cn_stock.py +0 -0
  28. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/factory.py +0 -0
  29. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/future.py +0 -0
  30. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/hk_stock.py +0 -0
  31. {rquote-0.4.1 → rquote-0.4.3}/rquote/markets/us_stock.py +0 -0
  32. {rquote-0.4.1 → rquote-0.4.3}/rquote/parsers/__init__.py +0 -0
  33. {rquote-0.4.1 → rquote-0.4.3}/rquote/parsers/kline.py +0 -0
  34. {rquote-0.4.1 → rquote-0.4.3}/rquote/plots.py +0 -0
  35. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils/__init__.py +0 -0
  36. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils/date.py +0 -0
  37. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils/helpers.py +0 -0
  38. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils/http.py +0 -0
  39. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils/web.py +0 -0
  40. {rquote-0.4.1 → rquote-0.4.3}/rquote/utils.py +0 -0
  41. {rquote-0.4.1 → rquote-0.4.3}/rquote.egg-info/SOURCES.txt +0 -0
  42. {rquote-0.4.1 → rquote-0.4.3}/rquote.egg-info/dependency_links.txt +0 -0
  43. {rquote-0.4.1 → rquote-0.4.3}/rquote.egg-info/requires.txt +0 -0
  44. {rquote-0.4.1 → rquote-0.4.3}/rquote.egg-info/top_level.txt +0 -0
  45. {rquote-0.4.1 → rquote-0.4.3}/setup.cfg +0 -0
  46. {rquote-0.4.1 → rquote-0.4.3}/tests/test_api.py +0 -0
  47. {rquote-0.4.1 → rquote-0.4.3}/tests/test_cache.py +0 -0
  48. {rquote-0.4.1 → rquote-0.4.3}/tests/test_config.py +0 -0
  49. {rquote-0.4.1 → rquote-0.4.3}/tests/test_exceptions.py +0 -0
  50. {rquote-0.4.1 → rquote-0.4.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rquote
3
- Version: 0.4.1
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.1**
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
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 版本信息
6
6
 
7
- 当前版本:**0.4.1**
7
+ 当前版本:**0.4.3**
8
8
 
9
9
  ## 主要特性
10
10
 
@@ -332,6 +332,50 @@ os.environ['RQUOTE_HTTP_TIMEOUT'] = '20'
332
332
  config_from_env = config.Config.from_env()
333
333
  ```
334
334
 
335
+ ### 日志配置
336
+
337
+ **默认情况下,日志功能是关闭的。** 如果需要启用日志,可以通过环境变量手动开启:
338
+
339
+ #### 通过环境变量开启日志
340
+
341
+ ```bash
342
+ # 设置日志级别为 INFO(会同时输出到文件和控制台)
343
+ export RQUOTE_LOG_LEVEL=INFO
344
+
345
+ # 可选:自定义日志文件路径(默认为 /tmp/rquote.log)
346
+ export RQUOTE_LOG_FILE=/path/to/your/logfile.log
347
+
348
+ # 然后运行你的Python脚本
349
+ python your_script.py
350
+ ```
351
+
352
+ #### 支持的日志级别
353
+
354
+ - `DEBUG`: 详细的调试信息
355
+ - `INFO`: 一般信息(推荐)
356
+ - `WARNING`: 警告信息
357
+ - `ERROR`: 错误信息
358
+ - `CRITICAL`: 严重错误
359
+
360
+ #### 在Python代码中开启日志
361
+
362
+ ```python
363
+ import os
364
+
365
+ # 在导入 rquote 之前设置环境变量
366
+ os.environ['RQUOTE_LOG_LEVEL'] = 'INFO'
367
+ os.environ['RQUOTE_LOG_FILE'] = '/tmp/rquote.log' # 可选
368
+
369
+ from rquote import get_price
370
+
371
+ # 现在日志已启用
372
+ sid, name, df = get_price('sh000001')
373
+ ```
374
+
375
+ #### 关闭日志
376
+
377
+ 如果不设置 `RQUOTE_LOG_LEVEL` 环境变量,或者设置为空值,日志功能将保持关闭状态(默认行为)。
378
+
335
379
  ### 使用改进的HTTP客户端
336
380
 
337
381
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rquote"
7
- version = "0.4.1"
7
+ version = "0.4.3"
8
8
  description = "Mostly day quotes of cn/hk/us/fund/future markets, side with quote list fetch"
9
9
  readme = "README.md"
10
10
  # requires-python = ">=3.6.1" # duckdb requires higher python version
@@ -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, earliest_date, latest_date, expire_at
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, earliest_date, latest_date, expire_at = result
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
- cached_earliest = self._parse_date(earliest_date)
258
- cached_latest = self._parse_date(latest_date)
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
- if request_edate and cached_earliest and request_edate < cached_earliest:
274
+ # 检查是否有重叠:如果请求的日期范围与缓存数据有重叠,就返回过滤后的数据
275
+ # 注意:即使缓存中有部分数据,也应该返回(让上层决定是否需要扩展)
276
+ has_overlap = True
277
+ if request_edate and request_edate < cached_earliest:
266
278
  # 请求的结束日期早于缓存的最早日期,无重叠
267
- return None
268
- if request_sdate and cached_latest and request_sdate > cached_latest:
279
+ has_overlap = False
280
+ if request_sdate and request_sdate > cached_latest:
269
281
  # 请求的开始日期晚于缓存的最晚日期,无重叠
270
- return None
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
- filtered_df = self._filter_dataframe_by_date(
279
- df,
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
- cached_earliest = self._parse_date(earliest_date)
319
- cached_latest = self._parse_date(latest_date)
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
- if request_edate and cached_earliest and request_edate < cached_earliest:
333
+ # 检查是否有重叠:如果请求的日期范围与缓存数据有重叠,就返回过滤后的数据
334
+ # 注意:即使缓存中有部分数据,也应该返回(让上层决定是否需要扩展)
335
+ has_overlap = True
336
+ if request_edate and request_edate < cached_earliest:
327
337
  # 请求的结束日期早于缓存的最早日期,无重叠
328
- return None
329
- if request_sdate and cached_latest and request_sdate > cached_latest:
338
+ has_overlap = False
339
+ if request_sdate and request_sdate > cached_latest:
330
340
  # 请求的开始日期晚于缓存的最晚日期,无重叠
331
- return None
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
- filtered_df = self._filter_dataframe_by_date(
340
- df,
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
 
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 日志工具
4
+ """
5
+ import logging
6
+ import os
7
+
8
+
9
+ def setup_logger():
10
+ """设置日志记录器"""
11
+ logger = logging.getLogger('rquote')
12
+ if not logger.handlers:
13
+ # 默认关闭日志,通过环境变量 RQUOTE_LOG_LEVEL 控制
14
+ log_level = os.getenv('RQUOTE_LOG_LEVEL', '').upper()
15
+ log_file = os.getenv('RQUOTE_LOG_FILE', '/tmp/rquote.log')
16
+
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)
40
+
41
+ return logger
42
+
43
+
44
+ logger = setup_logger()
45
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rquote
3
- Version: 0.4.1
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.1**
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
@@ -1,25 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- 日志工具
4
- """
5
- import logging
6
-
7
-
8
- def setup_logger():
9
- """设置日志记录器"""
10
- logger = logging.getLogger('rquote')
11
- if not logger.handlers:
12
- logger.setLevel(logging.INFO)
13
- file_handler = logging.FileHandler('/tmp/rquote.log')
14
-
15
- formatter = logging.Formatter('%(asctime)-15s:%(lineno)s %(message)s')
16
- file_handler.setFormatter(formatter)
17
-
18
- logger.addHandler(file_handler)
19
- logger.addHandler(logging.StreamHandler())
20
-
21
- return logger
22
-
23
-
24
- logger = setup_logger()
25
-
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