lixinger-python 0.3.11__py3-none-any.whl → 0.3.13__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.
- lixinger/__init__.py +11 -0
- lixinger/api/base.py +48 -3
- lixinger/api/macro/__init__.py +30 -0
- lixinger/api/macro/namespace.py +62 -0
- lixinger/api/macro/national_debt.py +122 -0
- lixinger/api/macro/rmb_deposits.py +126 -0
- lixinger/api/macro/rmb_loans.py +126 -0
- lixinger/api/macro/social_financing.py +125 -0
- lixinger/client.py +27 -0
- lixinger/exceptions.py +17 -1
- lixinger/models/macro/__init__.py +13 -0
- lixinger/models/macro/national_debt.py +20 -0
- lixinger/models/macro/rmb_deposits.py +20 -0
- lixinger/models/macro/rmb_loans.py +20 -0
- lixinger/models/macro/social_financing.py +20 -0
- lixinger/utils/retry.py +23 -2
- {lixinger_python-0.3.11.dist-info → lixinger_python-0.3.13.dist-info}/METADATA +1 -1
- {lixinger_python-0.3.11.dist-info → lixinger_python-0.3.13.dist-info}/RECORD +20 -9
- {lixinger_python-0.3.11.dist-info → lixinger_python-0.3.13.dist-info}/WHEEL +0 -0
- {lixinger_python-0.3.11.dist-info → lixinger_python-0.3.13.dist-info}/licenses/LICENSE +0 -0
lixinger/__init__.py
CHANGED
|
@@ -47,6 +47,12 @@ from lixinger.api.cn.industry import (
|
|
|
47
47
|
get_industry_sw_2021_margin_trading,
|
|
48
48
|
get_industry_sw_margin_trading,
|
|
49
49
|
)
|
|
50
|
+
from lixinger.api.macro import (
|
|
51
|
+
get_national_debt,
|
|
52
|
+
get_rmb_deposits,
|
|
53
|
+
get_rmb_loans,
|
|
54
|
+
get_social_financing,
|
|
55
|
+
)
|
|
50
56
|
from lixinger.client import AsyncLixingerClient
|
|
51
57
|
from lixinger.config import set_token
|
|
52
58
|
from lixinger.exceptions import (
|
|
@@ -104,4 +110,9 @@ __all__ = [
|
|
|
104
110
|
"get_industry_sw_margin_trading",
|
|
105
111
|
"get_industry_sw_2021_margin_trading",
|
|
106
112
|
"get_industry_cni_margin_trading",
|
|
113
|
+
# Macro
|
|
114
|
+
"get_national_debt",
|
|
115
|
+
"get_rmb_deposits",
|
|
116
|
+
"get_rmb_loans",
|
|
117
|
+
"get_social_financing",
|
|
107
118
|
]
|
lixinger/api/base.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
from email.utils import parsedate_to_datetime
|
|
1
3
|
from typing import Any
|
|
2
4
|
|
|
3
5
|
import httpx
|
|
4
6
|
|
|
5
7
|
from lixinger.config import Config, settings
|
|
6
|
-
from lixinger.exceptions import APIError, AuthenticationError, RateLimitError
|
|
8
|
+
from lixinger.exceptions import APIError, AuthenticationError, RateLimitError, ValidationError
|
|
7
9
|
from lixinger.utils.rate_limiter import AsyncRateLimiter
|
|
8
10
|
from lixinger.utils.retry import async_retry_on_failure
|
|
9
11
|
|
|
@@ -29,6 +31,39 @@ def get_global_client() -> httpx.AsyncClient:
|
|
|
29
31
|
return _global_http_client
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
def _parse_retry_after(header_value: str | None) -> float | None:
|
|
35
|
+
"""Parse a Retry-After header into seconds.
|
|
36
|
+
|
|
37
|
+
Accepts both formats defined by RFC 7231 §7.1.3:
|
|
38
|
+
- ``delta-seconds`` (e.g. ``"120"``)
|
|
39
|
+
- ``HTTP-date`` (e.g. ``"Wed, 21 Oct 2015 07:28:00 GMT"``)
|
|
40
|
+
|
|
41
|
+
Returns ``None`` when the header is missing or unparseable so the caller
|
|
42
|
+
can fall back to its default backoff.
|
|
43
|
+
"""
|
|
44
|
+
if not header_value:
|
|
45
|
+
return None
|
|
46
|
+
value = header_value.strip()
|
|
47
|
+
# Try delta-seconds first — the common case.
|
|
48
|
+
try:
|
|
49
|
+
seconds = float(value)
|
|
50
|
+
except ValueError:
|
|
51
|
+
pass
|
|
52
|
+
else:
|
|
53
|
+
return seconds if seconds >= 0 else None
|
|
54
|
+
# Fall back to HTTP-date.
|
|
55
|
+
try:
|
|
56
|
+
target = parsedate_to_datetime(value)
|
|
57
|
+
except (TypeError, ValueError):
|
|
58
|
+
return None
|
|
59
|
+
if target.tzinfo is None:
|
|
60
|
+
# RFC 7231 HTTP-date is always GMT, so treat naive dates as UTC.
|
|
61
|
+
target = target.replace(tzinfo=UTC)
|
|
62
|
+
now = datetime.now(tz=target.tzinfo)
|
|
63
|
+
delta = (target - now).total_seconds()
|
|
64
|
+
return max(delta, 0.0)
|
|
65
|
+
|
|
66
|
+
|
|
32
67
|
class BaseAPI:
|
|
33
68
|
"""Base class for API endpoint groups."""
|
|
34
69
|
|
|
@@ -42,7 +77,12 @@ class BaseAPI:
|
|
|
42
77
|
self._config = config or settings
|
|
43
78
|
self._rate_limiter = rate_limiter or _global_rate_limiter
|
|
44
79
|
|
|
45
|
-
@async_retry_on_failure(
|
|
80
|
+
@async_retry_on_failure(
|
|
81
|
+
max_retries=3,
|
|
82
|
+
backoff=1.0,
|
|
83
|
+
# Do NOT retry non-transient errors even though they inherit from Exception.
|
|
84
|
+
no_retry_on=(AuthenticationError, ValidationError),
|
|
85
|
+
)
|
|
46
86
|
async def _request(
|
|
47
87
|
self,
|
|
48
88
|
method: str,
|
|
@@ -72,7 +112,12 @@ class BaseAPI:
|
|
|
72
112
|
)
|
|
73
113
|
|
|
74
114
|
if response.status_code == 429:
|
|
75
|
-
|
|
115
|
+
retry_after = _parse_retry_after(response.headers.get("Retry-After"))
|
|
116
|
+
raise RateLimitError(
|
|
117
|
+
"Rate limit exceeded",
|
|
118
|
+
status_code=429,
|
|
119
|
+
retry_after=retry_after,
|
|
120
|
+
)
|
|
76
121
|
if response.status_code == 401:
|
|
77
122
|
raise AuthenticationError("Authentication failed", status_code=401)
|
|
78
123
|
if response.status_code != 200:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Macro APIs.
|
|
2
|
+
|
|
3
|
+
This module provides access to macroeconomic data APIs:
|
|
4
|
+
|
|
5
|
+
- national_debt: 国债收益率 (/macro/national-debt)
|
|
6
|
+
- rmb_deposits: 人民币存款 (/macro/rmb-deposits)
|
|
7
|
+
- rmb_loans: 人民币贷款 (/macro/rmb-loans)
|
|
8
|
+
- social_financing: 社会融资规模 (/macro/social-financing)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from lixinger.api.macro.national_debt import NationalDebtAPI, get_national_debt
|
|
12
|
+
from lixinger.api.macro.rmb_deposits import RmbDepositsAPI, get_rmb_deposits
|
|
13
|
+
from lixinger.api.macro.rmb_loans import RmbLoansAPI, get_rmb_loans
|
|
14
|
+
from lixinger.api.macro.social_financing import (
|
|
15
|
+
SocialFinancingAPI,
|
|
16
|
+
get_social_financing,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# API Classes
|
|
21
|
+
"NationalDebtAPI",
|
|
22
|
+
"RmbDepositsAPI",
|
|
23
|
+
"RmbLoansAPI",
|
|
24
|
+
"SocialFinancingAPI",
|
|
25
|
+
# Functional APIs
|
|
26
|
+
"get_national_debt",
|
|
27
|
+
"get_rmb_deposits",
|
|
28
|
+
"get_rmb_loans",
|
|
29
|
+
"get_social_financing",
|
|
30
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Macro namespace for grouping related APIs."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from lixinger.api.macro.national_debt import NationalDebtAPI
|
|
8
|
+
from lixinger.api.macro.rmb_deposits import RmbDepositsAPI
|
|
9
|
+
from lixinger.api.macro.rmb_loans import RmbLoansAPI
|
|
10
|
+
from lixinger.api.macro.social_financing import SocialFinancingAPI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MacroNamespace:
|
|
14
|
+
"""Namespace for macroeconomic APIs.
|
|
15
|
+
|
|
16
|
+
This class groups all macro APIs under a single namespace.
|
|
17
|
+
Access APIs via their specific attributes:
|
|
18
|
+
|
|
19
|
+
- national_debt: 国债收益率 API
|
|
20
|
+
- rmb_deposits: 人民币存款 API
|
|
21
|
+
- rmb_loans: 人民币贷款 API
|
|
22
|
+
- social_financing: 社会融资 API
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
national_debt: NationalDebtAPI,
|
|
29
|
+
rmb_deposits: RmbDepositsAPI,
|
|
30
|
+
rmb_loans: RmbLoansAPI,
|
|
31
|
+
social_financing: SocialFinancingAPI,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize the macro namespace.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
national_debt: 国债 API
|
|
37
|
+
rmb_deposits: 人民币存款 API
|
|
38
|
+
rmb_loans: 人民币贷款 API
|
|
39
|
+
social_financing: 社会融资 API
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
self.national_debt = national_debt
|
|
43
|
+
self.rmb_deposits = rmb_deposits
|
|
44
|
+
self.rmb_loans = rmb_loans
|
|
45
|
+
self.social_financing = social_financing
|
|
46
|
+
|
|
47
|
+
# Convenience aliases for shorter access
|
|
48
|
+
async def get_national_debt(self, *args: Any, **kwargs: Any) -> pd.DataFrame:
|
|
49
|
+
"""Alias for national_debt.get_national_debt."""
|
|
50
|
+
return await self.national_debt.get_national_debt(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
async def get_rmb_deposits(self, *args: Any, **kwargs: Any) -> pd.DataFrame:
|
|
53
|
+
"""Alias for rmb_deposits.get_rmb_deposits."""
|
|
54
|
+
return await self.rmb_deposits.get_rmb_deposits(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
async def get_rmb_loans(self, *args: Any, **kwargs: Any) -> pd.DataFrame:
|
|
57
|
+
"""Alias for rmb_loans.get_rmb_loans."""
|
|
58
|
+
return await self.rmb_loans.get_rmb_loans(*args, **kwargs)
|
|
59
|
+
|
|
60
|
+
async def get_social_financing(self, *args: Any, **kwargs: Any) -> pd.DataFrame:
|
|
61
|
+
"""Alias for social_financing.get_social_financing."""
|
|
62
|
+
return await self.social_financing.get_social_financing(*args, **kwargs)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""国债 API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from lixinger.api.base import BaseAPI
|
|
8
|
+
from lixinger.models.macro.national_debt import NationalDebt
|
|
9
|
+
from lixinger.utils.api import api
|
|
10
|
+
from lixinger.utils.dataframe import get_response_df
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NationalDebtAPI(BaseAPI):
|
|
14
|
+
"""国债 API."""
|
|
15
|
+
|
|
16
|
+
async def get_national_debt(
|
|
17
|
+
self,
|
|
18
|
+
area_code: str,
|
|
19
|
+
start_date: str,
|
|
20
|
+
end_date: str,
|
|
21
|
+
metrics_list: list[str],
|
|
22
|
+
limit: int | None = None,
|
|
23
|
+
) -> pd.DataFrame:
|
|
24
|
+
"""获取国债数据.
|
|
25
|
+
|
|
26
|
+
获取国债收益率数据. 可按时间范围获取大陆和美国各期限国债的收益率数据,
|
|
27
|
+
包括三月期/六月期/一年期/二年期/三年期/五年期/七年期/十年期/二十年期/
|
|
28
|
+
三十年期收益率. 当用户想了解无风险利率水平或利率期限结构时, 调用此接口.
|
|
29
|
+
|
|
30
|
+
API Endpoint: /macro/national-debt
|
|
31
|
+
API Method: POST
|
|
32
|
+
API Doc: https://www.lixinger.com/open/api/doc?api-key=macro/national-debt
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
area_code: 地区代码, ``cn`` 或 ``us``.
|
|
36
|
+
start_date: 信息起始时间 (YYYY-MM-DD, 北京时间), 开始和结束的时间间隔不超过10年.
|
|
37
|
+
end_date: 信息结束时间 (YYYY-MM-DD, 北京时间).
|
|
38
|
+
metrics_list: 指标列表, 例如 ``["tcm_m3", "tcm_y10"]``.
|
|
39
|
+
支持的指标: ``tcm_m3``, ``tcm_m6``, ``tcm_y1``, ``tcm_y2``, ``tcm_y3``,
|
|
40
|
+
``tcm_y5``, ``tcm_y7``, ``tcm_y10``, ``tcm_y20``, ``tcm_y30``.
|
|
41
|
+
limit: 返回最近数据的数量.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
DataFrame 包含以下列:
|
|
45
|
+
|
|
46
|
+
- date: 数据时间
|
|
47
|
+
- area_code: 地区代码
|
|
48
|
+
- 各请求指标 (如 tcm_m3, tcm_y10 等)
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
获取大陆三月期国债收益率::
|
|
52
|
+
|
|
53
|
+
from lixinger import AsyncLixingerClient
|
|
54
|
+
|
|
55
|
+
async with AsyncLixingerClient() as client:
|
|
56
|
+
df = await client.macro.national_debt.get_national_debt(
|
|
57
|
+
area_code="cn",
|
|
58
|
+
start_date="2025-06-13",
|
|
59
|
+
end_date="2026-06-13",
|
|
60
|
+
metrics_list=["tcm_m3"],
|
|
61
|
+
)
|
|
62
|
+
print(df)
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
payload: dict[str, Any] = {
|
|
66
|
+
"areaCode": area_code,
|
|
67
|
+
"startDate": start_date,
|
|
68
|
+
"endDate": end_date,
|
|
69
|
+
"metricsList": metrics_list,
|
|
70
|
+
}
|
|
71
|
+
if limit is not None:
|
|
72
|
+
payload["limit"] = limit
|
|
73
|
+
|
|
74
|
+
data = await self._request("POST", "/macro/national-debt", json=payload)
|
|
75
|
+
return get_response_df(data, NationalDebt)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Functional API instance
|
|
79
|
+
_api_instance = NationalDebtAPI()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@api
|
|
83
|
+
async def get_national_debt(
|
|
84
|
+
area_code: str,
|
|
85
|
+
start_date: str,
|
|
86
|
+
end_date: str,
|
|
87
|
+
metrics_list: list[str],
|
|
88
|
+
limit: int | None = None,
|
|
89
|
+
) -> pd.DataFrame:
|
|
90
|
+
"""获取国债数据.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
area_code: 地区代码, ``cn`` 或 ``us``.
|
|
94
|
+
start_date: 信息起始时间 (YYYY-MM-DD).
|
|
95
|
+
end_date: 信息结束时间 (YYYY-MM-DD).
|
|
96
|
+
metrics_list: 指标列表, 例如 ``["tcm_m3", "tcm_y10"]``.
|
|
97
|
+
limit: 返回最近数据的数量.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
DataFrame 包含国债收益率数据.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
获取大陆三月期国债收益率::
|
|
104
|
+
|
|
105
|
+
from lixinger import get_national_debt
|
|
106
|
+
|
|
107
|
+
df = await get_national_debt(
|
|
108
|
+
area_code="cn",
|
|
109
|
+
start_date="2025-06-13",
|
|
110
|
+
end_date="2026-06-13",
|
|
111
|
+
metrics_list=["tcm_m3"],
|
|
112
|
+
)
|
|
113
|
+
print(df)
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
return await _api_instance.get_national_debt(
|
|
117
|
+
area_code=area_code,
|
|
118
|
+
start_date=start_date,
|
|
119
|
+
end_date=end_date,
|
|
120
|
+
metrics_list=metrics_list,
|
|
121
|
+
limit=limit,
|
|
122
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""人民币存款 API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from lixinger.api.base import BaseAPI
|
|
8
|
+
from lixinger.models.macro.rmb_deposits import RmbDeposits
|
|
9
|
+
from lixinger.utils.api import api
|
|
10
|
+
from lixinger.utils.dataframe import get_response_df
|
|
11
|
+
from lixinger.utils.dict import flatten_dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RmbDepositsAPI(BaseAPI):
|
|
15
|
+
"""人民币存款 API."""
|
|
16
|
+
|
|
17
|
+
async def get_rmb_deposits(
|
|
18
|
+
self,
|
|
19
|
+
area_code: str,
|
|
20
|
+
start_date: str,
|
|
21
|
+
end_date: str,
|
|
22
|
+
metrics_list: list[str],
|
|
23
|
+
limit: int | None = None,
|
|
24
|
+
) -> pd.DataFrame:
|
|
25
|
+
"""获取人民币存款数据.
|
|
26
|
+
|
|
27
|
+
获取人民币存款数据, 包括人民币存款、境内存款、境外存款、住户存款、
|
|
28
|
+
非金融企业存款、机关团体存款、财政性存款、非银行业金融机构存款、住户活期存款、
|
|
29
|
+
住户定期及其他存款、非金融企业活期存款、非金融企业定期及其他存款等. 支持
|
|
30
|
+
存量及月度新增, 以及同比环比等. 当用户想了解银行负债端结构或居民储蓄意愿时,
|
|
31
|
+
调用此接口.
|
|
32
|
+
|
|
33
|
+
API Endpoint: /macro/rmb-deposits
|
|
34
|
+
API Method: POST
|
|
35
|
+
API Doc: https://www.lixinger.com/open/api/doc?api-key=macro/rmb-deposits
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
39
|
+
start_date: 信息起始时间 (YYYY-MM-DD, 北京时间), 开始和结束的时间间隔不超过10年.
|
|
40
|
+
end_date: 信息结束时间 (YYYY-MM-DD, 北京时间).
|
|
41
|
+
metrics_list: 指标列表, 格式为
|
|
42
|
+
``[granularity].[metricsName].[expressionCalculateType]``,
|
|
43
|
+
例如 ``["y.rmb_d.t", "m.rmb_d.c_y2y"]``.
|
|
44
|
+
limit: 返回最近数据的数量.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
DataFrame 包含以下列:
|
|
48
|
+
|
|
49
|
+
- date: 数据时间
|
|
50
|
+
- 各请求指标 (点号分隔, 例如 ``y.rmb_d.t``)
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
获取大陆人民币存款年度累积值::
|
|
54
|
+
|
|
55
|
+
from lixinger import AsyncLixingerClient
|
|
56
|
+
|
|
57
|
+
async with AsyncLixingerClient() as client:
|
|
58
|
+
df = await client.macro.rmb_deposits.get_rmb_deposits(
|
|
59
|
+
area_code="cn",
|
|
60
|
+
start_date="2023-06-13",
|
|
61
|
+
end_date="2026-06-13",
|
|
62
|
+
metrics_list=["y.rmb_d.t"],
|
|
63
|
+
)
|
|
64
|
+
print(df)
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
payload: dict[str, Any] = {
|
|
68
|
+
"areaCode": area_code,
|
|
69
|
+
"startDate": start_date,
|
|
70
|
+
"endDate": end_date,
|
|
71
|
+
"metricsList": metrics_list,
|
|
72
|
+
}
|
|
73
|
+
if limit is not None:
|
|
74
|
+
payload["limit"] = limit
|
|
75
|
+
|
|
76
|
+
data = await self._request("POST", "/macro/rmb-deposits", json=payload)
|
|
77
|
+
# Flatten nested metrics (e.g. {"y": {"rmb_d": {"t": ...}}})
|
|
78
|
+
flattened_data = [flatten_dict(item) for item in data]
|
|
79
|
+
return get_response_df(flattened_data, RmbDeposits)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Functional API instance
|
|
83
|
+
_api_instance = RmbDepositsAPI()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@api
|
|
87
|
+
async def get_rmb_deposits(
|
|
88
|
+
area_code: str,
|
|
89
|
+
start_date: str,
|
|
90
|
+
end_date: str,
|
|
91
|
+
metrics_list: list[str],
|
|
92
|
+
limit: int | None = None,
|
|
93
|
+
) -> pd.DataFrame:
|
|
94
|
+
"""获取人民币存款数据.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
98
|
+
start_date: 信息起始时间 (YYYY-MM-DD).
|
|
99
|
+
end_date: 信息结束时间 (YYYY-MM-DD).
|
|
100
|
+
metrics_list: 指标列表, 例如 ``["y.rmb_d.t"]``.
|
|
101
|
+
limit: 返回最近数据的数量.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
DataFrame 包含人民币存款数据.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
获取大陆人民币存款年度累积值::
|
|
108
|
+
|
|
109
|
+
from lixinger import get_rmb_deposits
|
|
110
|
+
|
|
111
|
+
df = await get_rmb_deposits(
|
|
112
|
+
area_code="cn",
|
|
113
|
+
start_date="2023-06-13",
|
|
114
|
+
end_date="2026-06-13",
|
|
115
|
+
metrics_list=["y.rmb_d.t"],
|
|
116
|
+
)
|
|
117
|
+
print(df)
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
return await _api_instance.get_rmb_deposits(
|
|
121
|
+
area_code=area_code,
|
|
122
|
+
start_date=start_date,
|
|
123
|
+
end_date=end_date,
|
|
124
|
+
metrics_list=metrics_list,
|
|
125
|
+
limit=limit,
|
|
126
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""人民币贷款 API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from lixinger.api.base import BaseAPI
|
|
8
|
+
from lixinger.models.macro.rmb_loans import RmbLoans
|
|
9
|
+
from lixinger.utils.api import api
|
|
10
|
+
from lixinger.utils.dataframe import get_response_df
|
|
11
|
+
from lixinger.utils.dict import flatten_dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RmbLoansAPI(BaseAPI):
|
|
15
|
+
"""人民币贷款 API."""
|
|
16
|
+
|
|
17
|
+
async def get_rmb_loans(
|
|
18
|
+
self,
|
|
19
|
+
area_code: str,
|
|
20
|
+
start_date: str,
|
|
21
|
+
end_date: str,
|
|
22
|
+
metrics_list: list[str],
|
|
23
|
+
limit: int | None = None,
|
|
24
|
+
) -> pd.DataFrame:
|
|
25
|
+
"""获取人民币贷款数据.
|
|
26
|
+
|
|
27
|
+
获取人民币贷款数据, 包括人民币贷款、境内贷款、海外贷款、住户贷款、住户短期贷款、
|
|
28
|
+
住户中长期贷款、企(事)业单位贷款、企(事)业单位短期贷款、企(事)业单位中长期
|
|
29
|
+
贷款、企(事)业单位票据融资、企(事)业单位融资租赁、企(事)业单位各项垫款、
|
|
30
|
+
非银行业金融机构贷款等. 支持存量及月度新增, 以及同比环比等. 当用户想了解
|
|
31
|
+
信贷结构或房贷需求时, 调用此接口.
|
|
32
|
+
|
|
33
|
+
API Endpoint: /macro/rmb-loans
|
|
34
|
+
API Method: POST
|
|
35
|
+
API Doc: https://www.lixinger.com/open/api/doc?api-key=macro/rmb-loans
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
39
|
+
start_date: 信息起始时间 (YYYY-MM-DD, 北京时间), 开始和结束的时间间隔不超过10年.
|
|
40
|
+
end_date: 信息结束时间 (YYYY-MM-DD, 北京时间).
|
|
41
|
+
metrics_list: 指标列表, 格式为
|
|
42
|
+
``[granularity].[metricsName].[expressionCalculateType]``,
|
|
43
|
+
例如 ``["y.rmb_l.t", "m.rmb_h_l.c_y2y"]``.
|
|
44
|
+
limit: 返回最近数据的数量.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
DataFrame 包含以下列:
|
|
48
|
+
|
|
49
|
+
- date: 数据时间
|
|
50
|
+
- 各请求指标 (点号分隔, 例如 ``y.rmb_l.t``)
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
获取大陆人民币贷款年度累积值::
|
|
54
|
+
|
|
55
|
+
from lixinger import AsyncLixingerClient
|
|
56
|
+
|
|
57
|
+
async with AsyncLixingerClient() as client:
|
|
58
|
+
df = await client.macro.rmb_loans.get_rmb_loans(
|
|
59
|
+
area_code="cn",
|
|
60
|
+
start_date="2023-06-13",
|
|
61
|
+
end_date="2026-06-13",
|
|
62
|
+
metrics_list=["y.rmb_l.t"],
|
|
63
|
+
)
|
|
64
|
+
print(df)
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
payload: dict[str, Any] = {
|
|
68
|
+
"areaCode": area_code,
|
|
69
|
+
"startDate": start_date,
|
|
70
|
+
"endDate": end_date,
|
|
71
|
+
"metricsList": metrics_list,
|
|
72
|
+
}
|
|
73
|
+
if limit is not None:
|
|
74
|
+
payload["limit"] = limit
|
|
75
|
+
|
|
76
|
+
data = await self._request("POST", "/macro/rmb-loans", json=payload)
|
|
77
|
+
# Flatten nested metrics (e.g. {"y": {"rmb_l": {"t": ...}}})
|
|
78
|
+
flattened_data = [flatten_dict(item) for item in data]
|
|
79
|
+
return get_response_df(flattened_data, RmbLoans)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Functional API instance
|
|
83
|
+
_api_instance = RmbLoansAPI()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@api
|
|
87
|
+
async def get_rmb_loans(
|
|
88
|
+
area_code: str,
|
|
89
|
+
start_date: str,
|
|
90
|
+
end_date: str,
|
|
91
|
+
metrics_list: list[str],
|
|
92
|
+
limit: int | None = None,
|
|
93
|
+
) -> pd.DataFrame:
|
|
94
|
+
"""获取人民币贷款数据.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
98
|
+
start_date: 信息起始时间 (YYYY-MM-DD).
|
|
99
|
+
end_date: 信息结束时间 (YYYY-MM-DD).
|
|
100
|
+
metrics_list: 指标列表, 例如 ``["y.rmb_l.t"]``.
|
|
101
|
+
limit: 返回最近数据的数量.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
DataFrame 包含人民币贷款数据.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
获取大陆人民币贷款年度累积值::
|
|
108
|
+
|
|
109
|
+
from lixinger import get_rmb_loans
|
|
110
|
+
|
|
111
|
+
df = await get_rmb_loans(
|
|
112
|
+
area_code="cn",
|
|
113
|
+
start_date="2023-06-13",
|
|
114
|
+
end_date="2026-06-13",
|
|
115
|
+
metrics_list=["y.rmb_l.t"],
|
|
116
|
+
)
|
|
117
|
+
print(df)
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
return await _api_instance.get_rmb_loans(
|
|
121
|
+
area_code=area_code,
|
|
122
|
+
start_date=start_date,
|
|
123
|
+
end_date=end_date,
|
|
124
|
+
metrics_list=metrics_list,
|
|
125
|
+
limit=limit,
|
|
126
|
+
)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""社会融资 API."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from lixinger.api.base import BaseAPI
|
|
8
|
+
from lixinger.models.macro.social_financing import SocialFinancing
|
|
9
|
+
from lixinger.utils.api import api
|
|
10
|
+
from lixinger.utils.dataframe import get_response_df
|
|
11
|
+
from lixinger.utils.dict import flatten_dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SocialFinancingAPI(BaseAPI):
|
|
15
|
+
"""社会融资 API."""
|
|
16
|
+
|
|
17
|
+
async def get_social_financing(
|
|
18
|
+
self,
|
|
19
|
+
area_code: str,
|
|
20
|
+
start_date: str,
|
|
21
|
+
end_date: str,
|
|
22
|
+
metrics_list: list[str],
|
|
23
|
+
limit: int | None = None,
|
|
24
|
+
) -> pd.DataFrame:
|
|
25
|
+
"""获取社会融资规模数据.
|
|
26
|
+
|
|
27
|
+
获取大陆社会融资规模数据, 包括社会融资、人民币贷款、外币贷款、委托贷款、
|
|
28
|
+
信托贷款、未贴现银行承兑汇票、企业债券、政府债券、非金融企业境内股票、
|
|
29
|
+
存款类金融机构资产支持证券、贷款核销等, 支持存量及月度新增以及同比环比等.
|
|
30
|
+
当用户想了解实体经济融资状况或信贷结构时, 调用此接口.
|
|
31
|
+
|
|
32
|
+
API Endpoint: /macro/social-financing
|
|
33
|
+
API Method: POST
|
|
34
|
+
API Doc: https://www.lixinger.com/open/api/doc?api-key=macro/social-financing
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
38
|
+
start_date: 信息起始时间 (YYYY-MM-DD, 北京时间), 开始和结束的时间间隔不超过10年.
|
|
39
|
+
end_date: 信息结束时间 (YYYY-MM-DD, 北京时间).
|
|
40
|
+
metrics_list: 指标列表, 格式为
|
|
41
|
+
``[granularity].[metricsName].[expressionCalculateType]``,
|
|
42
|
+
例如 ``["y.sf.t", "m.sf.c_y2y"]``.
|
|
43
|
+
limit: 返回最近数据的数量.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
DataFrame 包含以下列:
|
|
47
|
+
|
|
48
|
+
- date: 数据时间
|
|
49
|
+
- 各请求指标 (点号分隔, 例如 ``y.sf.t``)
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
获取大陆社会融资年度累积值::
|
|
53
|
+
|
|
54
|
+
from lixinger import AsyncLixingerClient
|
|
55
|
+
|
|
56
|
+
async with AsyncLixingerClient() as client:
|
|
57
|
+
df = await client.macro.social_financing.get_social_financing(
|
|
58
|
+
area_code="cn",
|
|
59
|
+
start_date="2023-06-13",
|
|
60
|
+
end_date="2026-06-13",
|
|
61
|
+
metrics_list=["y.sf.t"],
|
|
62
|
+
)
|
|
63
|
+
print(df)
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
payload: dict[str, Any] = {
|
|
67
|
+
"areaCode": area_code,
|
|
68
|
+
"startDate": start_date,
|
|
69
|
+
"endDate": end_date,
|
|
70
|
+
"metricsList": metrics_list,
|
|
71
|
+
}
|
|
72
|
+
if limit is not None:
|
|
73
|
+
payload["limit"] = limit
|
|
74
|
+
|
|
75
|
+
data = await self._request("POST", "/macro/social-financing", json=payload)
|
|
76
|
+
# Flatten nested metrics (e.g. {"y": {"sf": {"t": ...}}})
|
|
77
|
+
flattened_data = [flatten_dict(item) for item in data]
|
|
78
|
+
return get_response_df(flattened_data, SocialFinancing)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Functional API instance
|
|
82
|
+
_api_instance = SocialFinancingAPI()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@api
|
|
86
|
+
async def get_social_financing(
|
|
87
|
+
area_code: str,
|
|
88
|
+
start_date: str,
|
|
89
|
+
end_date: str,
|
|
90
|
+
metrics_list: list[str],
|
|
91
|
+
limit: int | None = None,
|
|
92
|
+
) -> pd.DataFrame:
|
|
93
|
+
"""获取社会融资规模数据.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
area_code: 地区代码, 目前仅支持 ``cn``.
|
|
97
|
+
start_date: 信息起始时间 (YYYY-MM-DD).
|
|
98
|
+
end_date: 信息结束时间 (YYYY-MM-DD).
|
|
99
|
+
metrics_list: 指标列表, 例如 ``["y.sf.t"]``.
|
|
100
|
+
limit: 返回最近数据的数量.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
DataFrame 包含社会融资规模数据.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
获取大陆社会融资年度累积值::
|
|
107
|
+
|
|
108
|
+
from lixinger import get_social_financing
|
|
109
|
+
|
|
110
|
+
df = await get_social_financing(
|
|
111
|
+
area_code="cn",
|
|
112
|
+
start_date="2023-06-13",
|
|
113
|
+
end_date="2026-06-13",
|
|
114
|
+
metrics_list=["y.sf.t"],
|
|
115
|
+
)
|
|
116
|
+
print(df)
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
return await _api_instance.get_social_financing(
|
|
120
|
+
area_code=area_code,
|
|
121
|
+
start_date=start_date,
|
|
122
|
+
end_date=end_date,
|
|
123
|
+
metrics_list=metrics_list,
|
|
124
|
+
limit=limit,
|
|
125
|
+
)
|
lixinger/client.py
CHANGED
|
@@ -50,6 +50,13 @@ from lixinger.api.cn.industry import (
|
|
|
50
50
|
IndustrySwMarginTradingAPI,
|
|
51
51
|
)
|
|
52
52
|
from lixinger.api.cn.industry.namespace import IndustryNamespace
|
|
53
|
+
from lixinger.api.macro import (
|
|
54
|
+
NationalDebtAPI,
|
|
55
|
+
RmbDepositsAPI,
|
|
56
|
+
RmbLoansAPI,
|
|
57
|
+
SocialFinancingAPI,
|
|
58
|
+
)
|
|
59
|
+
from lixinger.api.macro.namespace import MacroNamespace
|
|
53
60
|
from lixinger.config import get_config_from_env
|
|
54
61
|
from lixinger.utils.rate_limiter import AsyncRateLimiter
|
|
55
62
|
|
|
@@ -249,6 +256,26 @@ class AsyncLixingerClient:
|
|
|
249
256
|
self.cn_industry_margin_trading_sw_2021 = cn_industry_margin_trading_sw_2021
|
|
250
257
|
self.cn_industry_margin_trading_cni = cn_industry_margin_trading_cni
|
|
251
258
|
|
|
259
|
+
# Macro APIs
|
|
260
|
+
macro_national_debt = NationalDebtAPI(self._http_client, self.config, self._rate_limiter)
|
|
261
|
+
macro_rmb_deposits = RmbDepositsAPI(self._http_client, self.config, self._rate_limiter)
|
|
262
|
+
macro_rmb_loans = RmbLoansAPI(self._http_client, self.config, self._rate_limiter)
|
|
263
|
+
macro_social_financing = SocialFinancingAPI(self._http_client, self.config, self._rate_limiter)
|
|
264
|
+
|
|
265
|
+
# Macro namespace (groups all macro APIs)
|
|
266
|
+
self.macro = MacroNamespace(
|
|
267
|
+
national_debt=macro_national_debt,
|
|
268
|
+
rmb_deposits=macro_rmb_deposits,
|
|
269
|
+
rmb_loans=macro_rmb_loans,
|
|
270
|
+
social_financing=macro_social_financing,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Legacy flat access for macro APIs (deprecated, for backward compatibility)
|
|
274
|
+
self.macro_national_debt = macro_national_debt
|
|
275
|
+
self.macro_rmb_deposits = macro_rmb_deposits
|
|
276
|
+
self.macro_rmb_loans = macro_rmb_loans
|
|
277
|
+
self.macro_social_financing = macro_social_financing
|
|
278
|
+
|
|
252
279
|
# Fund APIs
|
|
253
280
|
self.cn_fund = FundAPI(self._http_client, self.config, self._rate_limiter)
|
|
254
281
|
self.cn_fund_profile = FundProfileAPI(self._http_client, self.config, self._rate_limiter)
|
lixinger/exceptions.py
CHANGED
|
@@ -11,7 +11,23 @@ class APIError(LixingerError):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class RateLimitError(APIError):
|
|
14
|
-
"""Rate limit exceeded (429).
|
|
14
|
+
"""Rate limit exceeded (429).
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
retry_after: Optional server-provided delay in seconds parsed from the
|
|
18
|
+
``Retry-After`` response header. ``None`` when the header was absent
|
|
19
|
+
or malformed. The retry decorator honors this value in place of the
|
|
20
|
+
default exponential backoff.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
message: str,
|
|
26
|
+
status_code: int | None = None,
|
|
27
|
+
retry_after: float | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
super().__init__(message, status_code=status_code)
|
|
30
|
+
self.retry_after = retry_after
|
|
15
31
|
|
|
16
32
|
|
|
17
33
|
class AuthenticationError(APIError):
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Macro models."""
|
|
2
|
+
|
|
3
|
+
from lixinger.models.macro.national_debt import NationalDebt
|
|
4
|
+
from lixinger.models.macro.rmb_deposits import RmbDeposits
|
|
5
|
+
from lixinger.models.macro.rmb_loans import RmbLoans
|
|
6
|
+
from lixinger.models.macro.social_financing import SocialFinancing
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"NationalDebt",
|
|
10
|
+
"RmbDeposits",
|
|
11
|
+
"RmbLoans",
|
|
12
|
+
"SocialFinancing",
|
|
13
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""国债数据模型."""
|
|
2
|
+
|
|
3
|
+
import pandera.pandas as pa
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NationalDebt(pa.DataFrameModel):
|
|
7
|
+
"""国债收益率数据模型.
|
|
8
|
+
|
|
9
|
+
表示某个地区(cn / us)某天各期限的国债收益率. 具体收益率字段(如 ``tcm_m3``,
|
|
10
|
+
``tcm_y10`` 等)随请求 ``metrics_list`` 变化, 以 extra columns 形式出现.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
class Config:
|
|
14
|
+
"""Pandera configuration."""
|
|
15
|
+
|
|
16
|
+
coerce = True
|
|
17
|
+
strict = False # Allow extra metrics columns (tcm_m3, tcm_y10, ...)
|
|
18
|
+
|
|
19
|
+
date: pa.typing.Series[pa.typing.DateTime]
|
|
20
|
+
area_code: pa.typing.Series[str]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""人民币存款数据模型."""
|
|
2
|
+
|
|
3
|
+
import pandera.pandas as pa
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RmbDeposits(pa.DataFrameModel):
|
|
7
|
+
"""人民币存款数据模型.
|
|
8
|
+
|
|
9
|
+
表示某个地区某个时点的人民币存款数据. 指标以 flatten 后的
|
|
10
|
+
``granularity.metricsName.expressionCalculateType`` (例如 ``y.rmb_d.t``)
|
|
11
|
+
形式出现在 extra columns 中, 由请求 ``metrics_list`` 决定.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
"""Pandera configuration."""
|
|
16
|
+
|
|
17
|
+
coerce = True
|
|
18
|
+
strict = False # Allow extra metrics columns like y.rmb_d.t
|
|
19
|
+
|
|
20
|
+
date: pa.typing.Series[pa.typing.DateTime]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""人民币贷款数据模型."""
|
|
2
|
+
|
|
3
|
+
import pandera.pandas as pa
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RmbLoans(pa.DataFrameModel):
|
|
7
|
+
"""人民币贷款数据模型.
|
|
8
|
+
|
|
9
|
+
表示某个地区某个时点的人民币贷款数据. 指标以 flatten 后的
|
|
10
|
+
``granularity.metricsName.expressionCalculateType`` (例如 ``y.rmb_l.t``)
|
|
11
|
+
形式出现在 extra columns 中, 由请求 ``metrics_list`` 决定.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
"""Pandera configuration."""
|
|
16
|
+
|
|
17
|
+
coerce = True
|
|
18
|
+
strict = False # Allow extra metrics columns like y.rmb_l.t
|
|
19
|
+
|
|
20
|
+
date: pa.typing.Series[pa.typing.DateTime]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""社会融资规模数据模型."""
|
|
2
|
+
|
|
3
|
+
import pandera.pandas as pa
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SocialFinancing(pa.DataFrameModel):
|
|
7
|
+
"""社会融资规模数据模型.
|
|
8
|
+
|
|
9
|
+
表示某个地区某个时点的社会融资规模数据. 指标以 flatten 后的
|
|
10
|
+
``granularity.metricsName.expressionCalculateType`` (例如 ``y.sf.t``)
|
|
11
|
+
形式出现在 extra columns 中, 由请求 ``metrics_list`` 决定.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
"""Pandera configuration."""
|
|
16
|
+
|
|
17
|
+
coerce = True
|
|
18
|
+
strict = False # Allow extra metrics columns like y.sf.t
|
|
19
|
+
|
|
20
|
+
date: pa.typing.Series[pa.typing.DateTime]
|
lixinger/utils/retry.py
CHANGED
|
@@ -10,8 +10,22 @@ def async_retry_on_failure(
|
|
|
10
10
|
max_retries: int = 3,
|
|
11
11
|
backoff: float = 1.0,
|
|
12
12
|
retry_on: tuple[type[Exception], ...] = (Exception,),
|
|
13
|
+
no_retry_on: tuple[type[Exception], ...] = (),
|
|
13
14
|
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
|
|
14
|
-
"""Retry failed async requests with exponential backoff.
|
|
15
|
+
"""Retry failed async requests with exponential backoff.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
max_retries: Maximum number of retry attempts (total attempts = max_retries + 1).
|
|
19
|
+
backoff: Base delay in seconds for exponential backoff (2**attempt * backoff).
|
|
20
|
+
retry_on: Exception types that trigger a retry.
|
|
21
|
+
no_retry_on: Exception types that must NOT be retried even if matched by
|
|
22
|
+
``retry_on``. Use this to short-circuit on non-transient errors such
|
|
23
|
+
as authentication failures.
|
|
24
|
+
|
|
25
|
+
If the caught exception exposes a ``retry_after`` attribute (float seconds,
|
|
26
|
+
typically parsed from a ``Retry-After`` HTTP header on 429 responses), that
|
|
27
|
+
value is used for the next sleep instead of the exponential backoff.
|
|
28
|
+
"""
|
|
15
29
|
|
|
16
30
|
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
|
|
17
31
|
@wraps(func)
|
|
@@ -20,9 +34,16 @@ def async_retry_on_failure(
|
|
|
20
34
|
try:
|
|
21
35
|
return await func(*args, **kwargs)
|
|
22
36
|
except retry_on as e:
|
|
37
|
+
# Bail out immediately on non-transient errors.
|
|
38
|
+
if no_retry_on and isinstance(e, no_retry_on):
|
|
39
|
+
raise
|
|
23
40
|
if attempt == max_retries:
|
|
24
41
|
raise
|
|
25
|
-
|
|
42
|
+
# Prefer server-provided retry hint when available.
|
|
43
|
+
hint = getattr(e, "retry_after", None)
|
|
44
|
+
wait_time = (
|
|
45
|
+
float(hint) if isinstance(hint, int | float) and hint > 0 else backoff * (2**attempt)
|
|
46
|
+
)
|
|
26
47
|
await asyncio.sleep(wait_time)
|
|
27
48
|
raise RuntimeError("Unreachable")
|
|
28
49
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
lixinger/__init__.py,sha256=
|
|
2
|
-
lixinger/client.py,sha256=
|
|
1
|
+
lixinger/__init__.py,sha256=VyNwJhDpm-NnDSskdx6bCHmG8iYcPSzX71HAJDtstxo,3290
|
|
2
|
+
lixinger/client.py,sha256=EZ6hbSyWSAL_kODJVDd3j6hUCGr57GiCQSvdu1U6GzM,15457
|
|
3
3
|
lixinger/config.py,sha256=JPz8EOrf1kP4Do-Z5-MsnOM0pMrNE1ZsP-Qoarh9y4c,2008
|
|
4
|
-
lixinger/exceptions.py,sha256=
|
|
4
|
+
lixinger/exceptions.py,sha256=Gkv1z-LC09paYGVs430-gDgDs43dLyRHbglB3Frehd4,1069
|
|
5
5
|
lixinger/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
lixinger/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
lixinger/api/base.py,sha256=
|
|
7
|
+
lixinger/api/base.py,sha256=oMXwauTncyd_anFS_2A642lH7NjUsp6uGtntVY4KoeE,4684
|
|
8
8
|
lixinger/api/cn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
lixinger/api/cn/company/__init__.py,sha256=K8aNp2gGj4iQOvc8KjXA5668QUAhwyTfMBPhP3tgtMg,961
|
|
10
10
|
lixinger/api/cn/company/announcement.py,sha256=lYt4AWOV5b9OTKfvJ7ZOqWP61pcPcyIQzb5N2DqFT6k,1920
|
|
@@ -56,6 +56,12 @@ lixinger/api/cn/industry/margin_trading/__init__.py,sha256=4JZDCF6v2TPg9uyBLKXAf
|
|
|
56
56
|
lixinger/api/cn/industry/margin_trading/cni.py,sha256=7IqidE2N2_INiVybeiTt5qFRUY4PJsY6nUPEGtdWJlM,4600
|
|
57
57
|
lixinger/api/cn/industry/margin_trading/sw.py,sha256=hjC5Uf7XXSwiiqoloIWFx5nDzbxd_D5bB1QPQCEjUjg,4591
|
|
58
58
|
lixinger/api/cn/industry/margin_trading/sw_2021.py,sha256=uZWAiIdPPLWnvYduAULPythgzgNzLFeVkqP5OEMq_vw,4706
|
|
59
|
+
lixinger/api/macro/__init__.py,sha256=Y7df3JkoF3YrvN9FSRxHyLUzaL0o5DY7n1WQp-q6BBE,871
|
|
60
|
+
lixinger/api/macro/namespace.py,sha256=YHsbOp5k6924KFfv4kOLE9Qge7kM-olId6JFd6VydCQ,2192
|
|
61
|
+
lixinger/api/macro/national_debt.py,sha256=Nt7EnJ6Mks2oQbpDznr3ejLl4zg-INRhuDoKsDvuY2A,3834
|
|
62
|
+
lixinger/api/macro/rmb_deposits.py,sha256=BOfQhSi9qPZy1qhVNDanasuwwD2Gz3GsU8RZuoGbIx4,4134
|
|
63
|
+
lixinger/api/macro/rmb_loans.py,sha256=ne1gmtxqR6jF0qI4W0U99NPf0ds3-O7DBzfo9Vf2grE,4152
|
|
64
|
+
lixinger/api/macro/social_financing.py,sha256=QGYxqqsEfv-e3Zi5NJmOHa8k7Ft3m-3w-zaKi-rgZyg,4111
|
|
59
65
|
lixinger/models/__init__.py,sha256=OAUYpI_JaGZExFqZ0H6l8fJ5qyLW0oFnnO4JGJQknTE,154
|
|
60
66
|
lixinger/models/cn/__init__.py,sha256=mhTq_PfPJ_0720E1rTKdqBTpb9A14_ZDDWLfJXjcVck,27
|
|
61
67
|
lixinger/models/cn/company/__init__.py,sha256=7E7p1NED5qr0yf_wcmOq_DDvOq2hzXv3bD4cLjHTtSQ,957
|
|
@@ -104,13 +110,18 @@ lixinger/models/cn/industry/margin_trading/__init__.py,sha256=JHzyvqzawYkgHa3HPN
|
|
|
104
110
|
lixinger/models/cn/industry/margin_trading/cni.py,sha256=D8O-SXFR1_6lboRzMMNxaLA3KnSlHHeS1N5kjUIUPrs,1375
|
|
105
111
|
lixinger/models/cn/industry/margin_trading/sw.py,sha256=GyBhpZxSm1aoQHdzJvjb5INp_lrTFh4Alp2WaDyPhVM,1374
|
|
106
112
|
lixinger/models/cn/industry/margin_trading/sw_2021.py,sha256=JWdEVXarlKso3JOpMJ7zvkw-I5tsLlXagJFXEJxYC3s,1399
|
|
113
|
+
lixinger/models/macro/__init__.py,sha256=MSuDJcTT6NLGa1SUCe6eQ2pIHa6feJp6YP98G1x9XIg,354
|
|
114
|
+
lixinger/models/macro/national_debt.py,sha256=EVOCgzoahjKW0YMq-08ICw17GpOmhJdBWQGqmO0oqQc,581
|
|
115
|
+
lixinger/models/macro/rmb_deposits.py,sha256=SeJoD0t3xHjNuSUX6BCUi343wQCnRgOs93BIW9bE9ss,583
|
|
116
|
+
lixinger/models/macro/rmb_loans.py,sha256=Mptmogd_R2e8TGYkJ36QMtIyLzxmJJdKxt7eJryX3nQ,580
|
|
117
|
+
lixinger/models/macro/social_financing.py,sha256=OIV1v_D0eW2y8wvVwG5lV8AspYv6QyxrQJlTUskQUa0,590
|
|
107
118
|
lixinger/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
119
|
lixinger/utils/api.py,sha256=BktR40JtKCMe9excFWo4ujsq3GiQK4ypJLG3MpJgo2s,402
|
|
109
120
|
lixinger/utils/dataframe.py,sha256=tYBrNdmJ4poyuwD-9XgzHt3A_A8bBo0cdHiu8Yy9e9A,2208
|
|
110
121
|
lixinger/utils/dict.py,sha256=yvbUtv8QpRmy0d_o_7gCuEwwiEfBji5_xB490ANxilw,589
|
|
111
122
|
lixinger/utils/rate_limiter.py,sha256=ZHlRTTL60L62uRA_f--LD26qkAfk-pHBllRqPapAXQM,1141
|
|
112
|
-
lixinger/utils/retry.py,sha256=
|
|
113
|
-
lixinger_python-0.3.
|
|
114
|
-
lixinger_python-0.3.
|
|
115
|
-
lixinger_python-0.3.
|
|
116
|
-
lixinger_python-0.3.
|
|
123
|
+
lixinger/utils/retry.py,sha256=erwB-wc9PA85vEoJD7yQFZTipDTFgsABD4QEOG7c_VM,2169
|
|
124
|
+
lixinger_python-0.3.13.dist-info/METADATA,sha256=5i65s2BVbG3ArH0Yzrf_95Uiue20D4Y2z5u8SHVdQIk,9207
|
|
125
|
+
lixinger_python-0.3.13.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
126
|
+
lixinger_python-0.3.13.dist-info/licenses/LICENSE,sha256=5oOwRq1lHSOScbNGCHr2feuNnhNYdGcArj6fSUfsC5U,1064
|
|
127
|
+
lixinger_python-0.3.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|