Skip to content

Commit e22564a

Browse files
committed
Add async support for external api calls
1 parent 884bc62 commit e22564a

File tree

10 files changed

+1163
-52
lines changed

10 files changed

+1163
-52
lines changed

docs/source/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ Changelog
33

44
All notable changes to this project will be documented in this file.
55

6+
[0.8.0] - 2025-10-08
7+
--------------------
8+
9+
Added
10+
^^^^^
11+
- Added async support for external api calls. :doc:`Check it out <options/external_api>`
12+
613

714
[0.7.3] - 2025-10-08
815
--------------------

docs/source/options/external_api.rst

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ Fields of `ExternalApiConfig`
4444
* - ``api_key``
4545
- ``Optional[str]``
4646
- API key for authorization, sent in the ``Authorization`` header.
47+
* - ``async_mode``
48+
- ``bool``
49+
- Enable async HTTP client (httpx) for parallel requests. Default: ``False``.
50+
* - ``timeout``
51+
- ``int``
52+
- Timeout in seconds for HTTP requests. Default: ``30``.
53+
* - ``retry_count``
54+
- ``int``
55+
- Number of retry attempts on failure. Default: ``0``.
56+
* - ``retry_delay``
57+
- ``float``
58+
- Delay in seconds between retries. Default: ``1.0``.
4759

4860
Examples
4961
--------
@@ -106,6 +118,158 @@ Handling Fallback Values
106118
}
107119
)
108120
121+
Async Support
122+
-------------
123+
124+
Installation
125+
^^^^^^^^^^^^
126+
127+
To use async external APIs, install the optional async dependencies:
128+
129+
.. code-block:: bash
130+
131+
pip install flask-inputfilter[async]
132+
133+
This installs ``httpx``, which is required for async HTTP requests.
134+
135+
Basic Async Usage
136+
^^^^^^^^^^^^^^^^^
137+
138+
Enable async mode by setting ``async_mode=True`` in the ``ExternalApiConfig``.
139+
The ``@validate()`` decorator automatically detects async routes and enables
140+
parallel API execution.
141+
142+
.. code-block:: python
143+
144+
from flask_inputfilter import InputFilter
145+
from flask_inputfilter.declarative import field
146+
from flask_inputfilter.models import ExternalApiConfig
147+
148+
class UserFilter(InputFilter):
149+
user_id: int = field(
150+
required=True,
151+
external_api=ExternalApiConfig(
152+
url="https://api.example.com/users/{{user_id}}",
153+
method="GET",
154+
async_mode=True # Enable async
155+
)
156+
)
157+
158+
@app.route('/users', methods=['POST'])
159+
@UserFilter.validate() # Auto-detects async route
160+
async def create_user(): # Note: async def
161+
data = g.validated_data
162+
return {"user": data}
163+
164+
Parallel API Calls
165+
^^^^^^^^^^^^^^^^^^
166+
167+
When multiple fields have async external APIs, all API calls execute in parallel,
168+
dramatically reducing total request time.
169+
170+
.. code-block:: python
171+
172+
class OrderFilter(InputFilter):
173+
user_id: int = field(
174+
required=True,
175+
external_api=ExternalApiConfig(
176+
url="https://api.users.com/{{user_id}}",
177+
method="GET",
178+
async_mode=True
179+
)
180+
)
181+
182+
product_id: int = field(
183+
required=True,
184+
external_api=ExternalApiConfig(
185+
url="https://api.products.com/{{product_id}}",
186+
method="GET",
187+
async_mode=True
188+
)
189+
)
190+
191+
shipping_id: int = field(
192+
required=True,
193+
external_api=ExternalApiConfig(
194+
url="https://api.shipping.com/{{shipping_id}}",
195+
method="GET",
196+
async_mode=True
197+
)
198+
)
199+
200+
@app.route('/orders', methods=['POST'])
201+
@OrderFilter.validate()
202+
async def create_order():
203+
# All 3 API calls execute in parallel!
204+
# Total time = max(user_api, product_api, shipping_api)
205+
return g.validated_data
206+
207+
Retry Logic
208+
^^^^^^^^^^^
209+
210+
Configure retry behavior for unreliable APIs:
211+
212+
.. code-block:: python
213+
214+
user_id: int = field(
215+
required=True,
216+
external_api=ExternalApiConfig(
217+
url="https://flaky-api.com/users/{{user_id}}",
218+
method="GET",
219+
async_mode=True,
220+
retry_count=3, # Retry up to 3 times
221+
retry_delay=0.5 # Wait 500ms between retries
222+
)
223+
)
224+
225+
Custom Timeout
226+
^^^^^^^^^^^^^^
227+
228+
Override the default 30-second timeout:
229+
230+
.. code-block:: python
231+
232+
user_id: int = field(
233+
required=True,
234+
external_api=ExternalApiConfig(
235+
url="https://slow-api.com/users/{{user_id}}",
236+
method="GET",
237+
async_mode=True,
238+
timeout=60 # 60 seconds
239+
)
240+
)
241+
242+
Mixed Sync and Async
243+
^^^^^^^^^^^^^^^^^^^^
244+
245+
You can mix sync and async external APIs in the same filter:
246+
247+
.. code-block:: python
248+
249+
class MixedFilter(InputFilter):
250+
# Legacy sync API
251+
legacy_id: int = field(
252+
external_api=ExternalApiConfig(
253+
url="https://legacy-api.com/{{id}}",
254+
method="GET",
255+
async_mode=False # Sync (default)
256+
)
257+
)
258+
259+
# Modern async API
260+
modern_id: int = field(
261+
external_api=ExternalApiConfig(
262+
url="https://modern-api.com/{{id}}",
263+
method="GET",
264+
async_mode=True # Async
265+
)
266+
)
267+
268+
@app.route('/data', methods=['POST'])
269+
@UserFilter.validate()
270+
async def get_data():
271+
return g.validated_data
272+
109273
Error Handling
110274
--------------
111275

0 commit comments

Comments
 (0)