settfex 0.22.2__tar.gz → 0.23.0__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.
- {settfex-0.22.2 → settfex-0.23.0}/AGENTS.md +13 -4
- {settfex-0.22.2 → settfex-0.23.0}/CHANGELOG.md +80 -0
- {settfex-0.22.2 → settfex-0.23.0}/CLAUDE.md +5 -1
- {settfex-0.22.2 → settfex-0.23.0}/PKG-INFO +1 -1
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/sec/financial_report.md +17 -0
- {settfex-0.22.2 → settfex-0.23.0}/pyproject.toml +1 -1
- {settfex-0.22.2 → settfex-0.23.0}/settfex/__init__.py +1 -1
- {settfex-0.22.2 → settfex-0.23.0}/settfex/exceptions.py +41 -1
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/__init__.py +2 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/financial_report.py +220 -17
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/api_surface.json +127 -0
- settfex-0.23.0/tests/services/sec/test_code_isolation.py +281 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_financial_report_accounting.py +50 -0
- {settfex-0.22.2 → settfex-0.23.0}/uv.lock +1 -1
- {settfex-0.22.2 → settfex-0.23.0}/.editorconfig +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/dependabot.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/instructions/core-architectrual-principles.instructions.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/instructions/documentation-standards.instructions.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/instructions/file-organization.instructions.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/instructions/git-commit.instructions.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/instructions/python-dependency-management.instructions.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/prompts/Coding.prompt.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/prompts/Git-Commit-Reviewer.prompt.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/prompts/Prompt-Engineer.prompt.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/prompts/Python-Architect.prompt.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/workflows/ci.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/workflows/dependency-drift.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/workflows/pre-commit.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/workflows/release.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.github/workflows/security.yml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.gitignore +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.pre-commit-config.yaml +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/.python-version +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/CODE_OF_CONDUCT.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/COMPREHENSIVE_AUDIT.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/CONTRIBUTING.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/LICENSE +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/README.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/RELEASING.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/SECURITY.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/guide/PYTHON_LIBRARY_BEST_PRACTICES.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/index.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/API_PROTECTION_NOTE.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/analyst_consensus.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/board_of_director.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/chart_quotation.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/corporate_action.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/dr_indicative_price.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/earnings_call.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/financial.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/highlight_data.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/holiday.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/index.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/info.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/latest_historical_trading.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/list.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/news.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/nvdr_holder.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/price_performance.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/profile_company.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/profile_dr.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/profile_stock.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/shareholder.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/set/trading_stat.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/tfex/list.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/tfex/trading_statistics.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/services/thaibma/yield_curve.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/utils/data_fetcher.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/settfex/utils/session_caching.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/solution/FINDINGS.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/solution/Performance Boost Session Caching Implementation.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/solution/SESSION_CACHE_SUMMARY.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/docs/solution/SOLUTION_100_PERCENT.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/README.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/sec/01_financial_report.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/01_stock_list.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/02_highlight_data.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/03_stock_profile.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/04_company_profile.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/05_corporate_action.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/06_shareholder.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/07_nvdr_holder.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/08_board_of_director.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/09_trading_statistics.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/10_price_performance.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/11_financial.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/12_earnings_call.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/13_chart_quotation.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/14_latest_historical_trading.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/15_market_index.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/16_news.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/17_holiday.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/18_dr_and_asset_type.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/19_analyst_consensus.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/20_stock_info.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/README.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/cpall_balance_sheet.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/market_dashboard_20251005_173221.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/market_dashboard_20251005_210737.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/market_dashboard_20251006_091440.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/thailand_banking_stocks.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/thailand_set_main_board.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/set/thailand_stock_universe.csv +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/tfex/01_series_list.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/tfex/02_trading_statistics.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/tfex/03_underlying_price.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/examples/thaibma/01_government_yield_curve.ipynb +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/py.typed +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/company.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/constants.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/download.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/sec.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/sec/utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/asset_type.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/constants.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/earnings_call.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/holiday.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/chart_quotation.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/composition.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/index.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/info.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/index/utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/news.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/analyst_consensus.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/board_of_director.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/chart_quotation.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/corporate_action.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/dr_indicative_price.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/financial/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/financial/financial.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/highlight_data.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/info.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/latest_historical_trading.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/nvdr_holder.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/price_performance.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/profile_company.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/profile_dr.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/profile_stock.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/shareholder.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/stock.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/trading_stat.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/set/stock/utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/tfex/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/tfex/constants.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/tfex/list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/tfex/trading_statistics.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/tfex/underlying_price.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/availability.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/constants.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/history.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/thaibma.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/services/thaibma/yield_curve.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/data_fetcher.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/http.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/logging.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/parsing.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/session_cache.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/session_manager.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/settfex/utils/youtube_transcript.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/conftest.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/analyst_consensus.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/dr_profile.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/earnings_call_item.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/highlight_data.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/holiday_calendar.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/index_list_response.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/news_search_response.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/sec_company_match.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/stock_info.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/stock_list_response.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/tfex_series_list_response.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/tfex_trading_statistics.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/tfex_underlying_price.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/golden/model_contract/yield_curve.json +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/integration/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/integration/test_live_endpoints.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/derive_th_fixtures.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/README.md +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/capital_indirection_en.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/en_mother_fs_h1_2025.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/en_ptt_56_1.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/en_ptt_56_2.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/en_ptt_fs_h1_2025.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/en_ptt_viewmore_56_2.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/idisc_505_error_page.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/th_cpall_viewmore_56_1.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/th_mother_fs_h1_2025.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/th_ptt_56_1.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/th_ptt_56_2.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/fixtures_sec/th_ptt_fs_h1_2025.html +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_company.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_download.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_download_indirection.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_download_result.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_financial_report.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_financial_report_th.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_listing_transport.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_sec.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/sec/test_view_more_slugs.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/test_chart_quotation.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/test_composition.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/test_index.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/test_info.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/index/test_list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/financial/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/financial/test_financial.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_analyst_consensus.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_chart_quotation.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_dr_indicative_price.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_info.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_latest_historical_trading.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/stock/test_profile_dr.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_asset_type.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_board_of_director.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_client.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_corporate_action.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_earnings_call.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_historical.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_holiday.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_news.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_profile_company.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_realtime.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_shareholder.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/set/test_symbol_suggestion.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_client.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_historical.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_list.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_realtime.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_trading_statistics.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/tfex/test_underlying_price.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/fixtures.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/test_availability.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/test_history.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/test_thaibma.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/test_utils.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/services/thaibma/test_yield_curve.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/test_impersonate_target.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/test_model_contract.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/test_public_api_surface.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/__init__.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_data_fetcher.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_formatting.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_http.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_logging.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_parsing.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_session_cache.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_session_manager.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_validation.py +0 -0
- {settfex-0.22.2 → settfex-0.23.0}/tests/utils/test_youtube_transcript.py +0 -0
|
@@ -130,10 +130,19 @@ listing where **every** usable row was lost raises `IncompleteListingError` rath
|
|
|
130
130
|
transport: a failed request raises `FetchError`, and a body that is not a listing page raises
|
|
131
131
|
`ParseError`, instead of parsing to zero rows and looking like "this issuer filed nothing".
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
its truncated inline rows and the shortfall shows in `completeness()
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
Two things deliberately do **not** raise. A broken "display all results" page: that section keeps
|
|
134
|
+
its truncated inline rows and the shortfall shows in `completeness()`. And **one failing report
|
|
135
|
+
code**: the others keep their documents, and the failure is recorded on
|
|
136
|
+
`docs.accounting.failed_codes` (with a WARNING). So after a call that returned normally, check
|
|
137
|
+
`docs.accounting.has_losses` before treating the result as complete — an exception is no longer how
|
|
138
|
+
a partial failure announces itself. `repr(docs)` states it too.
|
|
139
|
+
|
|
140
|
+
As of 2026-09-20 SEC's Thai `fs-kf` page answers HTTP 500, which is an upstream bug, not a settfex
|
|
141
|
+
one; it shows up as a `key_financial_ratio` shortfall on Thai listings.
|
|
142
|
+
|
|
143
|
+
⚠️ Read `.accounting` / `.reported_counts` off the object `get_sec_documents` returned: like
|
|
144
|
+
`DownloadResult`, `SecDocumentList` is a `list` subclass, so slicing, `sorted()`, `list()` and
|
|
145
|
+
comprehensions return a plain `list` and silently drop them.
|
|
137
146
|
|
|
138
147
|
`download_sec_documents(...)` returns a `DownloadResult` — a list of the files that downloaded,
|
|
139
148
|
which also carries `.failed` (each with its target and reason) and `.is_complete`. Check it before
|
|
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.23.0] - 2026-09-20
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **One failing report code discarded the successfully parsed documents of every other code**
|
|
15
|
+
(issue #132). `fetch_documents` defaults to all five categories, which collapse to three
|
|
16
|
+
`ddlReportType` codes run through `asyncio.gather` **without** `return_exceptions=True` — so the
|
|
17
|
+
first exception propagated before the line that collects the results was ever reached, and a
|
|
18
|
+
total loss in the `R562` search threw away the `FS` documents that had parsed perfectly well in
|
|
19
|
+
the same call.
|
|
20
|
+
|
|
21
|
+
The irony is worth stating, because it is the whole argument: 0.22.0 added
|
|
22
|
+
`IncompleteListingError` so a caller could tell loss from emptiness, and this `gather` discarded
|
|
23
|
+
the evidence of what *did* parse at the exact moment of raising it.
|
|
24
|
+
|
|
25
|
+
The rule is the one this module already uses twice — **a partial loss reports, a total loss
|
|
26
|
+
raises**: one code failing keeps its siblings' documents, records a `CodeFailure` and warns;
|
|
27
|
+
every code failing re-raises the first cause with its type and traceback intact. Anything that is
|
|
28
|
+
**not** a `FetchError` propagates untouched — a bug in settfex must not be laundered into an
|
|
29
|
+
accounting row, where it would be buried in a field most callers never read.
|
|
30
|
+
|
|
31
|
+
Deliberately **not** an `ExceptionGroup`, despite requiring Python 3.11+: `except FetchError`
|
|
32
|
+
does not catch one, so it would silently break every existing handler. When every code fails the
|
|
33
|
+
other causes ride along as **PEP 678 notes** on the raised one, so "the caller can always tell
|
|
34
|
+
which code failed and why" holds even in the total-failure case — with no new type, no changed
|
|
35
|
+
signature, and the traceback intact. "First" means first in **report-code order**, not first to
|
|
36
|
+
fail in time: `gather` returns positionally, so two identical runs raise the same cause.
|
|
37
|
+
|
|
38
|
+
- **The same gather problem one level down**, on the "display all results" pages. 0.22.1 made a
|
|
39
|
+
failing ViewMore *response* degrade; a transport exception in that gather still killed the whole
|
|
40
|
+
call. It now degrades the same way: keep the section's truncated inline rows, warn, and let
|
|
41
|
+
`completeness()` show the shortfall.
|
|
42
|
+
|
|
43
|
+
- **`IncompleteListingError` could not carry `unknown_sections`.** The field is inherited from
|
|
44
|
+
`ParseError`, but the subclass's `__init__` never accepted it, so it was **always `[]`** — a
|
|
45
|
+
field that looked answered and was not. A page can lose every row to a missing download link
|
|
46
|
+
*and* carry a heading nobody recognises; that combination is now visible.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **`HTTPStatusError(FetchError)`** with `url` and `report_code`. `FetchError.status_code` already
|
|
51
|
+
distinguished a transport failure from an HTTP one; what was missing was somewhere for the two
|
|
52
|
+
facts a caller otherwise had to scrape out of the message string. An archive recording "the R562
|
|
53
|
+
search failed" should not have to parse prose to do it.
|
|
54
|
+
|
|
55
|
+
- **`ListingAccounting.failed_codes`** — a list of `CodeFailure` (`code`, `categories`, `error`,
|
|
56
|
+
`error_type`, `status_code`, `url`), deliberately the same shape as `FailedDownload` from #128:
|
|
57
|
+
one pattern for "what did not make it", whether it was a file or a whole search. `has_losses` is
|
|
58
|
+
true when it is non-empty, because a search that never ran is the largest loss there is.
|
|
59
|
+
|
|
60
|
+
- **`RowTally.unlinked_hrefs`** — a capped sample of the hrefs behind `no_link`, the smaller half of
|
|
61
|
+
issue #133. Every unknown download shape so far (`fsdl`, `viewdoc`, `capfin`) was found by a human
|
|
62
|
+
looking at a dropped row's URL, and each time that meant re-parsing the page by hand. The sample
|
|
63
|
+
is **diversity-first**: one href per distinct `(host, path)` before the cap of 20 is spent, so
|
|
64
|
+
twenty instances of an already-known shape can never crowd out the single instance of a new one —
|
|
65
|
+
which is precisely the row worth seeing. `no_link` remains the true total.
|
|
66
|
+
|
|
67
|
+
- **`SecDocumentList.__repr__`**, and a failed-codes block in `summary()`. A summary that lists only
|
|
68
|
+
what arrived reads as success when an entire search is missing.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- ⚠️ **Callers that relied on an exception to detect a partial failure must now check
|
|
73
|
+
`has_losses` / `accounting.failed_codes`.** Before 0.23.0, one report code failing raised and took
|
|
74
|
+
everything with it; now the call returns the documents that parsed. That is the point of the fix —
|
|
75
|
+
but a `try/except FetchError` that treated "no exception" as "complete" will now see a partial
|
|
76
|
+
result as a whole one. Every failed code is also logged at WARNING, which is the signal that
|
|
77
|
+
cannot be lost.
|
|
78
|
+
|
|
79
|
+
### Documentation
|
|
80
|
+
|
|
81
|
+
- **Issue #134's scope now covers `SecDocumentList`, not only `DownloadResult`** — verified: 6 of 8
|
|
82
|
+
ordinary list operations (slicing, `sorted()`, `list()`, concatenation, comprehensions) return a
|
|
83
|
+
plain `list` and drop `.accounting` and `.reported_counts`; `copy.copy()` and `filter()` are the
|
|
84
|
+
exceptions, and `filter()` only because 0.22.0 wired it through deliberately. This matters more
|
|
85
|
+
now that `failed_codes` rides on that same attribute, which is why each failed code is *also*
|
|
86
|
+
logged. Interim warning on the class and in the docs; the redesign is breaking and covers both
|
|
87
|
+
types as one decision.
|
|
88
|
+
|
|
89
|
+
|
|
10
90
|
## [0.22.2] - 2026-09-20
|
|
11
91
|
|
|
12
92
|
### Fixed
|
|
@@ -173,7 +173,7 @@ Host is **`market.sec.or.th`** (the Thai SEC IDISC system), NOT set.or.th — a
|
|
|
173
173
|
|
|
174
174
|
| # | Service | Module | Endpoint Pattern | Key Data |
|
|
175
175
|
|---|---|---|---|---|
|
|
176
|
-
| 1 | SEC Documents | `sec/{company,financial_report,download,sec}.py` | `POST /public/idisc/api/company/valuebyuniqueId`; `GET`/`POST /public/idisc/{lang}/FinancialReport/{FS\|R561\|R562\|KFR}`; `GET /public/idisc/{lang}/ViewMore/{slug}`; `GET /public/idisc/Download?FILEID=`; `GET /ipos/Common/IPOSGetFile.aspx?id=`; `GET /public/idisc/Views/FinancialStatementDownload?query=`; `GET /public/idisc/views/viewdoc?TransId=` | List + download **raw disclosure documents** for any issuer across 5 categories (`DocumentCategory`: financial_statement/form_56_1/form_56_2/key_financial_ratio/mda). Company resolver (`resolve_company` → 10-digit uniqueIDReference); listing replays the ASP.NET WebForms search (GET `__VIEWSTATE` → form POST → stdlib HTML-table parse), follows ViewMore for complete large sections; downloads return raw bytes (`DownloadedFile.save()`), concurrent `download_all`, soft-404 detection (dead links = HTML "file not found" under HTTP 200 → `FetchError`). Listing returns a **`SecDocumentList`** (a `list[SecDocument]` subclass, backward compatible) with `years_by_category()`/`available_years()`/`filter(category=,year=)`/`categories()`/`summary()` helpers — pass a **wide** date window to see full year history. `SecCompany("CPALL")` facade; `get_sec_documents()`/`download_sec_document(s)()`. dd/mm/yyyy dates. Stateless host (no SessionManager). **Bilingual** — `lang="th"` returns the Thai-language filing documents (different files from the English ones), with Buddhist-era years/dates normalized to C.E. in the model; free-text cells stay in the page's language. `SecDocumentList.reported_counts`/`completeness()` carry what the site said each section holds, and an unclassifiable page raises `ParseError` instead of returning `[]`. **Row/column accounting** (`docs.accounting`: `no_link`/`placeholders`/`navigation`/`unmapped_headers`/`unverifiable_sections`/`by_category`, computed totals) makes every drop visible in the return value; a **total** loss raises `IncompleteListingError`, a partial one warns. `download_all` returns a `DownloadResult` (a `list[DownloadedFile]` subclass) carrying `.failed`. |
|
|
176
|
+
| 1 | SEC Documents | `sec/{company,financial_report,download,sec}.py` | `POST /public/idisc/api/company/valuebyuniqueId`; `GET`/`POST /public/idisc/{lang}/FinancialReport/{FS\|R561\|R562\|KFR}`; `GET /public/idisc/{lang}/ViewMore/{slug}`; `GET /public/idisc/Download?FILEID=`; `GET /ipos/Common/IPOSGetFile.aspx?id=`; `GET /public/idisc/Views/FinancialStatementDownload?query=`; `GET /public/idisc/views/viewdoc?TransId=` | List + download **raw disclosure documents** for any issuer across 5 categories (`DocumentCategory`: financial_statement/form_56_1/form_56_2/key_financial_ratio/mda). Company resolver (`resolve_company` → 10-digit uniqueIDReference); listing replays the ASP.NET WebForms search (GET `__VIEWSTATE` → form POST → stdlib HTML-table parse), follows ViewMore for complete large sections; downloads return raw bytes (`DownloadedFile.save()`), concurrent `download_all`, soft-404 detection (dead links = HTML "file not found" under HTTP 200 → `FetchError`). Listing returns a **`SecDocumentList`** (a `list[SecDocument]` subclass, backward compatible) with `years_by_category()`/`available_years()`/`filter(category=,year=)`/`categories()`/`summary()` helpers — pass a **wide** date window to see full year history. `SecCompany("CPALL")` facade; `get_sec_documents()`/`download_sec_document(s)()`. dd/mm/yyyy dates. Stateless host (no SessionManager). **Bilingual** — `lang="th"` returns the Thai-language filing documents (different files from the English ones), with Buddhist-era years/dates normalized to C.E. in the model; free-text cells stay in the page's language. `SecDocumentList.reported_counts`/`completeness()` carry what the site said each section holds, and an unclassifiable page raises `ParseError` instead of returning `[]`. **Row/column accounting** (`docs.accounting`: `no_link`/`placeholders`/`navigation`/`unmapped_headers`/`unverifiable_sections`/`by_category`, computed totals) makes every drop visible in the return value; a **total** loss raises `IncompleteListingError`, a partial one warns. `download_all` returns a `DownloadResult` (a `list[DownloadedFile]` subclass) carrying `.failed`. **Transport guards** on every listing leg (non-2xx → `HTTPStatusError`, non-listing body → `ParseError`); **per-code isolation** so one failing `ddlReportType` keeps its siblings' documents and records a `CodeFailure` on `accounting.failed_codes`. |
|
|
177
177
|
|
|
178
178
|
### ThaiBMA Services (1)
|
|
179
179
|
|
|
@@ -418,6 +418,10 @@ See [`CHANGELOG.md`](CHANGELOG.md) for the full, versioned release history — t
|
|
|
418
418
|
- **SEC's old-format filings are on a FIFTH host and behind a JavaScript indirection (issue #133):** `capital.sec.or.th/…/get_zip_all_public_page.php` carries pre-2014 `.DOC`/`.XLS` filings. The URL answers **HTTP 200 with a 2 KB HTML page whose entire body is `document.location = '/tmp/<id>.zip'`** — so recognising the link is necessary but not sufficient, and `download()` resolves the redirect at download time (one extra request). Three properties the code depends on: the target is **minted per request** (`XP`/`sD`/`V1` for the same filing across three captures), so `file_url` stays the stable indirection URL and the `/tmp/…` one is never stored; it is **per-language** (the Thai request mints a different archive, so `lang` is part of the `capfin:<comp_id>-<year>-<period>-<lang>` id); and `comp_id` is a **different id space from `set_id`** in the same query — they are not interchangeable. The redirect pattern accepts an absolute URL on purpose, so the same-host check is live rather than dead code.
|
|
419
419
|
- **These old-format zips are MEMBER-STABLE, not byte-stable — do not key an archive on the container hash:** re-minting the same filing produced a different container sha256 while every member payload stayed byte-identical. Exactly **12 bytes of 334,498** differ, in four 3-byte runs, one per member: the Info-ZIP extended-timestamp (`UT`) extra field, i.e. the pack time. **Judge integrity per member.** A consumer that hashes the container will see a legitimate re-fetch as a new — or corrupted — object. (Also observed: one sample's embedded pack time *predated* the request that minted it, suggesting the server sometimes hands back a previously generated archive. INFERRED from two samples.)
|
|
420
420
|
- **`DownloadResult` loses `.failed` the moment you derive a new list (issue #134, documented not fixed):** `.failed`, `.requested` and `.is_complete` are instance attributes on a `list` subclass, so slicing, `sorted()`, `list()`, concatenation and comprehensions all return a plain `list` without them — 6 of 7 ordinary operations, `copy.copy()` being the exception. `for f in sorted(result)` therefore quietly gives up the failure report #128 added. **Read `.failed` from the object `download_all` returned, before deriving anything from it.** `SecDocumentList` has the identical hazard for `.accounting`/`.reported_counts`. Removing the edge is a breaking change and is tracked for a later release.
|
|
421
|
+
- **One failing report code no longer discards the others — and `has_losses` is now the only reliable "was it complete?" check:** `fetch_documents` collapses the requested categories to up to three `ddlReportType` codes run concurrently; the gather had no `return_exceptions=True`, so the first exception propagated before the collecting line ran and a total loss in `R562` threw away the `FS` documents that had parsed fine (issue #132). Now: **partial loss reports, total loss raises** — one code failing keeps its siblings' documents and records a `CodeFailure` on `accounting.failed_codes`; every code failing re-raises the first cause with its type and traceback intact. ⚠️ **Anything that is not a `FetchError` propagates untouched** — a settfex bug must not be laundered into an accounting row, where it would be buried in a field most callers never read. ⚠️ **Deliberately NOT an `ExceptionGroup`** despite the 3.11+ floor: `except FetchError` does not catch one, so it would silently break every existing handler. When every code fails, the other causes ride along as **PEP 678 notes** (`exc.add_note`) on the raised one — otherwise re-raising a single exception would quietly drop them, breaking the rule that a caller can always tell which code failed and why. **"First" means first in report-code order, not first to fail in time** (`gather` returns positionally), so the raised cause is deterministic across runs; a test pins that by making the last code fail instantly while the first yields 20 times. ⚠️ **Upgrade trap:** a `try/except FetchError` that treated "no exception" as "complete" now sees a partial result as a whole one — check `has_losses` / `failed_codes`. The same `return_exceptions` treatment was applied to the ViewMore gather, so a transport error there costs one section's completeness rather than the call.
|
|
422
|
+
- **`unlinked_hrefs` is sampled for DIVERSITY, not "the first 20" — and that is the whole point:** `RowTally.unlinked_hrefs` keeps a capped sample of the hrefs behind `no_link`, because every unknown download shape so far (`fsdl`, `viewdoc`, `capfin`) was found by a human looking at a dropped row's URL, and each time that meant re-parsing the page by hand. The sampler takes **one href per distinct `(host, path)` first**, then fills to the cap of 20 — so thirty instances of an already-known shape can never crowd out the single instance of a new one, which is exactly the row worth seeing. `no_link` stays the true total, and `plus()` **re-samples** on merge rather than concatenating, or a merge could exceed the cap or lose the property the sample exists for.
|
|
423
|
+
- **`IncompleteListingError` could not carry `unknown_sections` until 0.23.0 — a field that looked answered and was not:** it is inherited from `ParseError`, but the subclass's `__init__` never accepted it, so it was **always `[]`**. Anyone who inspected it concluded "no unrecognised headings" from a value that had never been populated. Fixed by accepting and forwarding it; a page can lose every row to a missing link *and* carry a heading nobody recognises.
|
|
424
|
+
- **`SecDocumentList` has `DownloadResult`'s side-channel problem too (issue #134, documented not fixed):** 6 of 8 ordinary list operations — slicing, `sorted()`, `list()`, `+`, comprehensions — return a plain `list` and drop `.accounting` and `.reported_counts`. `copy.copy()` and `filter()` survive, and `filter()` only because 0.22.0 wired it through on purpose. **This got sharper in 0.23.0**, because `accounting.failed_codes` records which report code failed, so `sorted(docs, …)` would discard that too — which is why every failed code is **also logged at WARNING**: a log line cannot be sliced away. Read the accounting off the object `fetch_documents` returned. The redesign is breaking and covers both types as one decision.
|
|
421
425
|
- **`no_href` was three different things, and only one is a defect:** a dropped SEC listing row is a `ไม่พบข้อมูล`/`Data not found` **placeholder** (the site saying the section is empty), a "display all results" **ViewMore navigation row** (how a long section truncates — 24% of live sections), or a **real data row whose download link is gone** (`no_link`). They shared one counter, which is why P1 could not be escalated: any rule strict enough to catch a genuine loss also fired on every truncated section. Split (`ListingAccounting.placeholders`/`navigation`/`no_link`), `no_link` is **0 on every real page captured** and non-zero only on a page derived to exhibit the bug — so it can be escalated without a record-count marker at all. `_MapResult.no_href` is kept as the sum of the three; **branch on `accounting.no_link`, never on `no_href`** (it is non-zero on perfectly healthy pages).
|
|
422
426
|
- **Two cross-checks on a SEC listing, and they answer different questions:** `completeness()`/`reported_counts` compares what you hold against **the number the site printed** — a shortfall there is usually legitimate "view more" truncation, so it is reported and never raised. `docs.accounting` is **ours**: what the parser did with each row it received. A shortfall is a maybe; `accounting.no_link > 0` is always a loss. Do not fold one into the other.
|
|
423
427
|
- **`IncompleteListingError` fires only on a TOTAL loss:** rows classified fine and *every* usable one was dropped for want of a download link. A **partial** loss warns and lands on `accounting.no_link` instead — raising there would turn one bad row in a 200-row listing into no listing at all, and unlike a total loss a partial one is visible in the return value. Same severity ladder as the 0.21.0 `ParseError` for unrecognised headings, and it is a `ParseError` subclass so `except ParseError`/`except FetchError` keep working. The escalation is scoped to the **requested** categories (a single `FS` search returns three sections) — the same principle PR #125 applied to `completeness()`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: settfex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: Async Python library for fetching real-time and historical data from the Stock Exchange of Thailand (SET) and Thailand Futures Exchange (TFEX)
|
|
5
5
|
Project-URL: Homepage, https://github.com/lumduan/settfex
|
|
6
6
|
Project-URL: Documentation, https://github.com/lumduan/settfex#readme
|
|
@@ -134,8 +134,22 @@ docs.accounting.navigation # "display all results" rows — how a long sec
|
|
|
134
134
|
docs.accounting.unmapped_headers # columns the site serves that map to no field
|
|
135
135
|
docs.accounting.has_losses # the one flag worth branching on
|
|
136
136
|
docs.accounting.by_category # the same tally per DocumentCategory
|
|
137
|
+
docs.accounting.failed_codes # report-code searches that failed while their siblings succeeded
|
|
138
|
+
docs.accounting.by_category["financial_statement"].unlinked_hrefs # a capped sample of the URLs
|
|
139
|
+
# behind no_link, for reporting
|
|
137
140
|
```
|
|
138
141
|
|
|
142
|
+
> ⚠️ **After a call that returned normally, check `has_losses` before treating the result as
|
|
143
|
+
> complete.** Since 0.23.0 one failing report code no longer takes the whole call down with it —
|
|
144
|
+
> its siblings keep their documents and the failure is recorded on `failed_codes` (and logged at
|
|
145
|
+
> WARNING). An exception is therefore no longer how a partial failure announces itself.
|
|
146
|
+
> `repr(docs)` and `summary()` both state it.
|
|
147
|
+
|
|
148
|
+
> ⚠️ **Read the accounting off the object `fetch_documents` returned.** `SecDocumentList` is a
|
|
149
|
+
> `list` subclass, so slicing, `sorted()`, `list()`, concatenation and comprehensions return a
|
|
150
|
+
> plain `list` and drop `.accounting` / `.reported_counts` — the same sharp edge `DownloadResult`
|
|
151
|
+
> has (issue #134 covers both). `copy.copy()` and `.filter()` are the exceptions.
|
|
152
|
+
|
|
139
153
|
The three drop reasons used to share one counter, so the only one that is a defect looked exactly
|
|
140
154
|
like the two that are the site working normally. They are separated because `no_link` is the signal
|
|
141
155
|
worth escalating and the other two must never be able to trigger it.
|
|
@@ -147,6 +161,9 @@ carries data rows and **none** of them can be classified — the signature of a
|
|
|
147
161
|
An issuer with no filings still returns an empty list and does **not** raise: the check keys on
|
|
148
162
|
unrecognised section headings, not on emptiness.
|
|
149
163
|
|
|
164
|
+
`HTTPStatusError` (a subclass of `FetchError`) is raised for a non-2xx on any listing leg, carrying
|
|
165
|
+
`status_code`, `url` and `report_code` as data rather than as message text.
|
|
166
|
+
|
|
150
167
|
`IncompleteListingError` (a subclass of `ParseError`) is its sibling: rows classified fine and
|
|
151
168
|
then **every** usable one was dropped for want of a download link. Deliberately
|
|
152
169
|
narrow — a *partial* loss does not raise, it warns and lands on `accounting.no_link`, because a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "settfex"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.23.0"
|
|
4
4
|
description = "Async Python library for fetching real-time and historical data from the Stock Exchange of Thailand (SET) and Thailand Futures Exchange (TFEX)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -26,6 +26,7 @@ __all__ = [
|
|
|
26
26
|
"StaleDataError",
|
|
27
27
|
"ParseError",
|
|
28
28
|
"IncompleteListingError",
|
|
29
|
+
"HTTPStatusError",
|
|
29
30
|
"InvalidSymbolError",
|
|
30
31
|
"InvalidLanguageError",
|
|
31
32
|
"InvalidDateError",
|
|
@@ -160,6 +161,11 @@ class IncompleteListingError(ParseError):
|
|
|
160
161
|
``lost_rows`` is how many rows were dropped that way; ``reported`` is what the page said each
|
|
161
162
|
section holds. Subclasses :class:`ParseError`, so ``except ParseError`` and
|
|
162
163
|
``except FetchError`` handlers keep working.
|
|
164
|
+
|
|
165
|
+
``unknown_sections`` is inherited from :class:`ParseError` and, until 0.23.0, this subclass had
|
|
166
|
+
no way to accept it — so it was **always** ``[]`` here, a field that looked answered and was
|
|
167
|
+
not. A page can lose every row to a missing download link *and* carry a heading nobody
|
|
168
|
+
recognises, and that combination is worth seeing.
|
|
163
169
|
"""
|
|
164
170
|
|
|
165
171
|
def __init__(
|
|
@@ -168,14 +174,48 @@ class IncompleteListingError(ParseError):
|
|
|
168
174
|
*,
|
|
169
175
|
url: str | None = None,
|
|
170
176
|
rows_parsed: int | None = None,
|
|
177
|
+
unknown_sections: list[str] | None = None,
|
|
171
178
|
lost_rows: int | None = None,
|
|
172
179
|
reported: dict[str, int] | None = None,
|
|
173
180
|
) -> None:
|
|
174
|
-
super().__init__(
|
|
181
|
+
super().__init__(
|
|
182
|
+
message, url=url, rows_parsed=rows_parsed, unknown_sections=unknown_sections
|
|
183
|
+
)
|
|
175
184
|
self.lost_rows = lost_rows
|
|
176
185
|
self.reported = dict(reported or {})
|
|
177
186
|
|
|
178
187
|
|
|
188
|
+
class HTTPStatusError(FetchError):
|
|
189
|
+
"""A request returned a non-2xx status, with the context needed to act on it.
|
|
190
|
+
|
|
191
|
+
:class:`FetchError` already carries ``status_code``, so this adds no new *capability* — what it
|
|
192
|
+
adds is a place for the two facts a caller otherwise has to scrape out of the message string:
|
|
193
|
+
which URL, and which report code. An archive recording "the R562 search failed" should not have
|
|
194
|
+
to parse prose to do it, which is the same failure mode this family of fixes keeps closing.
|
|
195
|
+
|
|
196
|
+
Deliberately **not** raised via ``raise_for_status``: that helper maps 404 to
|
|
197
|
+
:class:`SymbolNotFoundError` and consults the symbol suggester, which is wrong on an endpoint
|
|
198
|
+
where the identifier was already resolved — a 404 there means the route or the host is wrong,
|
|
199
|
+
not that an issuer does not exist. (The same reasoning gives the DR-profile and
|
|
200
|
+
analyst-consensus endpoints their own handling; see CLAUDE.md's Known Gotchas.)
|
|
201
|
+
|
|
202
|
+
Subclasses :class:`FetchError`, so ``except FetchError`` keeps working.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
message: str,
|
|
208
|
+
*,
|
|
209
|
+
status_code: int | None = None,
|
|
210
|
+
url: str | None = None,
|
|
211
|
+
report_code: str | None = None,
|
|
212
|
+
symbol: str | None = None,
|
|
213
|
+
) -> None:
|
|
214
|
+
super().__init__(message, status_code=status_code, symbol=symbol)
|
|
215
|
+
self.url = url
|
|
216
|
+
self.report_code = report_code
|
|
217
|
+
|
|
218
|
+
|
|
179
219
|
class InvalidSymbolError(ValueError):
|
|
180
220
|
"""A symbol was empty or invalid."""
|
|
181
221
|
|
|
@@ -19,6 +19,7 @@ from settfex.services.sec.download import (
|
|
|
19
19
|
)
|
|
20
20
|
from settfex.services.sec.financial_report import (
|
|
21
21
|
CATEGORY_TO_REPORT_TYPE,
|
|
22
|
+
CodeFailure,
|
|
22
23
|
DocumentCategory,
|
|
23
24
|
FinancialReportService,
|
|
24
25
|
ListingAccounting,
|
|
@@ -33,6 +34,7 @@ from settfex.services.sec.sec import SecCompany
|
|
|
33
34
|
|
|
34
35
|
__all__ = [
|
|
35
36
|
"CATEGORY_TO_REPORT_TYPE",
|
|
37
|
+
"CodeFailure",
|
|
36
38
|
"CompanyMatch",
|
|
37
39
|
"DocumentCategory",
|
|
38
40
|
"DocumentDownloadService",
|