Repository navigation
feat(fmp-py): add AsyncFmpClient asyncio interface to Python bindings - #423
Merged
Merged
Conversation
This change introduces `AsyncFmpClient`, an asyncio-compatible counterpart to `FmpClient` whose endpoint methods are coroutines to `await`. It shares the same constructor keywords, environment fallbacks, defaults, and exceptions as the synchronous client, and runs requests on the process-wide runtime so awaiting never blocks the event loop and concurrent coroutines run in parallel. **New async client:** * Added `AsyncFmpClient` with `close()`, `await aclose()`, and `async with` support via `__aenter__`/`__aexit__`, mirroring the synchronous client's connection lifecycle and `on_response` hook behaviour. * Enabled the `experimental-async` pyo3 feature required for the async methods. **Shared construction:** * Extracted the constructor validation and builder assembly into a reusable `build_handle` helper so both `FmpClient` and `AsyncFmpClient` construct their client handle through the same path. **Module registration:** * Registered `AsyncFmpClient` in the native module and re-exported it from the `fmp` and `fmp.client` facades alongside `FmpClient`.
Extend the Python bindings to expose an `AsyncFmpClient` that mirrors the existing synchronous client. Every namespace node now renders two classes: the sync `<Path>Namespace` and its `Async<Path>Namespace` twin, each with a getter on the respective client. The async wrappers take owned `String` arguments (coroutine arguments must be `'static`), lend them back to the shared query builders, and run each request as a task on the process-wide runtime instead of blocking. **Runtime support:** * Add `spawn`, the asynchronous counterpart of `block_on`, which runs an operation against a cached client as a task on the shared runtime and awaits it without blocking the event loop thread. * Introduce `AbortOnDrop` so a cancelled Python coroutine aborts its in-flight request rather than detaching it. **Code generation:** * Factor the method renderer into a shared header, call, and query-builder path reused by both `render_method` and the new `render_async_method`. * Add `async_struct_name_for` and emit both class flavors per node, wiring the `Async` twins into registration, client getters, and parent getters. * Register every `Async*Namespace` class alongside its sync counterpart. Dynamic-row endpoints reattach for the `dict` conversion once the task resolves. Tests cover the async class shape, owned-argument lending, and registration wiring.
This change exposes an asyncio-based client across the FMP Python bindings, giving users a fully asynchronous interface alongside the existing synchronous one. **New async client:** * Added `AsyncFmpClient` to the top-level `fmp` package and `fmp.client`, mirroring `FmpClient` with the same constructor keywords, environment fallbacks, defaults, and exceptions, but with every endpoint method as an awaitable coroutine. * The client shares the process-wide runtime so concurrent coroutines run their requests in parallel without blocking the event loop, supports cancellation, `close()`/`await aclose()`, and `async with` usage. **Async namespaces:** * Added `Async*Namespace` type stubs for every endpoint group (analyst, bulk, calendar, chart, company, quote, statements, and all others), each providing the same methods as its synchronous counterpart as coroutines. * Re-exported the new async classes through the generated `__init__.py` and `__init__.pyi` modules and their `__all__` lists.
Add Rust unit tests for the `AbortOnDrop` guard, verifying that dropping the guard aborts the spawned task and tears down its state, that the guard resolves to the task output, and that a panicking task surfaces as an error. Extend the Python fixture server to simulate slow and abandoned requests: routes now carry a `delay`, the server tracks `disconnects` for clients that hang up before a delayed answer, and a larger listen backlog absorbs bursts of concurrent connections. Add an `async_client` fixture returning an `AsyncFmpClient` with the same unauthenticated configuration as `client`, and document the new behaviour in the fixture docstrings.
Add a new test file `crates/fmp-py/tests/test_async_client.py` to cover the asynchronous client functionality. This ensures the async client behaves correctly and guards against regressions in future changes.
Replace the "no async facade" notes in the fmp-py and wiki Python docs with coverage of `AsyncFmpClient`, the asyncio twin that shares the same keywords, namespaces, and methods as `FmpClient` with coroutine endpoints. Describe its event loop, concurrency, cancellation, error, and lifecycle behavior, and add an Airflow deferrable operator example for use inside a trigger. Add an `async_client.py` example and list it in the examples README. close #422
benbenbang
enabled auto-merge (rebase)
October 11, 2026 17:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The FMP Python bindings previously offered only a synchronous client, forcing users to block threads or wrap calls themselves to use the API from asyncio code. This branch adds a fully asynchronous
AsyncFmpClientthat mirrors the synchronous client so callers can await endpoints directly, run requests concurrently, and integrate with event-loop-based frameworks.Async client:
AsyncFmpClientwith coroutine endpoint methods that share the same constructor keywords, environment fallbacks, defaults, exceptions, andon_responsehook asFmpClient.close()/await aclose(), andasync with.AsyncFmpClientin the native module and re-export it from thefmpandfmp.clientfacades.Async namespaces:
Async*Namespacetwin for every endpoint group, each exposing the same methods as its synchronous counterpart as coroutines, wired into client and parent getters.Async*Namespacetype stubs and re-export the new classes through the generated__init__.py/__init__.pyimodules and their__all__lists.Runtime and code generation:
spawn, the async counterpart ofblock_on, and anAbortOnDropguard so a cancelled Python coroutine aborts its in-flight request.build_handlehelper and factor the method renderer into shared paths reused by sync and async generation.Tests and docs:
AbortOnDropguard and a Python async client test suite, and extend the fixture server to simulate slow and abandoned requests with anasync_clientfixture.AsyncFmpClient, add anasync_client.pyexample, and document an Airflow deferrable operator use case.close #422