lixinger-python 0.3.12__tar.gz → 0.3.14__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.
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.claude/settings.local.json +6 -1
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/CHANGELOG.md +38 -1
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/PKG-INFO +1 -1
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/base.py +68 -5
- lixinger_python-0.3.14/lixinger/exceptions.py +38 -0
- lixinger_python-0.3.14/lixinger/utils/retry.py +63 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/pyproject.toml +1 -1
- lixinger_python-0.3.14/tests/test_no_data.py +126 -0
- lixinger_python-0.3.14/tests/test_retry.py +349 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/uv.lock +1 -1
- lixinger_python-0.3.12/lixinger/exceptions.py +0 -22
- lixinger_python-0.3.12/lixinger/utils/retry.py +0 -31
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.env.example +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.github/copilot-instructions.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.github/workflows/README.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.github/workflows/pr-checks.yml +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.gitignore +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.pre-commit-config.yaml +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.python-version +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/.ruff.toml +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/ADD_NEW_API_QUICK.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/ARCHITECTURE.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/CLAUDE.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/DOCUMENTATION.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/LICENSE +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/MANIFEST.in +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/PUBLISHING.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/README.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/codecov.yml +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/client.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/announcement.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/company.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/dividend.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/equity_change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fundamental/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fundamental/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fundamental/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fundamental/other_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/fundamental/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/indices.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/company/profile.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/announcement.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/asset_combination.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/asset_industry_combination.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/profile.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/fund/shareholdings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/constituent_weightings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/constituents.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/drawdown.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/fundamental.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/index/tracking_fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/api/overview.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/development/code-style.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/development/contributing.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/development/testing.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/examples/company.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/examples/fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/examples/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/getting-started/configuration.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/getting-started/installation.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/getting-started/quickstart.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/docs/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/company_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/company_profile_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/dividend_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/fs_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/index_constituent_weightings_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/index_constituents_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/index_info_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/examples/index_margin_trading_example.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/company.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/dividend.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/equity_change.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/insurance.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/other_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fs/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/insurance.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/other_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/fundamental/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/indices.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/namespace.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/company/profile.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/asset_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/asset_industry_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/fund.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/profile.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/fund/shareholdings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/constituent_weightings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/constituents.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/drawdown.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fs/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fs/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fs/hybrid.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fs/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fs/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/fundamental.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/index.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/margin_trading.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/namespace.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/index/tracking_fund.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/margin_trading/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/margin_trading/cni.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/margin_trading/sw.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/margin_trading/sw_2021.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/cn/industry/namespace.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/namespace.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/national_debt.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/rmb_deposits.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/rmb_loans.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/api/macro/social_financing.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/client.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/config.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/company.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/dividend.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/equity_change.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/insurance.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/other_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fs/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/insurance.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/other_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/fundamental/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/company/indices.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/asset_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/asset_industry_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/fund.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/profile.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/fund/shareholdings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/constituent_weightings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/constituents.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/drawdown.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fs/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fs/bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fs/hybrid.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fs/non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fs/security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/fundamental.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/index.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/margin_trading.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/index/tracking_fund.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/industry/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/industry/margin_trading/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/industry/margin_trading/cni.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/industry/margin_trading/sw.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/cn/industry/margin_trading/sw_2021.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/macro/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/macro/national_debt.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/macro/rmb_deposits.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/macro/rmb_loans.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/models/macro/social_financing.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/py.typed +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/utils/__init__.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/utils/api.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/utils/dataframe.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/utils/dict.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger/utils/rate_limiter.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/REMINDER.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/allotment.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/announcement.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/block-deal.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/customers.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/dividend.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/equity-change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fs/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fs/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fs/other_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fs/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fund-collection-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fund-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fundamental/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fundamental/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fundamental/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fundamental/other_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/fundamental/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/capita.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/df.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/elr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/esc.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/mm_ha.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/mssc.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/mtasl.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/npd.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/ple.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/shnc.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/t_a.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/tr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/hot/tr_dri.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/indices.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/industries.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/inquiry.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/major-shareholders-shares-change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/majority-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/margin-trading-and-securities-lending.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/measures.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/mutual-market.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/nolimit-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/operating-data.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/operation-revenue-constitution.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/pledge.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/profile.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/senior-executive-shares-change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/shareholders-num.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/suppliers.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/trading-abnormal.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/company.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/announcement.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/asset-combination.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/asset-industry-combination.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/dividend.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/drawdown.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/fees.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/f_as.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/f_nlacan.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/fet_s.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/ff.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/fp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/fpr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/hot/fss.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/manager.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/net-value-of-dividend-reinvestment.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/net-value.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/profile.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/shareholders-structure.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/shareholdings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/shares.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/split.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/total-net-value.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/turnover-rate.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/asset-scale.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/fund-list.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/fund-manager-list.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/hot/fc_as.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/hot/fc_asr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company/shareholdings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-company.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager/hot/fmi.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager/hot/fmp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager/management-funds.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager/profit-ratio.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager/shareholdings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund-manager.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/constituent-weightings.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/constituents.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/drawdown.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/fs/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/fs/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/fs/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/fundamental.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/cp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/ic.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/ifet_sni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/mm_ha.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/mtasl.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/tr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/hot/tr_cp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/margin-trading-and-securities-lending.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/mutual-market.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/tracking-fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/constituents/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/constituents/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/constituents/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/cni/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/cni/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/cni/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/cni/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/cni/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw_2021/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw_2021/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw_2021/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw_2021/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fs/sw_2021/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fundamental/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fundamental/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/fundamental/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mm_ha/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mm_ha/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mm_ha/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mtasl/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mtasl/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/hot/mtasl/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/margin-trading-and-securities-lending/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/margin-trading-and-securities-lending/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/margin-trading-and-securities-lending/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/mutual-market/cni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/mutual-market/sw.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry/mutual-market/sw_2021.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/cn/industry.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/allotment.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/announcement.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/dividend.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/employee.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/equity-change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/other_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/reit.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fs/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fund-collection-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fund-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/bank.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/insurance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/other_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/reit.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/fundamental/security.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/capita.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/director_equity_change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/mm_ah.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/npd.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/rep.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/ss.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/ss_ha.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/tr.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/hot/tr_dri.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/indices.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/industries.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/latest-shareholders.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/mutual-market.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/operation-revenue-constitution.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/profile.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/repurchase.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/shareholders-equity-change.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/short-selling.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/split.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/company.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/constituents.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/drawdown.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/fs/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/fundamental.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/hot/cp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/hot/ic.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/hot/ifet_sni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/hot/mm_ah.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/mutual-market.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/tracking-fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/constituents/hsi.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/fs/hsi/hybrid.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/fs/hsi/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/fundamental/hsi.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/hot/mm_ah/hsi.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry/mutual-market/hsi.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/hk/industry.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/bop.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/central-bank-balance-sheet.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/credit-securities-account.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/crude-oil.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/currency-exchange-rate.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/domestic-debt-securities.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/domestic-trade.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/energy.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/foreign-assets.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/foreign-trade.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/gdp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/gold-price.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/industrialization.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/interest-rates.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/investment-in-fixed-assets.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/investor.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/leverage-ratio.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/money-supply.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/national-debt.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/national-finance.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/natural-gas.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/non-ferrous-metals.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/official-reserve-assets.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/platinum-price.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/population.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/price-index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/real-estate.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/required-reserves.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/rmb-deposits.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/rmb-loans.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/rmbidx.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/silver-price.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/social-financing.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/stamp-duty.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/traffic-transportation.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/unemployment-rate.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/usdx.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/macro/vix-fear-index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/candlestick.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/constituents.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/drawdown.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/fs/non_financial.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/fundamental.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/hot/cp.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/hot/ifet_sni.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/tracking-fund.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index/volatility.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/lixinger-api/us/index.md +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/mkdocs.yml +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/mypy.ini +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/scripts/explore_api.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/scripts/generate_docs.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/scripts/publish.sh +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/api/cn/company/test_announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/api/cn/company/test_dividend.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/api/cn/index/test_constituents.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/conftest.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_bank_statements.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_client.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_cn_index_candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_cn_index_constituent_weightings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_company.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_company_profile.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_equity_change.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_announcement.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_asset_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_asset_industry_combination.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_candlestick.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_profile.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fund_shareholdings.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fundamental.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fundamental_bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_fundamental_security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_constituents.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_drawdown.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_fs_bank.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_fs_hybrid.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_fs_non_financial.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_fs_security.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_fundamental.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_index_margin_trading.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_indices.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_industry_margin_trading.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_insurance_statements.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_integration.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_macro.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_non_financial_statements.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_other_financial_statements.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_security_statements.py +0 -0
- {lixinger_python-0.3.12 → lixinger_python-0.3.14}/tests/test_tracking_fund.py +0 -0
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"Bash(git push *)",
|
|
13
13
|
"Bash(uv sync *)",
|
|
14
14
|
"Bash(curl -s https://pypi.org/pypi/lixinger-python/0.3.11/json)",
|
|
15
|
-
"Bash(python3 -c \"import sys, json; d = json.load\\(sys.stdin\\); print\\('Name:', d['info']['name']\\); print\\('Version:', d['info']['version']\\); print\\('Files:', len\\(d['urls']\\)\\)\")"
|
|
15
|
+
"Bash(python3 -c \"import sys, json; d = json.load\\(sys.stdin\\); print\\('Name:', d['info']['name']\\); print\\('Version:', d['info']['version']\\); print\\('Files:', len\\(d['urls']\\)\\)\")",
|
|
16
|
+
"Bash(curl -s https://pypi.org/pypi/lixinger-python/0.3.12/json)",
|
|
17
|
+
"Bash(curl -s https://pypi.org/pypi/lixinger-python/0.3.13/json)",
|
|
18
|
+
"Bash(git diff *)",
|
|
19
|
+
"Read(//tmp/**)",
|
|
20
|
+
"Bash(git checkout *)"
|
|
16
21
|
]
|
|
17
22
|
}
|
|
18
23
|
}
|
|
@@ -11,6 +11,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
- Add support for US stock market APIs
|
|
12
12
|
- Add caching mechanism for API responses
|
|
13
13
|
|
|
14
|
+
## [0.3.14] - 2026-07-05
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `code=1, message="no data"` 响应不再被误报为错误. 之前会抛
|
|
18
|
+
`APIError("API returned error code 1: no data")`, 现在正确返回空 payload,
|
|
19
|
+
下游得到空 `DataFrame`. Lixinger 在非交易日、空日期范围、停牌股票等
|
|
20
|
+
场景会返回该响应.
|
|
21
|
+
- 服务端未返回 `Retry-After` header 的 429 响应现在退避 15 秒 (可通过
|
|
22
|
+
`DEFAULT_RATE_LIMIT_RETRY_AFTER` 观察), 而不是之前的 1/2/4 秒指数退避.
|
|
23
|
+
Lixinger 的速率窗口是 60 秒, 1 秒后重试几乎必然再 429, 白白浪费重试配额.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- `_request` 的成功判断从 `code == 1 AND message == "success"` 放宽为
|
|
27
|
+
`code == 1`. message 视为人类可读的描述.
|
|
28
|
+
- `_request` 会把 `data` 字段的 `None` / 缺失情况归一到 `[]`, 避免下游
|
|
29
|
+
20+ 个遍历 `data` 的 API 方法崩在 `for item in None`.
|
|
30
|
+
- `async_retry_on_failure` 新增 `jitter: float = 0.2` 参数, 每次 sleep
|
|
31
|
+
会在 `[base*(1-jitter), base*(1+jitter)]` 之间均匀采样, 避免多客户端
|
|
32
|
+
惊群. 传 `jitter=0` 关闭 (方便测试和确定性场景).
|
|
33
|
+
|
|
34
|
+
## [0.3.13] - 2026-07-05
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- `AuthenticationError` (401) 和 `ValidationError` 不再被重试. 之前会被
|
|
38
|
+
重试装饰器兜底 catch 3 次, 白白浪费 ~7 秒和 3 次配额.
|
|
39
|
+
- 429 响应现在会尊重服务端返回的 `Retry-After` header, 按 header 指定的
|
|
40
|
+
时长退避(同时支持 RFC 7231 的 `delta-seconds` 和 `HTTP-date` 两种格式);
|
|
41
|
+
header 缺失时保持原有的 1/2/4 秒指数退避.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- `async_retry_on_failure` 新增 `no_retry_on: tuple[type[Exception], ...]`
|
|
45
|
+
参数, 命中即立即抛出.
|
|
46
|
+
- `RateLimitError` 新增 `retry_after: float | None` 属性; 若异常对象带
|
|
47
|
+
`retry_after` 属性, 重试装饰器会用它替代默认的指数退避.
|
|
48
|
+
|
|
14
49
|
## [0.3.12] - 2026-07-05
|
|
15
50
|
|
|
16
51
|
### Added
|
|
@@ -186,7 +221,9 @@ asyncio.run(main())
|
|
|
186
221
|
- Pre-commit hooks
|
|
187
222
|
- MkDocs for documentation
|
|
188
223
|
|
|
189
|
-
[unreleased]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.
|
|
224
|
+
[unreleased]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.14...HEAD
|
|
225
|
+
[0.3.14]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.13...v0.3.14
|
|
226
|
+
[0.3.13]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.12...v0.3.13
|
|
190
227
|
[0.3.12]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.11...v0.3.12
|
|
191
228
|
[0.3.11]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.10...v0.3.11
|
|
192
229
|
[0.3.10]: https://github.com/TedaLIEz/lixinger-python/compare/v0.3.9...v0.3.10
|
|
@@ -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
|
|
|
@@ -13,6 +15,12 @@ _global_rate_limiter = AsyncRateLimiter(max_requests=1000)
|
|
|
13
15
|
# Lazy initialization of global http client to avoid issues with proxy/config changes
|
|
14
16
|
_global_http_client: httpx.AsyncClient | None = None
|
|
15
17
|
|
|
18
|
+
# Fallback delay in seconds when a 429 response omits Retry-After. The Lixinger
|
|
19
|
+
# rate-limit window is 60s, so short exponential backoff (1/2/4s) almost always
|
|
20
|
+
# fires inside the same window and gets 429ed again. 15s gives the window room
|
|
21
|
+
# to age and burns fewer retry attempts on doomed requests.
|
|
22
|
+
DEFAULT_RATE_LIMIT_RETRY_AFTER = 15.0
|
|
23
|
+
|
|
16
24
|
|
|
17
25
|
def get_global_client() -> httpx.AsyncClient:
|
|
18
26
|
"""Get or create the global HTTP client."""
|
|
@@ -29,6 +37,39 @@ def get_global_client() -> httpx.AsyncClient:
|
|
|
29
37
|
return _global_http_client
|
|
30
38
|
|
|
31
39
|
|
|
40
|
+
def _parse_retry_after(header_value: str | None) -> float | None:
|
|
41
|
+
"""Parse a Retry-After header into seconds.
|
|
42
|
+
|
|
43
|
+
Accepts both formats defined by RFC 7231 §7.1.3:
|
|
44
|
+
- ``delta-seconds`` (e.g. ``"120"``)
|
|
45
|
+
- ``HTTP-date`` (e.g. ``"Wed, 21 Oct 2015 07:28:00 GMT"``)
|
|
46
|
+
|
|
47
|
+
Returns ``None`` when the header is missing or unparseable so the caller
|
|
48
|
+
can fall back to its default backoff.
|
|
49
|
+
"""
|
|
50
|
+
if not header_value:
|
|
51
|
+
return None
|
|
52
|
+
value = header_value.strip()
|
|
53
|
+
# Try delta-seconds first — the common case.
|
|
54
|
+
try:
|
|
55
|
+
seconds = float(value)
|
|
56
|
+
except ValueError:
|
|
57
|
+
pass
|
|
58
|
+
else:
|
|
59
|
+
return seconds if seconds >= 0 else None
|
|
60
|
+
# Fall back to HTTP-date.
|
|
61
|
+
try:
|
|
62
|
+
target = parsedate_to_datetime(value)
|
|
63
|
+
except (TypeError, ValueError):
|
|
64
|
+
return None
|
|
65
|
+
if target.tzinfo is None:
|
|
66
|
+
# RFC 7231 HTTP-date is always GMT, so treat naive dates as UTC.
|
|
67
|
+
target = target.replace(tzinfo=UTC)
|
|
68
|
+
now = datetime.now(tz=target.tzinfo)
|
|
69
|
+
delta = (target - now).total_seconds()
|
|
70
|
+
return max(delta, 0.0)
|
|
71
|
+
|
|
72
|
+
|
|
32
73
|
class BaseAPI:
|
|
33
74
|
"""Base class for API endpoint groups."""
|
|
34
75
|
|
|
@@ -42,7 +83,12 @@ class BaseAPI:
|
|
|
42
83
|
self._config = config or settings
|
|
43
84
|
self._rate_limiter = rate_limiter or _global_rate_limiter
|
|
44
85
|
|
|
45
|
-
@async_retry_on_failure(
|
|
86
|
+
@async_retry_on_failure(
|
|
87
|
+
max_retries=3,
|
|
88
|
+
backoff=1.0,
|
|
89
|
+
# Do NOT retry non-transient errors even though they inherit from Exception.
|
|
90
|
+
no_retry_on=(AuthenticationError, ValidationError),
|
|
91
|
+
)
|
|
46
92
|
async def _request(
|
|
47
93
|
self,
|
|
48
94
|
method: str,
|
|
@@ -72,7 +118,16 @@ class BaseAPI:
|
|
|
72
118
|
)
|
|
73
119
|
|
|
74
120
|
if response.status_code == 429:
|
|
75
|
-
|
|
121
|
+
retry_after = _parse_retry_after(response.headers.get("Retry-After"))
|
|
122
|
+
# Server didn't tell us how long to wait — pick a delay long enough
|
|
123
|
+
# for the 60s rate-limit window to actually age.
|
|
124
|
+
if retry_after is None:
|
|
125
|
+
retry_after = DEFAULT_RATE_LIMIT_RETRY_AFTER
|
|
126
|
+
raise RateLimitError(
|
|
127
|
+
"Rate limit exceeded",
|
|
128
|
+
status_code=429,
|
|
129
|
+
retry_after=retry_after,
|
|
130
|
+
)
|
|
76
131
|
if response.status_code == 401:
|
|
77
132
|
raise AuthenticationError("Authentication failed", status_code=401)
|
|
78
133
|
if response.status_code != 200:
|
|
@@ -88,7 +143,15 @@ class BaseAPI:
|
|
|
88
143
|
code = data.get("code")
|
|
89
144
|
message = data.get("message")
|
|
90
145
|
|
|
91
|
-
|
|
146
|
+
# code is the machine-readable success signal; message is human text.
|
|
147
|
+
# Lixinger returns code=1 for success even when there's no data
|
|
148
|
+
# (e.g. message="no data" on non-trading days or empty ranges) —
|
|
149
|
+
# treat any code=1 as success and normalize a missing/null payload
|
|
150
|
+
# to [] so downstream iteration and get_response_df both work.
|
|
151
|
+
if code != 1:
|
|
92
152
|
raise APIError(f"API returned error code {code}: {message}")
|
|
93
153
|
|
|
94
|
-
|
|
154
|
+
payload = data.get("data")
|
|
155
|
+
if payload is None:
|
|
156
|
+
return []
|
|
157
|
+
return payload
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class LixingerError(Exception):
|
|
2
|
+
"""Base exception for Lixinger SDK."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class APIError(LixingerError):
|
|
6
|
+
"""API request failed."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
9
|
+
super().__init__(message)
|
|
10
|
+
self.status_code = status_code
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RateLimitError(APIError):
|
|
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
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AuthenticationError(APIError):
|
|
34
|
+
"""Authentication failed (401)."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ValidationError(LixingerError):
|
|
38
|
+
"""Request validation failed."""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
from functools import wraps
|
|
4
|
+
import random
|
|
5
|
+
from typing import Any, TypeVar
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def async_retry_on_failure(
|
|
11
|
+
max_retries: int = 3,
|
|
12
|
+
backoff: float = 1.0,
|
|
13
|
+
retry_on: tuple[type[Exception], ...] = (Exception,),
|
|
14
|
+
no_retry_on: tuple[type[Exception], ...] = (),
|
|
15
|
+
jitter: float = 0.2,
|
|
16
|
+
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
|
|
17
|
+
"""Retry failed async requests with exponential backoff and jitter.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
max_retries: Maximum number of retry attempts (total attempts = max_retries + 1).
|
|
21
|
+
backoff: Base delay in seconds for exponential backoff (2**attempt * backoff).
|
|
22
|
+
retry_on: Exception types that trigger a retry.
|
|
23
|
+
no_retry_on: Exception types that must NOT be retried even if matched by
|
|
24
|
+
``retry_on``. Use this to short-circuit on non-transient errors such
|
|
25
|
+
as authentication failures.
|
|
26
|
+
jitter: Multiplicative jitter fraction applied to every sleep. The actual
|
|
27
|
+
sleep is drawn from ``uniform(base * (1 - jitter), base * (1 + jitter))``
|
|
28
|
+
where ``base`` is either the ``retry_after`` hint or the exponential
|
|
29
|
+
backoff. Pass ``0`` to disable (mostly useful in tests). Values are
|
|
30
|
+
clamped so the sleep never goes negative.
|
|
31
|
+
|
|
32
|
+
If the caught exception exposes a ``retry_after`` attribute (float seconds,
|
|
33
|
+
typically parsed from a ``Retry-After`` HTTP header on 429 responses), that
|
|
34
|
+
value is used for the next sleep instead of the exponential backoff.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
|
|
38
|
+
@wraps(func)
|
|
39
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
40
|
+
for attempt in range(max_retries + 1):
|
|
41
|
+
try:
|
|
42
|
+
return await func(*args, **kwargs)
|
|
43
|
+
except retry_on as e:
|
|
44
|
+
# Bail out immediately on non-transient errors.
|
|
45
|
+
if no_retry_on and isinstance(e, no_retry_on):
|
|
46
|
+
raise
|
|
47
|
+
if attempt == max_retries:
|
|
48
|
+
raise
|
|
49
|
+
# Prefer server-provided retry hint when available.
|
|
50
|
+
hint = getattr(e, "retry_after", None)
|
|
51
|
+
base = float(hint) if isinstance(hint, int | float) and hint > 0 else backoff * (2**attempt)
|
|
52
|
+
if jitter > 0:
|
|
53
|
+
low = max(0.0, base * (1.0 - jitter))
|
|
54
|
+
high = base * (1.0 + jitter)
|
|
55
|
+
wait_time = random.uniform(low, high) # noqa: S311 (non-crypto jitter)
|
|
56
|
+
else:
|
|
57
|
+
wait_time = base
|
|
58
|
+
await asyncio.sleep(wait_time)
|
|
59
|
+
raise RuntimeError("Unreachable")
|
|
60
|
+
|
|
61
|
+
return wrapper
|
|
62
|
+
|
|
63
|
+
return decorator
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Unit tests for `code=1` / "no data" handling in BaseAPI._request."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import AsyncMock, Mock, patch
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from lixinger.api.base import BaseAPI
|
|
12
|
+
from lixinger.exceptions import APIError
|
|
13
|
+
from lixinger.utils.rate_limiter import AsyncRateLimiter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _mock_response(json_body: dict) -> Mock:
|
|
17
|
+
resp = Mock()
|
|
18
|
+
resp.status_code = 200
|
|
19
|
+
resp.text = "mock body"
|
|
20
|
+
resp.headers = {}
|
|
21
|
+
resp.json.return_value = json_body
|
|
22
|
+
return resp
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _make_api(monkeypatch: pytest.MonkeyPatch) -> BaseAPI:
|
|
26
|
+
monkeypatch.setenv("LIXINGER_API_KEY", "test_key")
|
|
27
|
+
return BaseAPI(rate_limiter=AsyncRateLimiter(max_requests=1000))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_no_data_message_returns_empty_list(monkeypatch):
|
|
32
|
+
"""Lixinger returns code=1/message="no data" for successful requests that
|
|
33
|
+
happen to have nothing to return (non-trading days, empty ranges, etc.).
|
|
34
|
+
_request should treat this as success and return an empty payload rather
|
|
35
|
+
than raising APIError."""
|
|
36
|
+
api = _make_api(monkeypatch)
|
|
37
|
+
|
|
38
|
+
body = {"code": 1, "message": "no data", "data": None}
|
|
39
|
+
|
|
40
|
+
with patch("lixinger.api.base.get_global_client") as mock_client_factory:
|
|
41
|
+
client = Mock(spec=httpx.AsyncClient)
|
|
42
|
+
client.request = AsyncMock(return_value=_mock_response(body))
|
|
43
|
+
mock_client_factory.return_value = client
|
|
44
|
+
|
|
45
|
+
result = await api._request("POST", "/whatever", json={"a": 1})
|
|
46
|
+
|
|
47
|
+
assert result == []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_missing_data_key_returns_empty_list(monkeypatch):
|
|
52
|
+
"""Some 'no data' responses omit the data key entirely instead of nulling it."""
|
|
53
|
+
api = _make_api(monkeypatch)
|
|
54
|
+
|
|
55
|
+
body = {"code": 1, "message": "no data"}
|
|
56
|
+
|
|
57
|
+
with patch("lixinger.api.base.get_global_client") as mock_client_factory:
|
|
58
|
+
client = Mock(spec=httpx.AsyncClient)
|
|
59
|
+
client.request = AsyncMock(return_value=_mock_response(body))
|
|
60
|
+
mock_client_factory.return_value = client
|
|
61
|
+
|
|
62
|
+
result = await api._request("POST", "/whatever", json={"a": 1})
|
|
63
|
+
|
|
64
|
+
assert result == []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.asyncio
|
|
68
|
+
async def test_success_with_data_unchanged(monkeypatch):
|
|
69
|
+
"""Regression guard: code=1/message="success" with real data still works."""
|
|
70
|
+
api = _make_api(monkeypatch)
|
|
71
|
+
|
|
72
|
+
body = {"code": 1, "message": "success", "data": [{"x": 1}, {"x": 2}]}
|
|
73
|
+
|
|
74
|
+
with patch("lixinger.api.base.get_global_client") as mock_client_factory:
|
|
75
|
+
client = Mock(spec=httpx.AsyncClient)
|
|
76
|
+
client.request = AsyncMock(return_value=_mock_response(body))
|
|
77
|
+
mock_client_factory.return_value = client
|
|
78
|
+
|
|
79
|
+
result = await api._request("POST", "/whatever", json={"a": 1})
|
|
80
|
+
|
|
81
|
+
assert result == [{"x": 1}, {"x": 2}]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_non_success_code_still_raises(monkeypatch):
|
|
86
|
+
"""Real errors (code != 1) must still raise regardless of message text."""
|
|
87
|
+
api = _make_api(monkeypatch)
|
|
88
|
+
|
|
89
|
+
body = {"code": 0, "message": "bad token", "data": None}
|
|
90
|
+
|
|
91
|
+
with patch("lixinger.api.base.get_global_client") as mock_client_factory:
|
|
92
|
+
client = Mock(spec=httpx.AsyncClient)
|
|
93
|
+
client.request = AsyncMock(return_value=_mock_response(body))
|
|
94
|
+
mock_client_factory.return_value = client
|
|
95
|
+
|
|
96
|
+
with pytest.raises(APIError, match="error code 0"):
|
|
97
|
+
await api._request("POST", "/whatever", json={"a": 1})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.asyncio
|
|
101
|
+
async def test_no_data_downstream_returns_empty_dataframe(monkeypatch):
|
|
102
|
+
"""End-to-end check: an API method that iterates the payload and passes it
|
|
103
|
+
through get_response_df should return an empty DataFrame (not crash) when
|
|
104
|
+
the server returns code=1/message='no data'."""
|
|
105
|
+
# Late import so the AsyncLixingerClient instantiation picks up the env var.
|
|
106
|
+
from lixinger import AsyncLixingerClient
|
|
107
|
+
|
|
108
|
+
monkeypatch.setenv("LIXINGER_API_KEY", "test_key")
|
|
109
|
+
client = AsyncLixingerClient()
|
|
110
|
+
|
|
111
|
+
body = {"code": 1, "message": "no data", "data": None}
|
|
112
|
+
|
|
113
|
+
with patch.object(client._http_client, "request") as mock_request:
|
|
114
|
+
mock_request.return_value = Mock()
|
|
115
|
+
mock_request.return_value.status_code = 200
|
|
116
|
+
mock_request.return_value.headers = {}
|
|
117
|
+
mock_request.return_value.json.return_value = body
|
|
118
|
+
|
|
119
|
+
df = await client.cn_industry.margin_trading.sw.get_margin_trading(
|
|
120
|
+
stock_code="490000",
|
|
121
|
+
start_date="2025-06-13",
|
|
122
|
+
end_date="2026-06-13",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
assert isinstance(df, pd.DataFrame)
|
|
126
|
+
assert df.empty
|