API reference¶
Sending requests¶
fetch_all
async
¶
fetch_all(
urls: Iterable[Union[str, Request]],
method: str = ...,
*,
headers: Optional[Mapping[str, str]] = ...,
params: Optional[Mapping[str, Any]] = ...,
json: Any = ...,
data: Any = ...,
concurrency: Union[int, str] = ...,
max_concurrency: int = ...,
timeout: Optional[float] = ...,
retries: int = ...,
backoff: float = ...,
max_backoff: float = ...,
jitter: bool = ...,
retry_statuses: Sequence[int] = ...,
retry_rounds: int = ...,
retry_round_delay: float = ...,
verify_ssl: bool = ...,
ssl: Optional[SSLContext] = ...,
rate_limit: Union[RateLimit, str, None] = ...,
concurrency_per_host: int = ...,
auth: Any = ...,
cache: Any = ...,
proxy: Union[str, Sequence[str], None] = ...,
paginate: Any = ...,
callback: Optional[Callback] = ...,
progress: ProgressTarget = ...,
total: Optional[int] = ...,
log_level: LogLevel = ...,
log_file: LogTarget = ...,
log_format: str = ...,
session: Optional[ClientSession] = ...,
) -> Results
fetch_all(
urls: Iterable[Union[str, Request]],
method: Union[str, Callable[..., Any]] = "GET",
*,
headers: Optional[Mapping[str, str]] = None,
params: Optional[Mapping[str, Any]] = None,
json: Any = None,
data: Any = None,
concurrency: Union[int, str] = 100,
max_concurrency: int = 500,
timeout: Optional[float] = 30.0,
retries: int = 0,
backoff: float = 0.5,
max_backoff: float = 30.0,
jitter: bool = True,
retry_statuses: Sequence[int] = DEFAULT_RETRY_STATUSES,
retry_rounds: int = 0,
retry_round_delay: float = 5.0,
verify_ssl: bool = True,
ssl: Optional[SSLContext] = None,
rate_limit: Union[RateLimit, str, None] = None,
concurrency_per_host: int = 0,
auth: Any = None,
cache: Any = None,
proxy: Union[str, Sequence[str], None] = None,
paginate: Any = None,
callback: Optional[Callback] = None,
progress: ProgressTarget = False,
total: Optional[int] = None,
log_level: LogLevel = None,
log_file: LogTarget = None,
log_format: str = "text",
session: Optional[ClientSession] = None,
**legacy: Any,
) -> Optional[Results]
Send all requests concurrently and return their results in the order given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
urls
|
Iterable[Union[str, Request]]
|
URLs, or |
required |
method
|
Union[str, Callable[..., Any]]
|
HTTP method for plain URLs (default |
'GET'
|
headers
|
Optional[Mapping[str, str]]
|
Headers sent with every request; a |
None
|
params
|
Optional[Mapping[str, Any]]
|
Query parameters for every request, unless a |
None
|
json
|
Any
|
JSON body for every request, unless a |
None
|
data
|
Any
|
Form or raw body for every request, unless a |
None
|
concurrency
|
Union[int, str]
|
Maximum number of requests in flight at once, or |
100
|
max_concurrency
|
int
|
With |
500
|
timeout
|
Optional[float]
|
Seconds allowed per attempt, including reading the body. |
30.0
|
retries
|
int
|
How many times to retry a request right away after a connection error,
a timeout or a status in |
0
|
backoff
|
float
|
Delay before the first retry in seconds, doubled for each further retry (0.5, 1, 2, 4, ... with the default). |
0.5
|
max_backoff
|
float
|
Upper limit for that delay in seconds (default 30), so many retries
never wait minutes. A |
30.0
|
jitter
|
bool
|
Wait a random time between half and all of the delay, so requests that
failed together do not retry at the same moment. |
True
|
retry_rounds
|
int
|
After all requests have finished, send the ones that still failed for a retryable reason again, up to this many more rounds. |
0
|
retry_round_delay
|
float
|
Seconds to wait before each retry round. |
5.0
|
verify_ssl
|
bool
|
Verify TLS certificates (default). Only disable this for hosts you control. |
True
|
ssl
|
Optional[SSLContext]
|
A custom |
None
|
rate_limit
|
Union[RateLimit, str, None]
|
Maximum request rate per host ( |
None
|
concurrency_per_host
|
int
|
Maximum requests in flight to each host; 0 means no per-host limit. |
0
|
auth
|
Any
|
|
None
|
cache
|
Any
|
|
None
|
proxy
|
Union[str, Sequence[str], None]
|
A proxy URL, or a list of them used in turn, attempt by attempt:
|
None
|
paginate
|
Any
|
|
None
|
callback
|
Optional[Callback]
|
Called with each final |
None
|
progress
|
ProgressTarget
|
|
False
|
total
|
Optional[int]
|
Number of requests, for the percentage and the time left when |
None
|
log_level
|
LogLevel
|
|
None
|
log_file
|
LogTarget
|
With |
None
|
log_format
|
str
|
|
'text'
|
session
|
Optional[ClientSession]
|
An existing |
None
|
Returns a Results list; results.errors() and results.summary() report the failures.
A failing request never stops the others: its Result.error holds the exception and
Result.history lists every attempt.
fetch_all_sync ¶
fetch_all_sync(
urls: Iterable[Union[str, Request]],
method: str = "GET",
**options: Any,
) -> Results
Blocking version of fetch_all: send the requests and return their results in order.
Takes the same options as fetch_all except session, which belongs to an event loop.
results = reqstorm.fetch_all_sync(urls, concurrency=20, retries=2)
stream
async
¶
stream(
urls: Iterable[Union[str, Request]],
method: str = "GET",
*,
headers: Optional[Mapping[str, str]] = None,
params: Optional[Mapping[str, Any]] = None,
json: Any = None,
data: Any = None,
concurrency: Union[int, str] = 100,
max_concurrency: int = 500,
timeout: Optional[float] = 30.0,
retries: int = 0,
backoff: float = 0.5,
max_backoff: float = 30.0,
jitter: bool = True,
retry_statuses: Sequence[int] = DEFAULT_RETRY_STATUSES,
verify_ssl: bool = True,
ssl: Optional[SSLContext] = None,
rate_limit: Union[RateLimit, str, None] = None,
concurrency_per_host: int = 0,
auth: Any = None,
cache: Any = None,
proxy: Union[str, Sequence[str], None] = None,
paginate: Any = None,
progress: ProgressTarget = False,
total: Optional[int] = None,
log_level: LogLevel = None,
log_file: LogTarget = None,
log_format: str = "text",
session: Optional[ClientSession] = None,
_run: Optional[Run] = None,
) -> AsyncGenerator[Result, None]
Send the requests concurrently and yield each Result as soon as it completes.
urls may be any iterable, including a generator; it is consumed lazily, so
millions of requests can be streamed without building them all in memory.
See fetch_all for the parameters. Retry rounds are not available here;
use fetch_all or fetch_to_file for those.
stream_sync ¶
stream_sync(
urls: Iterable[Union[str, Request]],
method: str = "GET",
**options: Any,
) -> Iterator[Result]
Blocking version of stream: yield each Result as soon as it completes.
The requests run on an event loop in a background thread. Leaving the loop early
(break) stops the remaining requests.
for result in reqstorm.stream_sync(urls, concurrency=50):
print(result.url, result.status)
Writing results¶
fetch_to_file
async
¶
fetch_to_file(
urls: Iterable[Union[str, Request]],
path: Union[str, PathLike],
*,
format: Optional[str] = None,
body: str = "text",
include_headers: bool = False,
resume: bool = False,
ordered: bool = False,
progress: ProgressTarget = False,
retry_rounds: int = 0,
retry_round_delay: float = 5.0,
table: str = "reqstorm_results",
batch_size: int = 100,
flush_interval: float = 1.0,
schema: Any = None,
explode: Optional[str] = None,
rejects_table: Optional[str] = None,
include_source: bool = True,
**options: Any,
) -> Summary
Send the requests and write one record per request to path as soon as it is final.
Results are not collected in memory, so this suits very large batches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Union[str, PathLike]
|
|
required |
format
|
Optional[str]
|
|
None
|
body
|
str
|
|
'text'
|
include_headers
|
bool
|
Also write the response headers. |
False
|
resume
|
bool
|
Skip the requests already recorded as successful in an existing file or table; the rest (including earlier failures) are sent and appended. Without it a JSONL or CSV file is overwritten; database rows are never deleted. |
False
|
ordered
|
bool
|
Write records in input order. By default they are written as they complete. Ordered output holds back records that finish early until the ones before them are done, so it uses more memory when a few requests are slow. |
False
|
progress
|
ProgressTarget
|
As for |
False
|
retry_rounds
|
int
|
As for |
0
|
retry_round_delay
|
float
|
Seconds to wait before each retry round. |
5.0
|
table
|
str
|
Table name for SQLite output. |
'reqstorm_results'
|
batch_size
|
int
|
Write as soon as this many records are waiting. |
100
|
flush_interval
|
float
|
And at least every this many seconds while records are waiting
(default 1), even when no request finishes, so other programs reading the
output are never more than about a second behind. |
1.0
|
schema
|
Any
|
Write typed fields parsed from each JSON response instead of the raw
response: a dict of column name to |
None
|
explode
|
Optional[str]
|
With a schema, path to an array in each response; every element becomes a row. |
None
|
rejects_table
|
Optional[str]
|
With a schema and SQLite output, also write rejected records to this table. |
None
|
include_source
|
bool
|
With a schema, add |
True
|
options
|
Any
|
Any other option of |
{}
|
Each record has index (position in urls), method, url, status, ok,
error, attempts, elapsed, final_url and history (every attempt).
Returns a Summary whose errors lists the failed records.
fetch_to_file_sync ¶
Blocking version of fetch_to_file.
fetch_to_db
async
¶
fetch_to_db(
urls: Iterable[Union[str, Request]],
connection: Any,
*,
table: str = "reqstorm_results",
body: str = "text",
include_headers: bool = False,
resume: bool = False,
ordered: bool = False,
progress: ProgressTarget = False,
retry_rounds: int = 0,
retry_round_delay: float = 5.0,
batch_size: int = 100,
flush_interval: float = 1.0,
schema: Any = None,
explode: Optional[str] = None,
rejects_table: Optional[str] = None,
include_source: bool = True,
**options: Any,
) -> Summary
Send the requests and insert one row per request into table.
connection is an open DB-API connection from sqlite3, psycopg / psycopg2
(PostgreSQL), or pymysql / MySQLdb / mysql.connector (MySQL). reqstorm does not
close it. The table is created if it does not exist, with one column per field
(request_index, method, url, status, ok, error, attempts,
elapsed, final_url), JSON columns for history and headers, body,
and created_at. Rows are inserted in batches on a background thread, except for
sqlite3 connections, which must stay on the thread that created them.
Existing rows are never deleted. With resume=True, requests that already have a
successful row are skipped. body="bytes" stores the raw body as a binary column.
Rows are committed per batch and at least every flush_interval seconds. The other
arguments, including logging, are as for fetch_to_file and fetch_all.
With schema, each JSON response is turned into typed rows instead: one column per
field (BIGINT, DOUBLE PRECISION, TEXT, BOOLEAN, TIMESTAMPTZ or
JSONB in PostgreSQL, and their equivalents), plus source_index,
source_method and source_url unless include_source=False. explode
turns each element of an array into a row. Records with a missing required field or
a value of the wrong type (including NaN and Infinity) are not written: they are
listed in Summary.errors and, with rejects_table, written to that table with
their reasons. Fields marked key=True form the primary key, and a record whose
key already exists updates that row. An existing table must already have the columns.
fetch_to_db_sync ¶
fetch_to_db_sync(
urls: Iterable[Union[str, Request]],
connection: Any,
**options: Any,
) -> Summary
Blocking version of fetch_to_db.
The requests run on a helper thread when called from a running event loop; a sqlite3
connection must then be opened with check_same_thread=False.
Summary
dataclass
¶
What fetch_to_file or fetch_to_db did.
rejected
class-attribute
instance-attribute
¶
With a schema: records rejected because a field was missing or had the wrong type.
report
class-attribute
instance-attribute
¶
Response times, statuses, errors and per-host figures, as Results.report().
log_file
class-attribute
instance-attribute
¶
The log file of this run, when log_file= was given.
Schemas¶
Field
dataclass
¶
Where a column's value comes from in the JSON, and what type it must have.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Dotted path to the value, such as |
required |
type
|
FieldType
|
|
'json'
|
required
|
bool
|
Reject the record when the value is missing or null. |
False
|
coerce
|
bool
|
Also accept compatible values of another type, such as |
False
|
key
|
bool
|
Part of the table's primary key. Writing a record whose key already exists updates that row instead of adding a new one. |
False
|
Schema
dataclass
¶
Columns of a table and the fields they are read from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
Mapping[str, Field]
|
Column name to |
required |
explode
|
Optional[str]
|
Path to an array in the response; each element becomes a row
( |
None
|
infer_schema ¶
infer_schema(
samples: Iterable[Any],
*,
explode: Optional[str] = None,
max_depth: int = 3,
) -> Schema
Draft a schema from sample responses, for you to review and edit.
samples can be Result objects, parsed JSON documents, or JSON text. Nested
objects become dotted paths (up to max_depth levels), arrays become "json"
fields, and a field present and non-null in every sample is marked required.
print(schema) shows it as Python code.
The draft only reflects the samples: a field that was always an integer there may be a float in other responses. Check it against the API's documentation.
extract ¶
extract(
results: Iterable[Result],
schema: Any,
*,
explode: Optional[str] = None,
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]
Typed rows from results you already have, for example from fetch_all.
Returns (rows, rejected). Each row is a dict of column to value plus
source_index and source_url. Each rejected entry has source_index,
source_url, reasons and the record it came from. Failed requests are
rejected with the reason request failed.
SchemaError ¶
Bases: ValueError
A schema definition is invalid (not raised for invalid data, which is rejected instead).
Pagination¶
NextLink
dataclass
¶
Bases: Paginator
The response JSON holds the next page's URL, for example {"links": {"next": "..."}}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Dotted path to the URL ( |
'next'
|
max_pages
|
int
|
Stop after this many pages per starting URL. |
1000
|
LinkHeader
dataclass
¶
Bases: Paginator
The next page is in the Link response header (RFC 8288), as GitHub and many APIs do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rel
|
str
|
The relation to follow (default |
'next'
|
max_pages
|
int
|
Stop after this many pages per starting URL. |
1000
|
Cursor
dataclass
¶
Bases: Paginator
The response JSON holds a cursor, sent back as a query parameter for the next page.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Dotted path to the cursor in the response ( |
required |
param
|
str
|
Query parameter to send it in ( |
'cursor'
|
max_pages
|
int
|
Stop after this many pages per starting URL. |
1000
|
Pagination stops when the cursor is missing, null or an empty string.
PageNumber
dataclass
¶
Bases: Paginator
Request ?page=1, ?page=2, ... until a page comes back empty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
param
|
str
|
Query parameter holding the page number ( |
'page'
|
start
|
int
|
Number of the first page, used when the starting URL has no such parameter. |
1
|
items
|
Optional[str]
|
Dotted path to the array of results; pagination stops when it is empty or missing. Without it, it stops at the first non-2xx response. |
None
|
max_pages
|
int
|
Stop after this many pages per starting URL. |
1000
|
Paginator ¶
Base class: given a page's request and result, return the next page's request or None.
Requests from data¶
from_template ¶
from_template(
url: str,
rows: Iterable[Mapping[str, Any]],
*,
method: Optional[str] = None,
json: BodyTemplate = None,
params: BodyTemplate = None,
headers: Optional[Mapping[str, str]] = None,
) -> Iterator[Request]
One Request per row, with {field} placeholders filled from the row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
URL template such as |
required |
rows
|
Iterable[Mapping[str, Any]]
|
Dicts, for example from |
required |
method
|
Optional[str]
|
HTTP method; the default of |
None
|
json
|
BodyTemplate
|
A JSON body: a template dict whose |
None
|
params
|
BodyTemplate
|
Query parameters, in the same forms as |
None
|
headers
|
Optional[Mapping[str, str]]
|
Headers sent with every request. |
None
|
The result is a generator, so millions of rows are read lazily.
rows = reqstorm.read_csv("users.csv")
requests = reqstorm.from_template("https://api.example.com/users/{id}", rows)
reqstorm.fetch_to_file_sync(requests, "users.jsonl")
read_csv ¶
Rows of a CSV file as dicts keyed by the header row, read lazily.
Extra keyword arguments go to csv.DictReader (for example delimiter=";").
read_sql ¶
read_sql(
connection: Any,
query: str,
parameters: Any = None,
batch_size: int = 1000,
) -> Iterator[Dict[str, Any]]
Rows of a SQL query as dicts keyed by column name, fetched in batches.
Works with any DB-API connection: sqlite3, psycopg, psycopg2, PyMySQL, ...
Tokens and caching¶
BearerAuth ¶
Send Authorization: Bearer <token>, and get a new token when the server says it expired.
auth = reqstorm.BearerAuth(refresh=get_token)
reqstorm.fetch_all_sync(urls, auth=auth)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
Optional[str]
|
The current token. Leave it out to fetch one with |
None
|
refresh
|
Optional[TokenSource]
|
A function or coroutine function that returns a new token. It is called when a response is 401 Unauthorized; the request is then sent again once with the new token. Concurrent 401s share a single refresh. |
None
|
header
|
str
|
Header to send the token in (default |
'Authorization'
|
scheme
|
str
|
Prefix before the token (default |
'Bearer'
|
Cache ¶
Keep successful GET responses on disk and reuse them on the next run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | PathLike[str]
|
SQLite file to keep the responses in (created if missing). |
'reqstorm-cache.sqlite'
|
ttl
|
Optional[float]
|
Seconds a cached response is used without asking the server. |
None
|
Only GET and HEAD responses with status 200 are cached. A result served from the cache
has from_cache=True. Close the cache when done, or use it in a with block.
with reqstorm.Cache("responses.sqlite", ttl=3600) as cache:
reqstorm.fetch_all_sync(urls, cache=cache)
Requests and results¶
Request
dataclass
¶
A single request. Fields left as None take the defaults given to fetch_all.
Result
dataclass
¶
The outcome of one request: a response, or the error that prevented one.
from_cache
class-attribute
instance-attribute
¶
True when the response came from the cache (fresh, or revalidated with a 304).
page
class-attribute
instance-attribute
¶
With paginate: 0 for the starting URL, 1 for the next page, and so on.
seed_index
class-attribute
instance-attribute
¶
Position in the input of the starting URL this result belongs to.
raise_for_error ¶
Raise the request's error, or HTTPStatusError for a status of 400 or above.
to_dict ¶
A JSON-serialisable summary, as written by fetch_to_file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
body
|
str
|
|
'none'
|
include_headers
|
bool
|
Include the response headers. |
False
|
Results ¶
Bases: List[Result]
The list returned by fetch_all, in input order, with helpers for reporting.
errors ¶
Every failed request as a dict: index, method, url, status, error, attempts and history.
summary ¶
Counts of the batch: total, ok, failed, failures grouped by status or error type,
response times (latency: min, p50, p90, p95, p99, max, mean in seconds),
statuses and per-host figures (hosts). See report() for the full set.
report ¶
Response times (p50, p90, p95, p99), statuses, errors and per-host figures.
Attempt
dataclass
¶
One try at a request: the status it got or the error it raised.
HTTPStatusError ¶
Bases: Exception
Raised by Result.raise_for_error for a response status of 400 or above.
Progress¶
ProgressInfo
dataclass
¶
A snapshot of a running batch, passed to progress= when it is a function.
The function is called every few seconds while the batch runs, even when no request
finishes, and once more at the end with finished=True.
total
instance-attribute
¶
Number of requests, or None when the input is a generator of unknown length.
in_flight
instance-attribute
¶
Requests being sent, waiting for a response, or waiting to retry right now.
round
class-attribute
instance-attribute
¶
Retry round being sent (0 for the first pass).
paused
class-attribute
instance-attribute
¶
Hosts the server asked to wait (rate_limit="auto"), with seconds left.
concurrency
class-attribute
instance-attribute
¶
With concurrency="auto": the number of requests currently allowed in flight.
Planning¶
estimate ¶
estimate(
requests: int,
*,
rate_limit: Optional[RateLimit] = None,
hosts: int = 1,
concurrency: int = 100,
concurrency_per_host: int = 0,
latency: float = 0.5,
) -> Estimate
Estimate the duration of a batch, without sending anything.
>>> print(reqstorm.estimate(7000, rate_limit="100/min"))
7000 requests: about 1h 10m (limited by rate_limit)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requests
|
int
|
Number of requests. |
required |
rate_limit
|
Optional[RateLimit]
|
The per-host rate limit you plan to use (same formats as |
None
|
hosts
|
int
|
How many different hosts the requests are spread evenly over. |
1
|
concurrency
|
int
|
The overall concurrency you plan to use. |
100
|
concurrency_per_host
|
int
|
The per-host concurrency you plan to use; 0 means none. |
0
|
latency
|
float
|
Typical seconds per request (response time); 0.5 by default. |
0.5
|
Retries are not included; they add time for requests that fail.
Estimate
dataclass
¶
How long requests requests should take, and which setting limits them.
parse_rate ¶
Requests per second from 5, "100/min", "30/5min", "1000/h" or (100, 60).