equity-aggregator 0.1.4__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.github/workflows/publish-pypi.yml +4 -1
  2. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/PKG-INFO +2 -2
  3. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/README.md +1 -1
  4. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/pyproject.toml +1 -1
  5. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/README.md +18 -12
  6. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_enrich.py +211 -15
  7. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/uv.lock +1 -1
  8. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.dockerignore +0 -0
  9. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.env_example +0 -0
  10. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.github/workflows/publish-build-release.yml +0 -0
  11. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.github/workflows/validate-push.yml +0 -0
  12. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.gitignore +0 -0
  13. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/.python-version +0 -0
  14. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/Dockerfile +0 -0
  15. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/LICENCE.txt +0 -0
  16. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/docker-compose.yml +0 -0
  17. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/intrinio_equity.json +0 -0
  18. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/lseg_equities.json +0 -0
  19. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/sec_equity.json +0 -0
  20. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/stock_analysis_equity.json +0 -0
  21. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/tiprank_equity.json +0 -0
  22. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/tradingview_equities.json +0 -0
  23. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/xetra_equities.json +0 -0
  24. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/yfinance_quote_summary_equity.json +0 -0
  25. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/documentation/reference_data/yfinance_search_equity.json +0 -0
  26. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/examples/data_integrity_analysis.py +0 -0
  27. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/__init__.py +0 -0
  28. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/__init__.py +0 -0
  29. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/__init__.py +0 -0
  30. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/_utils/__init__.py +0 -0
  31. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/_utils/_client.py +0 -0
  32. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/_utils/_record_types.py +0 -0
  33. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/__init__.py +0 -0
  34. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/__init__.py +0 -0
  35. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/__init__.py +0 -0
  36. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/backoff.py +0 -0
  37. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/_utils/parser.py +0 -0
  38. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/intrinio.py +0 -0
  39. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/intrinio/session.py +0 -0
  40. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/__init__.py +0 -0
  41. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/__init__.py +0 -0
  42. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/backoff.py +0 -0
  43. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/_utils/parser.py +0 -0
  44. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/lseg.py +0 -0
  45. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/lseg/session.py +0 -0
  46. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/sec/__init__.py +0 -0
  47. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/sec/sec.py +0 -0
  48. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/stock_analysis/__init__.py +0 -0
  49. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/stock_analysis/stock_analysis.py +0 -0
  50. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/tradingview/__init__.py +0 -0
  51. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/tradingview/tradingview.py +0 -0
  52. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/xetra/__init__.py +0 -0
  53. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/discovery_feeds/xetra/xetra.py +0 -0
  54. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/__init__.py +0 -0
  55. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/__init__.py +0 -0
  56. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/api.py +0 -0
  57. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/download.py +0 -0
  58. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/gleif.py +0 -0
  59. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/gleif/parser.py +0 -0
  60. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/__init__.py +0 -0
  61. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/__init__.py +0 -0
  62. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/backoff.py +0 -0
  63. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/fuzzy.py +0 -0
  64. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/_utils/json.py +0 -0
  65. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/__init__.py +0 -0
  66. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/quote_summary.py +0 -0
  67. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/api/search.py +0 -0
  68. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/auth.py +0 -0
  69. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/config.py +0 -0
  70. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/ranking.py +0 -0
  71. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/session.py +0 -0
  72. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/transport.py +0 -0
  73. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/enrichment_feeds/yfinance/yfinance.py +0 -0
  74. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/reference_lookup/__init__.py +0 -0
  75. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/reference_lookup/exchange_rate_api.py +0 -0
  76. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/adapters/data_sources/reference_lookup/openfigi.py +0 -0
  77. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/__init__.py +0 -0
  78. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/config.py +0 -0
  79. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/dispatcher.py +0 -0
  80. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/main.py +0 -0
  81. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/parser.py +0 -0
  82. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/cli/signals.py +0 -0
  83. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/__init__.py +0 -0
  84. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/_utils/__init__.py +0 -0
  85. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/_utils/_load_converter.py +0 -0
  86. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/_utils/_merge.py +0 -0
  87. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/_utils/_merge_config.py +0 -0
  88. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/_utils/_strategies.py +0 -0
  89. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/__init__.py +0 -0
  90. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/resolve.py +0 -0
  91. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/runner.py +0 -0
  92. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/seed.py +0 -0
  93. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/__init__.py +0 -0
  94. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/canonicalise.py +0 -0
  95. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/convert.py +0 -0
  96. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/enrich.py +0 -0
  97. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/group.py +0 -0
  98. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/identify.py +0 -0
  99. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/pipeline/transforms/parse.py +0 -0
  100. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/retrieval/__init__.py +0 -0
  101. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/domain/retrieval/retrieval.py +0 -0
  102. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/logging_config.py +0 -0
  103. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/__init__.py +0 -0
  104. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/canonical.py +0 -0
  105. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/__init__.py +0 -0
  106. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/feed_validators.py +0 -0
  107. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/gleif_feed_data.py +0 -0
  108. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/intrinio_feed_data.py +0 -0
  109. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/lseg_feed_data.py +0 -0
  110. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/sec_feed_data.py +0 -0
  111. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/stock_analysis_feed_data.py +0 -0
  112. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/tradingview_feed_data.py +0 -0
  113. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/xetra_feed_data.py +0 -0
  114. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/feeds/yfinance_feed_data.py +0 -0
  115. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/raw.py +0 -0
  116. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/types.py +0 -0
  117. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/schemas/validators.py +0 -0
  118. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/__init__.py +0 -0
  119. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/_utils.py +0 -0
  120. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/cache.py +0 -0
  121. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/data_store.py +0 -0
  122. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/export.py +0 -0
  123. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/src/equity_aggregator/storage/metadata.py +0 -0
  124. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/README.md +0 -0
  125. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/__init__.py +0 -0
  126. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/conftest.py +0 -0
  127. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/__init__.py +0 -0
  128. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/__init__.py +0 -0
  129. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/__init__.py +0 -0
  130. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/__init__.py +0 -0
  131. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/intrinio/__init__.py +0 -0
  132. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/intrinio/test_intrinio.py +0 -0
  133. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/lseg/__init__.py +0 -0
  134. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/lseg/test_lseg.py +0 -0
  135. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/sec/__init__.py +0 -0
  136. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/sec/test_sec.py +0 -0
  137. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/stock_analysis/__init__.py +0 -0
  138. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/stock_analysis/test_stock_analysis.py +0 -0
  139. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/tradingview/__init__.py +0 -0
  140. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/tradingview/test_tradingview.py +0 -0
  141. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/xetra/__init__.py +0 -0
  142. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/discovery_feeds/xetra/test_xetra.py +0 -0
  143. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/enrichment_feeds/__init__.py +0 -0
  144. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/enrichment_feeds/yfinance/__init__.py +0 -0
  145. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/live/adapters/data_sources/enrichment_feeds/yfinance/test_yfinance.py +0 -0
  146. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/__init__.py +0 -0
  147. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/__init__.py +0 -0
  148. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/_utils/__init__.py +0 -0
  149. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/_utils/test_client.py +0 -0
  150. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/__init__.py +0 -0
  151. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/__init__.py +0 -0
  152. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/__init__.py +0 -0
  153. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/_utils/__init__.py +0 -0
  154. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/_utils/test_backoff.py +0 -0
  155. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/_utils/test_parser.py +0 -0
  156. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/test_intrinio.py +0 -0
  157. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/intrinio/test_session.py +0 -0
  158. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/__init__.py +0 -0
  159. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/_utils/__init__.py +0 -0
  160. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/_utils/test_backoff.py +0 -0
  161. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/_utils/test_parser.py +0 -0
  162. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/test_lseg.py +0 -0
  163. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/lseg/test_session.py +0 -0
  164. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/sec/__init__.py +0 -0
  165. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/sec/test_sec.py +0 -0
  166. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/stock_analysis/test_stock_analysis.py +0 -0
  167. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/tradingview/__init__.py +0 -0
  168. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/tradingview/test_tradingview.py +0 -0
  169. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/xetra/__init__.py +0 -0
  170. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/discovery_feeds/xetra/test_xetra.py +0 -0
  171. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/__init__.py +0 -0
  172. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/__init__.py +0 -0
  173. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/_helpers.py +0 -0
  174. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/test_api.py +0 -0
  175. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/test_download.py +0 -0
  176. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/test_gleif.py +0 -0
  177. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/gleif/test_parser.py +0 -0
  178. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/__init__.py +0 -0
  179. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/_helpers.py +0 -0
  180. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/_utils/__init__.py +0 -0
  181. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/_utils/test_backoff.py +0 -0
  182. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/_utils/test_fuzzy.py +0 -0
  183. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/_utils/test_json.py +0 -0
  184. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/api/__init__.py +0 -0
  185. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/api/test_quote_summary.py +0 -0
  186. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/api/test_search.py +0 -0
  187. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_auth.py +0 -0
  188. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_config.py +0 -0
  189. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_ranking.py +0 -0
  190. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_session.py +0 -0
  191. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_transport.py +0 -0
  192. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/enrichment_feeds/yfinance/test_yfinance.py +0 -0
  193. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/reference_lookup/__init__.py +0 -0
  194. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/reference_lookup/test_exchange_rate_api.py +0 -0
  195. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/adapters/data_sources/reference_lookup/test_openfigi.py +0 -0
  196. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/cli/__init__.py +0 -0
  197. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/cli/test_config.py +0 -0
  198. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/cli/test_dispatcher.py +0 -0
  199. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/cli/test_main.py +0 -0
  200. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/cli/test_parser.py +0 -0
  201. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/__init__.py +0 -0
  202. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/_utils/__init__.py +0 -0
  203. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/_utils/test__merge.py +0 -0
  204. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/_utils/test__merge_config.py +0 -0
  205. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/_utils/test__strategies.py +0 -0
  206. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/_utils/test_load_converter.py +0 -0
  207. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/__init__.py +0 -0
  208. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/test_resolve.py +0 -0
  209. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/__init__.py +0 -0
  210. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_canonicalise.py +0 -0
  211. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_convert.py +0 -0
  212. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_group.py +0 -0
  213. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_identify.py +0 -0
  214. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/pipeline/transforms/test_parse.py +0 -0
  215. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/domain/retrieval/test_retrieval.py +0 -0
  216. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/__init__.py +0 -0
  217. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/__init__.py +0 -0
  218. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_feed_validators.py +0 -0
  219. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_gleif_feed_data.py +0 -0
  220. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_intrinio_feed_data.py +0 -0
  221. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_lseg_feed_data.py +0 -0
  222. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_sec_feed_data.py +0 -0
  223. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_stock_analysis_feed_data.py +0 -0
  224. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_tradingview_feed_data.py +0 -0
  225. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_xetra_feed_data.py +0 -0
  226. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/feeds/test_yfinance_feed_data.py +0 -0
  227. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/test_canonical.py +0 -0
  228. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/test_raw.py +0 -0
  229. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/test_types.py +0 -0
  230. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/schemas/test_validators.py +0 -0
  231. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/__init__.py +0 -0
  232. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/test__utils.py +0 -0
  233. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/test_cache.py +0 -0
  234. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/test_data_store.py +0 -0
  235. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/test_export.py +0 -0
  236. {equity_aggregator-0.1.4 → equity_aggregator-0.1.5}/tests/unit/storage/test_metadata.py +0 -0
@@ -77,6 +77,9 @@ jobs:
77
77
  sed -i "3s/.*/version = \"$NEW_VERSION\"/" pyproject.toml
78
78
  echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
79
79
 
80
+ - name: Update lockfile
81
+ run: uv lock
82
+
80
83
  - name: Configure git
81
84
  run: |
82
85
  git config user.name "github-actions[bot]"
@@ -84,7 +87,7 @@ jobs:
84
87
 
85
88
  - name: Commit and tag version bump
86
89
  run: |
87
- git add pyproject.toml
90
+ git add pyproject.toml uv.lock
88
91
  git commit -m "chore: bump version to ${{ steps.bump_version.outputs.new_version }} [skip ci]"
89
92
  git tag "v${{ steps.bump_version.outputs.new_version }}"
90
93
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: equity-aggregator
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Financial equity data aggregation toolkit
5
5
  Project-URL: Homepage, https://github.com/gregorykelleher/equity-aggregator
6
6
  Project-URL: Repository, https://github.com/gregorykelleher/equity-aggregator
@@ -45,7 +45,7 @@ Discovery feeds provide raw equity data from primary market sources:
45
45
  | 🇺🇸 SEC | United States | Securities and Exchange Commission - US-listed equities |
46
46
  | 🇺🇸 Stock Analysis | United States | Stock Analysis - Global equities |
47
47
  | 🇺🇸 TradingView | United States | TradingView - US-listed equities |
48
- | 🇩🇪 XETRA | Germany | Deutsche Börse electronic trading platform - Global listed equities |
48
+ | 🇩🇪 XETRA | International | Deutsche Börse electronic trading platform - Global listed equities |
49
49
 
50
50
  ### Enrichment Feeds
51
51
 
@@ -23,7 +23,7 @@ Discovery feeds provide raw equity data from primary market sources:
23
23
  | 🇺🇸 SEC | United States | Securities and Exchange Commission - US-listed equities |
24
24
  | 🇺🇸 Stock Analysis | United States | Stock Analysis - Global equities |
25
25
  | 🇺🇸 TradingView | United States | TradingView - US-listed equities |
26
- | 🇩🇪 XETRA | Germany | Deutsche Börse electronic trading platform - Global listed equities |
26
+ | 🇩🇪 XETRA | International | Deutsche Börse electronic trading platform - Global listed equities |
27
27
 
28
28
  ### Enrichment Feeds
29
29
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "equity-aggregator"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "Financial equity data aggregation toolkit"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The equity aggregator is a sophisticated financial data processing system that aggregates equity information from multiple discovery sources (LSEG, SEC, Stock Analysis, TradingView, XETRA) and enriches it with supplementary data from Yahoo Finance and Intrinio.
5
+ The equity aggregator is a sophisticated financial data processing system that aggregates equity information from multiple discovery sources (LSEG, SEC, Stock Analysis, TradingView, XETRA and Intrinio) and enriches it with supplementary data from Yahoo Finance and the Global LEI Foundation.
6
6
 
7
7
  ## Architecture & Design
8
8
 
@@ -15,6 +15,7 @@ src/equity_aggregator/
15
15
  ├── cli/ # Presentation Layer - User Interface
16
16
  ├── domain/ # Business Logic Layer - Core Domain
17
17
  │ ├── pipeline/ # Aggregation pipeline orchestration
18
+ │ ├── retrieval/ # Canonical equity download and retrieval
18
19
  │ └── _utils/ # Domain-specific utilities
19
20
  ├── adapters/ # Infrastructure Layer - External Integrations
20
21
  │ └── data_sources/ # Data source adapters
@@ -39,7 +40,7 @@ Raw Data Sources → Parse → Convert → Identify → Group → Enrich → Can
39
40
 
40
41
  Orchestrates parallel data fetching from discovery feeds:
41
42
 
42
- - Fetches data from LSEG, SEC, Stock Analysis, TradingView, and XETRA concurrently
43
+ - Fetches data from LSEG, SEC, Stock Analysis, TradingView, XETRA and Intrinio concurrently
43
44
  - Combines all feed data into a single stream for processing
44
45
  - Maintains feed source metadata for downstream processing
45
46
 
@@ -64,14 +65,13 @@ Standardises financial data to USD reference currency:
64
65
  Enriches records with global identification metadata:
65
66
 
66
67
  - Queries OpenFIGI API for FIGI identifiers
67
- - Adds CUSIP, ISIN, and other standard identifiers
68
68
  - Creates globally unique equity identities
69
69
 
70
70
  #### 5. **Group**
71
71
 
72
72
  Groups equities by Share Class FIGI:
73
73
 
74
- - Groups records with identical share_class_figi values
74
+ - Groups records with identical Share Class FIGI values
75
75
  - Preserves all discovery feed source data for later merging
76
76
  - Each group represents the same equity from multiple discovery sources
77
77
  - Yields groups as `list[RawEquity]` for enrichment processing
@@ -80,11 +80,10 @@ Groups equities by Share Class FIGI:
80
80
 
81
81
  Fetches enrichment data and performs comprehensive single merge:
82
82
 
83
- - Extracts representative identifiers from discovery sources using merge algorithms
84
- - Queries enrichment feeds (Yahoo Finance, Intrinio) with these identifiers
83
+ - Fetches representative identifiers from discovery data sources
84
+ - Queries enrichment feeds (Yahoo Finance, Global LEI Foundation) using these identifiers
85
85
  - Performs single merge of all sources (discovery + enrichment) for optimal data quality
86
- - Uses same merge logic for identifiers and final merge (mode for IDs, fuzzy clustering for names, frequency for symbols)
87
- - Applies controlled concurrency to respect API limits
86
+ - Applies controlled concurrency to enrichment feeds to respect API limits
88
87
 
89
88
  #### 7. **Canonicalise**
90
89
 
@@ -102,7 +101,7 @@ The pipeline uses asynchronous operations to process thousands of equity records
102
101
 
103
102
  **Parallel Data Fetching**
104
103
 
105
- - All discovery feeds (LSEG, SEC, Stock Analysis, TradingView, XETRA) are fetched simultaneously
104
+ - All discovery feeds (LSEG, SEC, Stock Analysis, TradingView, XETRA, Intrinio) are fetched simultaneously
106
105
 
107
106
  **Streaming Pipeline**
108
107
 
@@ -110,7 +109,9 @@ The pipeline uses asynchronous operations to process thousands of equity records
110
109
 
111
110
  **Controlled Concurrency**
112
111
 
113
- - External API calls (OpenFIGI, Yahoo Finance, Intrinio) use semaphores to limit concurrent requests and respect rate limits
112
+ - External API calls (OpenFIGI, Yahoo Finance, GLEIF) use semaphores to limit concurrent requests and respect rate limits
113
+ - Each enrichment feed has a configurable concurrency limit
114
+ - Fetch operations include timeout protection to prevent indefinite blocking
114
115
 
115
116
  **Non-blocking Operations**
116
117
 
@@ -145,11 +146,14 @@ schemas/
145
146
  ├── raw.py # RawEquity - intermediate pipeline format
146
147
  ├── canonical.py # CanonicalEquity - final standardised format
147
148
  ├── types.py # Type definitions and validators
149
+ ├── validators.py # Reusable validators for identifiers and financial data
148
150
  └── feeds/ # Feed-specific data models
149
151
  ├── lseg_feed_data.py
150
152
  ├── sec_feed_data.py
151
153
  ├── stock_analysis_feed_data.py
152
154
  ├── tradingview_feed_data.py
155
+ ├── gleif_feed_data.py
156
+ ├── feed_validators.py
153
157
  ├── xetra_feed_data.py
154
158
  ├── yfinance_feed_data.py
155
159
  └── intrinio_feed_data.py
@@ -158,6 +162,7 @@ schemas/
158
162
  ### Critical Role of Schemas
159
163
 
160
164
  #### 1. **Data Validation at Boundaries**
165
+
161
166
  Each feed has a dedicated Pydantic schema that:
162
167
  - Validates incoming data structure and types
163
168
  - Normalises field names and formats
@@ -204,6 +209,7 @@ class LsegFeedData(BaseModel):
204
209
  - **Stock Analysis**: US equities with comprehensive financial metrics
205
210
  - **TradingView**: US equities with comprehensive financial metrics
206
211
  - **XETRA**: Deutsche Börse Stock Exchange
212
+ - **Intrinio**: US financial data API providing company, securities, and real-time quote data
207
213
 
208
214
  **Characteristics**:
209
215
 
@@ -213,12 +219,12 @@ class LsegFeedData(BaseModel):
213
219
  ### Enrichment Feeds (Supplementary Sources)
214
220
 
215
221
  - **Yahoo Finance**: Market data and financial metrics
216
- - **Intrinio**: Market data and financial metrics
222
+ - **Global LEI Foundation**: ISIN->LEI mapping for Legal Entity Identifier enrichment
217
223
 
218
224
  **Characteristics**
219
225
 
220
226
  - Provides additional financial metrics (market cap, analyst ratings, etc.)
221
- - Uses representative identifiers from discovery sources for lookup
227
+ - Uses representative identifiers from discovery sources for look-up
222
228
  - Applied after grouping but before final merge
223
229
 
224
230
  ## Equity Aggregator Components
@@ -1,8 +1,8 @@
1
1
  # transforms/test_enrich.py
2
2
 
3
3
  import asyncio
4
- import os
5
- from collections.abc import AsyncIterable
4
+ from collections.abc import AsyncIterable, AsyncIterator
5
+ from contextlib import AsyncExitStack, asynccontextmanager
6
6
  from decimal import Decimal
7
7
 
8
8
  import pytest
@@ -10,8 +10,13 @@ import pytest
10
10
  from equity_aggregator.domain._utils import EquityIdentifiers, extract_identifiers
11
11
  from equity_aggregator.domain.pipeline.transforms.enrich import (
12
12
  EnrichmentFeed,
13
+ FeedSpec,
13
14
  _enrich_equity_group,
14
15
  _enrich_from_feed,
16
+ _init_feed,
17
+ _open_feeds,
18
+ _process_stream,
19
+ _rate_limited,
15
20
  _safe_fetch,
16
21
  _to_usd,
17
22
  _validate,
@@ -49,6 +54,30 @@ class ErrorFeedData:
49
54
  raise _ValidationError("validation failed")
50
55
 
51
56
 
57
+ class StubFeed:
58
+ """
59
+ Test feed that returns a fixed response.
60
+ """
61
+
62
+ def __init__(self, response: dict[str, object]) -> None:
63
+ self.response = response
64
+
65
+ async def fetch_equity(self, **kwargs: object) -> dict[str, object]:
66
+ return self.response
67
+
68
+
69
+ def stub_feed_factory(response: dict[str, object]) -> object:
70
+ """
71
+ Create an async context manager factory for StubFeed.
72
+ """
73
+
74
+ @asynccontextmanager
75
+ async def factory() -> AsyncIterator[StubFeed]:
76
+ yield StubFeed(response)
77
+
78
+ return factory
79
+
80
+
52
81
  def test_extract_identifiers_single_source() -> None:
53
82
  """
54
83
  ARRANGE: single RawEquity with identifiers
@@ -301,21 +330,30 @@ def test_safe_fetch_success_returns_dict() -> None:
301
330
  assert actual == {"foo": "bar"}
302
331
 
303
332
 
304
- def test_enrich_empty_stream_yields_nothing() -> None:
333
+ def test_process_stream_empty_stream_yields_nothing() -> None:
305
334
  """
306
- ARRANGE: async stream that never yields
307
- ACT: run enrich()
335
+ ARRANGE: async stream that never yields and test feed
336
+ ACT: run _process_stream
308
337
  ASSERT: yields empty list
309
338
  """
310
339
 
311
- os.environ.setdefault("INTRINIO_API_KEY", "DUMMY_KEY")
340
+ async def good_fetcher(
341
+ symbol: str,
342
+ name: str,
343
+ isin: str | None,
344
+ cusip: str | None,
345
+ **kwargs: object,
346
+ ) -> dict[str, object]:
347
+ return {}
348
+
349
+ mock_feed = EnrichmentFeed(fetch=good_fetcher, model=GoodFeedData)
312
350
 
313
351
  async def empty_src() -> AsyncIterable[list[RawEquity]]:
314
352
  if False:
315
353
  yield
316
354
 
317
355
  async def runner() -> list[RawEquity]:
318
- return [equity async for equity in enrich(empty_src())]
356
+ return [equity async for equity in _process_stream(empty_src(), (mock_feed,))]
319
357
 
320
358
  actual = asyncio.run(runner())
321
359
  assert actual == []
@@ -728,15 +766,12 @@ def test_to_usd_converts_currency() -> None:
728
766
  assert actual.currency == "USD"
729
767
 
730
768
 
731
- @pytest.mark.skip(reason="TODO - revisit this unit test")
732
- def test_enrich_passes_through_groups() -> None:
769
+ def test_process_stream_passes_through_groups() -> None:
733
770
  """
734
- ARRANGE: async stream of equity groups
735
- ACT: run enrich() over that stream
736
- ASSERT: yields merged equities
771
+ ARRANGE: async stream of equity groups and test feeds
772
+ ACT: run _process_stream over that stream
773
+ ASSERT: yields merged equities for each group
737
774
  """
738
- os.environ.setdefault("INTRINIO_API_KEY", "DUMMY_KEY")
739
-
740
775
  first_equity = RawEquity(
741
776
  name="ONE",
742
777
  symbol="ONE",
@@ -757,12 +792,30 @@ def test_enrich_passes_through_groups() -> None:
757
792
  market_cap=Decimal("200"),
758
793
  )
759
794
 
795
+ async def good_fetcher(
796
+ symbol: str,
797
+ name: str,
798
+ isin: str | None,
799
+ cusip: str | None,
800
+ **kwargs: object,
801
+ ) -> dict[str, object]:
802
+ return {
803
+ "name": name,
804
+ "symbol": symbol,
805
+ "mics": ["XLON"],
806
+ "currency": "USD",
807
+ "last_price": Decimal("10"),
808
+ "market_cap": Decimal("1000"),
809
+ }
810
+
811
+ mock_feed = EnrichmentFeed(fetch=good_fetcher, model=GoodFeedData)
812
+
760
813
  async def source() -> AsyncIterable[list[RawEquity]]:
761
814
  yield [first_equity]
762
815
  yield [second_equity]
763
816
 
764
817
  async def runner() -> list[RawEquity]:
765
- return [equity async for equity in enrich(source())]
818
+ return [equity async for equity in _process_stream(source(), (mock_feed,))]
766
819
 
767
820
  actual = asyncio.run(runner())
768
821
 
@@ -840,3 +893,146 @@ def test_enrich_from_feed_returns_validated_when_currency_is_none() -> None:
840
893
  actual = asyncio.run(_enrich_from_feed(identifiers, mock_feed))
841
894
 
842
895
  assert actual.lei == "G1MSYD2DQZX30V5DMX12"
896
+
897
+
898
+ def test_rate_limited_passes_through_result() -> None:
899
+ """
900
+ ARRANGE: async function wrapped with _rate_limited
901
+ ACT: call the wrapped function
902
+ ASSERT: returns the original function's result
903
+ """
904
+
905
+ async def simple_fetcher(**kwargs: object) -> dict[str, object]:
906
+ return {"result": "success"}
907
+
908
+ semaphore = asyncio.Semaphore(10)
909
+ wrapped = _rate_limited(simple_fetcher, semaphore)
910
+
911
+ actual = asyncio.run(wrapped(symbol="TEST"))
912
+
913
+ assert actual == {"result": "success"}
914
+
915
+
916
+ def test_rate_limited_serialises_calls_with_semaphore_of_one() -> None:
917
+ """
918
+ ARRANGE: semaphore with limit of 1, two concurrent calls
919
+ ACT: start both calls simultaneously
920
+ ASSERT: first call completes before second call starts
921
+ """
922
+ call_order: list[str] = []
923
+
924
+ async def tracking_fetcher(order: str, **kwargs: object) -> dict[str, object]:
925
+ call_order.append(f"{order}_start")
926
+ await asyncio.sleep(0.01)
927
+ call_order.append(f"{order}_end")
928
+ return {}
929
+
930
+ semaphore = asyncio.Semaphore(1)
931
+ wrapped = _rate_limited(tracking_fetcher, semaphore)
932
+
933
+ async def runner() -> None:
934
+ await asyncio.gather(
935
+ wrapped(order="first"),
936
+ wrapped(order="second"),
937
+ )
938
+
939
+ asyncio.run(runner())
940
+
941
+ assert call_order.index("first_end") < call_order.index("second_start")
942
+
943
+
944
+ def test_rate_limited_raises_timeout_error() -> None:
945
+ """
946
+ ARRANGE: slow function with short timeout
947
+ ACT: call the wrapped function
948
+ ASSERT: raises TimeoutError
949
+ """
950
+
951
+ async def slow_fetcher(**kwargs: object) -> dict[str, object]:
952
+ await asyncio.sleep(0.1)
953
+ return {}
954
+
955
+ semaphore = asyncio.Semaphore(10)
956
+ wrapped = _rate_limited(slow_fetcher, semaphore, timeout=0.01)
957
+
958
+ with pytest.raises(TimeoutError):
959
+ asyncio.run(wrapped())
960
+
961
+
962
+ def test_open_feeds_yields_single_feed_for_single_spec() -> None:
963
+ """
964
+ ARRANGE: single FeedSpec with stub factory
965
+ ACT: open feeds using _open_feeds
966
+ ASSERT: yields tuple containing one feed
967
+ """
968
+ specs = (FeedSpec(stub_feed_factory({}), GoodFeedData, 5),)
969
+
970
+ async def runner() -> int:
971
+ async with _open_feeds(specs) as feeds:
972
+ return len(feeds)
973
+
974
+ actual = asyncio.run(runner())
975
+
976
+ assert actual == 1
977
+
978
+
979
+ def test_open_feeds_fetch_returns_stub_response() -> None:
980
+ """
981
+ ARRANGE: FeedSpec with stub factory returning specific data
982
+ ACT: open feeds and call fetch
983
+ ASSERT: returns the stub response
984
+ """
985
+ specs = (FeedSpec(stub_feed_factory({"key": "value"}), GoodFeedData, 5),)
986
+
987
+ async def runner() -> dict[str, object]:
988
+ async with _open_feeds(specs) as feeds:
989
+ return await feeds[0].fetch(symbol="X", name="X")
990
+
991
+ actual = asyncio.run(runner())
992
+
993
+ assert actual == {"key": "value"}
994
+
995
+
996
+ def test_init_feed_returns_functional_enrichment_feed() -> None:
997
+ """
998
+ ARRANGE: FeedSpec with stub factory
999
+ ACT: initialise feed using _init_feed
1000
+ ASSERT: fetch returns the stub response
1001
+ """
1002
+ spec = FeedSpec(stub_feed_factory({"result": "ok"}), GoodFeedData, 5)
1003
+
1004
+ async def runner() -> dict[str, object]:
1005
+ async with AsyncExitStack() as stack:
1006
+ feed = await _init_feed(spec, stack)
1007
+ return await feed.fetch(symbol="X", name="X")
1008
+
1009
+ actual = asyncio.run(runner())
1010
+
1011
+ assert actual == {"result": "ok"}
1012
+
1013
+
1014
+ def test_enrich_yields_merged_equity() -> None:
1015
+ """
1016
+ ARRANGE: async stream with one equity group
1017
+ ACT: run enrich() over that stream
1018
+ ASSERT: yields the merged equity (enrichment failures are handled gracefully)
1019
+ """
1020
+ equity = RawEquity(
1021
+ name="TEST",
1022
+ symbol="TEST",
1023
+ share_class_figi="BBG000TEST01",
1024
+ mics=["XNAS"],
1025
+ currency="USD",
1026
+ last_price=Decimal("100"),
1027
+ market_cap=Decimal("1000000"),
1028
+ )
1029
+
1030
+ async def source() -> AsyncIterable[list[RawEquity]]:
1031
+ yield [equity]
1032
+
1033
+ async def runner() -> list[RawEquity]:
1034
+ return [e async for e in enrich(source())]
1035
+
1036
+ actual = asyncio.run(runner())
1037
+
1038
+ assert actual[0].symbol == "TEST"
@@ -160,7 +160,7 @@ wheels = [
160
160
 
161
161
  [[package]]
162
162
  name = "equity-aggregator"
163
- version = "0.1.3"
163
+ version = "0.1.5"
164
164
  source = { editable = "." }
165
165
  dependencies = [
166
166
  { name = "httpx", extra = ["http2"] },