Skip to content

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

Draft
Johnson-f wants to merge 2 commits into
Verdenroz:masterfrom
Johnson-f:feat/python-bindings-crate-and-library
Draft

Johnson-f wants to merge 2 commits 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 🙏 !

Johnson-f and others added 2 commits June 23, 2026 20:46
Bring the finance-query-python bindings to full parity with the Rust
Ticker/Tickers/analytics API (deferred: Python-authored strategies,
streaming, Arrow, Tickers.add/remove_symbols).

- Tickers batch: dividends, splits, capital_gains, recommendations,
  financials, spark, options, chart, charts_range, news, quote(symbol),
  clear_cache helpers
- Ticker reads: options, dividend_analytics, news_sentiment, filings,
  edgar_company_facts
- Expose provider_id (PyProvider feature-gated mirror) and article sentiment
- Analytics: indicators + indicator (PyIndicator, 40 ctors) + risk
- Backtest: 6 prebuilt strategies + backtest/backtest_with_benchmark/portfolio
- PyModel macro: handle generic F::Value formats + add py_model(skip)
- Hand-written wrappers for macro-resistant types (CompanyFacts,
  IndicatorResult, BacktestResult/PortfolioResult)
- Fix: enum mirrors now hashable (#[pyclass(hash, frozen)] + Hash)

cargo default/python/python+sentiment/workspace clean; 144 lib tests;
68 pytest passed/10 skipped; mypy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Johnson-f
Johnson-f force-pushed the feat/python-bindings-crate-and-library branch from d4a01ed to 49795c3 Compare June 24, 2026 07:55

@github-advanced-security github-advanced-security AI 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.

zizmor found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@Johnson-f
Johnson-f marked this pull request as draft September 12, 2026 19:17
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] Python Bindings

3 participants