Skip to content

feat: Python bindings via PyO3 + maturin (resolves #138)#140

Open
Johnson-f wants to merge 1 commit into
Verdenroz:masterfrom
Johnson-f:feat/python-bindings-crate-and-library
Open

feat: Python bindings via PyO3 + maturin (resolves #138)#140
Johnson-f wants to merge 1 commit into
Verdenroz:masterfrom
Johnson-f:feat/python-bindings-crate-and-library

Conversation

@Johnson-f
Copy link
Copy Markdown
Contributor

Summary

Adds a new workspace crate finance-query-python that compiles the Rust library
into a native Python extension via PyO3 + maturin, published to PyPI as
finance-query-py. Closes #138.

Verified live against Yahoo Finance / Alternative.me / SEC EDGAR — 51/51 surface
tests pass
(see Test plan below).

What ships

Surface Coverage
Ticker 17 methods: new, builder, symbol, quote, chart, chart_range, dividends, splits, capital_gains, financials, news, recommendations, edgar_submissions, 3× clear_cache*
TickerBuilder 7 chainable setters + async build()
Tickers (batch) new, symbols, __len__, quotes, charts + BatchResult { data, errors }
finance.* 12 free functions: search, screener, trending, fear_and_greed, lookup, market_summary, hours, sector, currencies, news, industry, exchanges
Enums 11 (Interval, TimeRange, Frequency, StatementType, Region, ValueFormat, Sector, Screener, ExchangeCode, Industry, FearGreedLabel)
Exceptions 6 typed (FinanceQueryError + NetworkError, RateLimitError, SymbolNotFound, ParseError, ConfigError)
Models ~110 PyModel-derived wrappers across all model modules; to_dict() everywhere, to_dataframe() on Chart, Candle, Dividend, Split, CapitalGain, OptionContract, FinancialStatement, News, SearchQuote, TrendingQuote, etc.
Helpers enable_logging(level), edgar_init(email), edgar_init_with_config(...)

Architecture

  • PyModel proc-macro in finance-query-derive (behind a new python feature) generates Py* wrappers from existing Rust structs. Attributes: eq, rename, dataframe = "columns", dataframe_from = "<field>". Handles primitives, String, Vec<T>, Option<T>, HashMap<K, V>, nested PyModel types, FormattedValue<T> (via concrete PyFormattedValueF64/I64/U64/String), and serde_json::Value (auto-pythonized).
  • Core crate (finance-query) gains an optional python feature pulling in pyo3 0.27, pyo3-polars 0.26, pythonize 0.27. Default builds unchanged — Rust users pay nothing.
  • Async bridging via pyo3-async-runtimes 0.27 on a single shared tokio runtime initialized at module import.
  • abi3-py39 wheels — one wheel per platform covers Python 3.9 → 3.13+.

Repository layout

finance-query-python/              # NEW workspace crate (cdylib)
├── src/                           # lib.rs, runtime.rs, error.rs, ticker.rs,
│                                  # tickers.rs, finance.rs, enums.rs, edgar.rs,
│                                  # logging_bridge.rs, models.rs
├── finance_query/                 # pure-Python package
│   ├── __init__.py
│   ├── _finance_query.pyi         # type stubs (mypy --strict clean)
│   └── py.typed
├── docs/examples/quickstart.ipynb # Jupyter walkthrough
├── tests/                         # pytest: unit + smoke + parity layers
├── Cargo.toml, pyproject.toml, Makefile, README.md

finance-query-derive/              # extended with PyModel derive
src/constants_py.rs                # NEW: 10 Python enum mirrors
src/models/quote/formatted_value.rs# NEW: PyFormattedValue* family
src/models/**/*.rs                 # ~50 files annotated with #[derive(PyModel)]
tools/record-parity-fixtures/      # NEW: Rust binary for cross-lang fixtures
.github/workflows/python-wheels.yml# NEW: 6-job CI (test, parity, build×3, smoke×20, publish)
docs/migration-from-yfinance.md    # NEW: yfinance → finance-query-py cheat sheet

Phase 2 follow-ups (not in this PR)

  • ticker.options()Options wraps pub(crate) OptionChainContainer; needs hand-written wrapper or making the inner type public.
  • ticker.edgar_company_facts()CompanyFacts.HashMap<String, FactsByTaxonomy> (tuple struct value); PyModel needs to support that combo.
  • Streaming PriceStream (always planned for Phase 2).
  • backtesting, risk namespace ergonomics.

Test plan

  • cargo check --features python — clean (40 doc-comment warnings, no errors)
  • cargo test -p finance-query-derive --features python — 3 trybuild tests pass (ui_simple, ui_collections, ui_full)
  • cd finance-query-python && maturin develop --release — builds wheel cp39-abi3-<platform>
  • python3 -m pytest -m "not network" — 34 passed
  • python3 -m mypy — clean against the stubs
  • Live network test — 51/51 (see comment below for full output)
  • CI: 5 platforms × abi3 → smoke matrix on 4 OS × 5 Python versions
  • PyPI trusted-publisher one-time setup before first finance-query-py-v* tag (see finance-query-python/README.md → Releasing)

Quickstart for reviewers

cd finance-query-python
maturin develop --release
python3 -c "
import asyncio
from finance_query import Ticker, Interval, TimeRange

async def main():
    t = await Ticker.new('AAPL')
    q = await t.quote()
    print(f'{q.symbol}: {q.short_name} @ \${q.regular_market_price.raw}')
    chart = await t.chart(Interval.OneDay, TimeRange.OneMonth)
    print(chart.to_dataframe().head())

asyncio.run(main())
"

🤖 Generated with Claude Code

@Verdenroz
Copy link
Copy Markdown
Owner

I hope to look at this around Wednesday, worst case Friday as the multi-provider architecture is revealing a lot of tech debt that I'm only reaping now 😭

@Verdenroz
Copy link
Copy Markdown
Owner

Finally figured out a decent structure. Given the sheer scope of the refactoring, it might honestly be best to just checkout from master on a fresh branch.

If you prefer to continue working on this branch, I can help rebase your branch if needed. Sorry for the conflicts 🙏 !

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.

[FEATURE REQUEST] Python Bindings

2 participants