Simple Parallel Asynchronous Requests for Python
Project description
SPARP: Simple Parallel Async Requests for Python
Turn sequential HTTP requests into concurrent ones without writing any async/await code:
# Before
responses = [requests.post(url, json=payload) for url, payload in items]
# After
result = SPARP(inspect_response=inspect_response).run(items)
Installation
pip install sparp
Basic Usage
import aiohttp
import json
from sparp import SPARP, ResponseState
def inspect_response(response: aiohttp.ClientResponse) -> ResponseState:
if response.status == 200:
return ResponseState.SUCCESS
if response.status in (429, 502):
return ResponseState.SOFT_FAIL
return ResponseState.HARD_FAIL
requests = [{"method": "GET", "url": f"https://httpbin.org/get?item={i}"} for i in range(100)]
sparp = SPARP(inspect_response=inspect_response, concurrency=20, show_progress_bar=True)
result = sparp.run(requests)
for item in result.success:
print(f"sent: {item['input']['url']} | received: {json.loads(item['text'])['args']['item']}")
print(f"Completed: {result.stats.success} successful, {result.stats.soft_retries} retries")
Running Examples
make run-example EXAMPLE=basic_example
make run-example EXAMPLE=callbacks
make run-example EXAMPLE=custom_parser
make run-example EXAMPLE=input_generator
make run-example EXAMPLE=retry_exhaustion
make run-example EXAMPLE=stop_conditions
make run-example EXAMPLE=timeouts
Features
- Generator support — pass a generator as input; requests are buffered into memory in small batches
- Smart retries — separate retry budgets for server-side failures (e.g. 429) and timeouts
- Custom parsing — control exactly what is kept from each response before results are returned
- Stop conditions — halt the entire run early on any failure category
- Progress bar — real-time terminal UI showing successes, failures, retries, and throughput
- Auto size detection — if the input is a list or other sized iterable, progress percentage is computed automatically
API Reference
SPARP(inspect_response, ...) — configuration
All options are passed at construction time. The input collection is passed separately to run().
sparp = SPARP(
inspect_response, # required — classifies each response as SUCCESS / SOFT_FAIL / HARD_FAIL
callbacks=None, # Callbacks — hooks for every outcome event
concurrency=100, # max simultaneous in-flight requests
max_retries_when_soft_fail=20, # retry budget for SOFT_FAIL responses
max_retries_on_timeout=20, # retry budget for timeouts
parse_response_fn=default_parse_response, # async fn to extract data from a response
stop_conditions=None, # StopConditions — halt the run on specific events
input_buffer_size=100, # items pre-fetched from a generator into memory
show_progress_bar=False, # print a live progress bar to the terminal
timeout_s=30.0, # per-request timeout in seconds
progress_bar_requests_threshold=1, # min requests completed between bar redraws
progress_bar_time_threshold=timedelta(seconds=0.5), # min time between bar redraws
ssl_verify=True, # set False to disable TLS certificate verification (insecure)
)
sparp.run(input_collection, estimated_input_collection_size=None) — execution
result: SparpResult = sparp.run(
input_collection, # Iterable[dict] — kwargs forwarded to aiohttp session.request()
estimated_input_collection_size=None, # hint for progress % when input is a generator;
# inferred automatically for lists and other sized iterables
)
ResponseState
class ResponseState(Enum):
SUCCESS = "SUCCESS" # request completed successfully
SOFT_FAIL = "SOFT_FAIL" # transient failure — will be retried
HARD_FAIL = "HARD_FAIL" # permanent failure — recorded and skipped
Callbacks
Optional hooks called on each outcome. All are None by default.
Callbacks(
on_success=lambda req, response: ...,
on_hard_fail=lambda req, response: ...,
on_soft_fail=lambda req, retry_count: ...,
on_timeout=lambda req, retry_count: ...,
on_max_retries_by_soft_fail_reached=lambda req: ...,
on_max_retries_by_timeout_reached=lambda req: ...,
)
StopConditions
Halt the entire run when a specific event occurs. All are False by default.
StopConditions(
stop_on_soft_fail=False,
stop_on_hard_fail=False,
stop_on_timeout=False,
stop_on_max_retries_by_soft_fail_reached=False,
stop_on_max_retries_by_timeout_reached=False,
)
SparpResult
@dataclass(frozen=True)
class SparpResult:
stats: SparpStats
success: list[Any] # parsed results from successful requests
failed: list[Any] # parsed results from hard-failed requests
max_retries_soft_fail_reached: list[dict] # request dicts that exhausted their soft-fail budget
max_retries_timeout_reached: list[dict] # request dicts that exhausted their timeout budget
@dataclass(frozen=True)
class SparpStats:
success: int # total successful requests
failed: int # total hard-failed requests
soft_retries: int # cumulative soft-fail retry attempts
timeout_retries: int # cumulative timeout retry attempts
default_parse_response
The built-in parser used when parse_response_fn is not overridden. Returns:
{
"input": request_dict, # the original request kwargs
"status": response.status,
"text": await response.text(),
"headers": dict(response.headers),
}
To customise, pass an async function with the same signature:
async def my_parser(request_dict: dict, response: aiohttp.ClientResponse) -> Any:
return {"status": response.status, "body": await response.json()}
sparp = SPARP(inspect_response=inspect_response, parse_response_fn=my_parser)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sparp-1.13.0.tar.gz.
File metadata
- Download URL: sparp-1.13.0.tar.gz
- Upload date:
- Size: 10.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81d066bd2cf2a19ad0b222f91f008e910defe1dd89844a6544d614a81c99fc3a
|
|
| MD5 |
7975b96a374c67d1506460ada0dba3b0
|
|
| BLAKE2b-256 |
b390f8b595b6ae3074ec0399bcf48a9d2b33e263ef85775605c7592ffc6c1432
|
Provenance
The following attestation bundles were made for sparp-1.13.0.tar.gz:
Publisher:
release.yml on fredo838/sparp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sparp-1.13.0.tar.gz -
Subject digest:
81d066bd2cf2a19ad0b222f91f008e910defe1dd89844a6544d614a81c99fc3a - Sigstore transparency entry: 2190011859
- Sigstore integration time:
-
Permalink:
fredo838/sparp@767ffa3c26dcd5b7452974e7dfa81b217fd54574 -
Branch / Tag:
refs/tags/v1.13.0 - Owner: https://github.com/fredo838
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@767ffa3c26dcd5b7452974e7dfa81b217fd54574 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sparp-1.13.0-py3-none-any.whl.
File metadata
- Download URL: sparp-1.13.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29a445f749d7de0c45be9afc2edd9638c2fc14e4154ee6847535753a3aae8af6
|
|
| MD5 |
a56208c01172c3df0bf646f16120047a
|
|
| BLAKE2b-256 |
e473b72d6dc428ec51c22e7e5ae7f5d85821bf34765915dd88dedc499c7259d8
|
Provenance
The following attestation bundles were made for sparp-1.13.0-py3-none-any.whl:
Publisher:
release.yml on fredo838/sparp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sparp-1.13.0-py3-none-any.whl -
Subject digest:
29a445f749d7de0c45be9afc2edd9638c2fc14e4154ee6847535753a3aae8af6 - Sigstore transparency entry: 2190011933
- Sigstore integration time:
-
Permalink:
fredo838/sparp@767ffa3c26dcd5b7452974e7dfa81b217fd54574 -
Branch / Tag:
refs/tags/v1.13.0 - Owner: https://github.com/fredo838
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@767ffa3c26dcd5b7452974e7dfa81b217fd54574 -
Trigger Event:
push
-
Statement type: