Skip to content

refactor: dedup B3 quote guard and push anbima emissor into the source#31

Merged
robertoecf merged 1 commit into
mainfrom
refactor/source-layer-cleanup
Jun 27, 2026
Merged

refactor: dedup B3 quote guard and push anbima emissor into the source#31
robertoecf merged 1 commit into
mainfrom
refactor/source-layer-cleanup

Conversation

@robertoecf

@robertoecf robertoecf commented Jun 27, 2026

Copy link
Copy Markdown
Owner

What

Two contained cleanups from the review of #29 (items 2 and 3 of #30):

  • Share the B3 live-quote guard. New api/_b3_common.py exposes resolve_quotes() and MAX_TICKERS. routers/b3.py and mcp_app.py both resolve the lazy yfinance import through it, so the guard and the pip install 'openfindata[b3]' hint live in one place instead of two near-copies.
  • Push the debenture emissor filter into the source. anbima.get_debentures(...) now takes emissor, and both the REST router and the MCP tool drop their hand-rolled filter loop and pass it through, matching every other source-level filter (FCA ticker, IPE categoria, FIP quarter).

Net -23 lines, no behaviour change. ruff format / ruff check / mypy --strict / pytest -m "not integration" (252) green.

Item 1 of #30 deliberately left out

The third item (push limit into ~18 source functions) is not included here. The unbounded-output bug that motivated it was already fixed in #29 (the MCP tools apply [:limit]). What remains is purely moving the slice into the source functions, which (a) gives no perf win (the CSV/zip is parsed in full regardless), (b) does not unify the REST routers, which need skip+limit via _page, and (c) adds a limit param to 18 public source functions for marginal DRY. Recommend closing #30 with items 2-3, or splitting item 1 into its own ticket if still wanted.

Summary by CodeRabbit

  • New Features

    • Added support for filtering debenture results by issuer name.
    • Unified B3 quote handling so single, multiple, and historical quote requests share the same backend selection logic.
  • Bug Fixes

    • Improved error messages when optional B3 quote support is unavailable, including setup guidance.
    • Enforced a consistent maximum number of tickers per request across quote endpoints.

New api/_b3_common.py exposes resolve_quotes() and MAX_TICKERS; routers/b3.py and mcp_app.py both resolve the lazy yfinance import through it, so the guard and the install hint live in one place instead of two near-copies.

anbima.get_debentures now takes an emissor filter; the REST router and the MCP tool drop their hand-rolled loop and pass it through, matching every other source-level filter.

Items 2 and 3 of #30. No behavior change; ruff/mypy/pytest (252) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

B3 quote resolution was centralized into a shared helper used by the MCP app and B3 router handlers, with a shared max-ticker limit. ANBIMA debenture filtering now happens in get_debentures, and both ANBIMA callers pass emissor through.

Changes

B3 quote resolver centralization

Layer / File(s) Summary
Shared helper and app wiring
src/findata/api/_b3_common.py, src/findata/api/mcp_app.py
MAX_TICKERS and resolve_quotes() are added, and mcp_app.py imports them while removing the local B3 quote fallback and limit constant.
B3 handlers use shared resolver
src/findata/api/mcp_app.py, src/findata/api/routers/b3.py
The B3 quote handlers call resolve_quotes() and validate ticker counts against MAX_TICKERS.

ANBIMA debenture emissor filtering

Layer / File(s) Summary
Debenture source filter
src/findata/sources/anbima/indices.py
get_debentures() accepts emissor and filters parsed rows by a case-insensitive issuer substring when provided.
ANBIMA callers pass emissor
src/findata/api/mcp_app.py, src/findata/api/routers/anbima.py
The ANBIMA debenture route and tool pass emissor into get_debentures() and keep the final limit slice.

Sequence Diagram(s)

sequenceDiagram
  participant b3_quote
  participant resolve_quotes
  participant quotes_module as findata.sources.b3.quotes
  b3_quote->>resolve_quotes: resolve quotes backend
  resolve_quotes->>quotes_module: lazy import
  quotes_module-->>b3_quote: get_quote / get_history / get_multiple_quotes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • robertoecf/OpenFinData#22: It touches get_debentures in the same ANBIMA path, so it is code-adjacent to the source-layer filtering change here.

Poem

A bunny hops through B3 glow,
with one shared quote path now in tow.
Debentures whisper, “find my issuer,”
and the source nods back, a bit slicker. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: deduplicating the B3 quote guard and moving the ANBIMA emissor filter into the source layer.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/source-layer-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the B3 live-quote logic by consolidating shared helpers into a new _b3_common.py module, and optimizes debenture querying by pushing the emissor filtering logic down to the source layer in get_debentures. A critical issue was identified in resolve_quotes() where the lazy import of quotes will not raise an ImportError when yfinance is missing, potentially causing unexpected 500 or 404 errors instead of the intended 503; a suggestion was provided to explicitly import yfinance in the try block.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +17 to +26
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
try:
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc
return quotes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The quotes module (src/findata/sources/b3/quotes.py) imports yfinance lazily inside its functions rather than at the module level. As a result, from findata.sources.b3 import quotes will not raise an ImportError when yfinance is missing. This means resolve_quotes() will successfully return the module, and the missing dependency will only trigger a RuntimeError later when a quote is actually fetched. This leads to a 500 Internal Server Error in the MCP app or a misleading 404 Ticker not found in the REST router instead of the intended 503 Service Unavailable with the installation hint.

To fix this, explicitly attempt to import yfinance inside the try block to correctly detect if the optional extra is missing.

Suggested change
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
try:
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc
return quotes
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
try:
import yfinance # noqa: F401
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc
return quotes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/findata/api/_b3_common.py`:
- Around line 17-25: The missing-[b3] path is no longer caught by the
ImportError in resolve_quotes(), because importing findata.sources.b3.quotes now
succeeds before yfinance is needed. Update resolve_quotes() to verify the
optional dependency at import time by triggering the yfinance-backed path or
calling the helper that loads it from quotes, and keep raising the shared
HTTPException 503 with the install hint when the extra is absent. Use the
existing resolve_quotes() helper and the lazy import flow in
findata.sources.b3.quotes to make sure callers always get the intended 503
instead of a later RuntimeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa37420e-b1a6-4088-90b3-bab63be4964e

📥 Commits

Reviewing files that changed from the base of the PR and between 6db63f0 and 3f88dd7.

📒 Files selected for processing (5)
  • src/findata/api/_b3_common.py
  • src/findata/api/mcp_app.py
  • src/findata/api/routers/anbima.py
  • src/findata/api/routers/b3.py
  • src/findata/sources/anbima/indices.py

Comment on lines +17 to +25
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
try:
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The missing-[b3] path no longer turns into the shared 503 response.

findata.sources.b3.quotes now defers the yfinance import to _import_yfinance() in src/findata/sources/b3/quotes.py:70-77, so from findata.sources.b3 import quotes succeeds even when the extra is absent. In that case this helper returns normally, and the later quote call raises RuntimeError, which the REST/MCP callers currently translate into 404/500 instead of the advertised 503 install hint.

Possible fix
+from importlib.util import find_spec
 from typing import Any
 
 from fastapi import HTTPException
@@
 def resolve_quotes() -> Any:
     """Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
+    if find_spec("yfinance") is None:
+        raise HTTPException(
+            status_code=503,
+            detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
+        )
     try:
         from findata.sources.b3 import quotes
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
try:
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc
from importlib.util import find_spec
from typing import Any
from fastapi import HTTPException
def resolve_quotes() -> Any:
"""Return the yfinance-backed quotes module, or raise 503 if the extra is missing."""
if find_spec("yfinance") is None:
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
)
try:
from findata.sources.b3 import quotes
except ImportError as exc: # pragma: no cover - only without the [b3] extra
raise HTTPException(
status_code=503,
detail="B3 live quotes need the optional extra: pip install 'openfindata[b3]'",
) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/findata/api/_b3_common.py` around lines 17 - 25, The missing-[b3] path is
no longer caught by the ImportError in resolve_quotes(), because importing
findata.sources.b3.quotes now succeeds before yfinance is needed. Update
resolve_quotes() to verify the optional dependency at import time by triggering
the yfinance-backed path or calling the helper that loads it from quotes, and
keep raising the shared HTTPException 503 with the install hint when the extra
is absent. Use the existing resolve_quotes() helper and the lazy import flow in
findata.sources.b3.quotes to make sure callers always get the intended 503
instead of a later RuntimeError.

@robertoecf robertoecf merged commit b43c6d5 into main Jun 27, 2026
7 checks passed
@robertoecf robertoecf deleted the refactor/source-layer-cleanup branch June 27, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant