RedCum
Async HTTP client for Python with native libcurl performance, extended with:
- A synchronous API (
RedCum.sync) — noasync/awaitrequired. impersonate=support wired through to libcurl-impersonate, with an auto-detecting fetch script (fetch_impersonate.sh) so getting it built is one command, not a manual per-architecture hunt — see BUILDING_IMPERSONATE.md.- Every libcurl failure raised as a specific, catchable Python exception — this was already true in upstream RedCum and is unchanged/preserved here.
- A fix for a real crash-on-shutdown bug found while testing this fork (see "What changed and why" below).
Everything upstream RedCum does (async Client, streaming, HTTP/1.1/2/3,
cookies, connection pooling, CurlURL, ...) still works exactly as before —
nothing was removed, only added to.
Quick start (sync API)
import RedCum.sync as curl
resp = curl.get("https://target.com", impersonate="chrome110")
resp2 = curl.get("https://target.com", impersonate="safari18_0_ios")
print(resp.status_code, resp.text[:200])
No client setup, no event loop, no await. Connections are still pooled
under the hood (a background thread owns one persistent event loop + one
RedCum.Client), so repeated calls to curl.get(...) aren't reconnecting
from scratch each time.
impersonate requires RedCum to be built against libcurl-impersonate.
./publish.sh --build-only --auto-impersonate handles this end-to-end —
auto-detects your architecture, downloads the matching library, and builds
a working wheel — verified for real, not just documented (see
BUILDING_IMPERSONATE.md, which walks through
the exact process that was used to confirm this: downloaded the actual
release archive, linked RedCum against it, and confirmed real HTTP requests
with impersonate="chrome110" and impersonate="safari18_0_ios" both
return 200. On a stock-libcurl build (what ships in this zip by default),
passing impersonate= raises a clear RuntimeError explaining exactly
what to do — it never silently ignores the argument or crashes.
import RedCum
print(RedCum.has_impersonate()) # False until built per BUILDING_IMPERSONATE.md
Session — for anything beyond one-off calls
import RedCum.sync as curl
with curl.Session(impersonate="chrome110", timeout=15) as s:
for url in urls:
r = s.get(url)
print(r.status_code)
Use Session when you want:
- Shared headers/cookies/timeout across many calls
- A single
impersonateprofile applied to every request from that session - Deterministic cleanup (
with ... :or explicit.close()) - To make concurrent calls from multiple threads against one connection pool
(a
Sessionis thread-safe — seetests/test_sync_api.py::test_concurrent_calls_from_multiple_threads)
Every request parameter is still there
curl.get, .post, .put, .patch, .delete, .head, .options, and
.request all accept the same arguments as RedCum.Client's async methods —
params, json, data, files, headers, cookies, timeout,
allow_redirects, proxy_url, auth, verify, cert,
stream_callback, progress_callback, verbose — plus the new
impersonate / impersonate_default_headers.
resp = curl.post(
"https://api.internal.example.com/upload",
files={"file": ("report.pdf", open("report.pdf", "rb"))},
headers={"Authorization": "Bearer ..."},
impersonate="chrome110",
timeout=30,
)
Error handling — the whole point of "simple"
Every connection-level failure raises. DNS failure, TLS handshake
failure, connection refused, timeout — you never get back a Response that
silently has status_code == -1. This is different from upstream RedCum's
default (where that's opt-in via raise_for_status=True); for a "simple"
API the sync layer here always raises on curl-level failure, since a caller
who didn't opt into error-handling complexity shouldn't have to check for a
magic -1 sentinel.
import RedCum.sync as curl
import RedCum.exceptions as exc
try:
curl.get("https://this-domain-does-not-exist.invalid")
except exc.CouldntResolveHostError as e:
print("DNS lookup failed:", e)
except exc.CurlError as e:
# catches every other libcurl failure: timeouts, TLS errors,
# connection refused, too many redirects, etc.
print(f"Request failed ({e.code}):", e)
HTTP 4xx/5xx status codes stay opt-in, same as requests/httpx:
resp = curl.get("https://api.example.com/missing")
print(resp.status_code) # 404, no exception
# opt in per-call:
resp.raise_for_status() # raises HTTPError now
# or opt in for a whole session:
with curl.Session(raise_for_status=True) as s:
s.get("https://api.example.com/missing") # raises HTTPError immediately
Every libcurl CURLcode has its own exception class (CouldntResolveHostError,
OperationTimedoutError, SslConnectErrorError, PeerFailedVerificationError,
...) — see RedCum/exceptions/__init__.py. This was already the case in
upstream RedCum; this fork doesn't touch that mapping, just makes sure the
sync layer actually surfaces it instead of swallowing it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
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 redcum-0.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: redcum-0.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 5.6 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6503dfa06c18450d6630a31e72a2b7d08dddf3154ea7e12275cdb01f6345138a
|
|
| MD5 |
5baa19362968c485bea80500f64231e4
|
|
| BLAKE2b-256 |
f10b4125f193947b3d1d1c4045a07444b06930d97c1391c9a4f4dca5e3ed2575
|
File details
Details for the file redcum-0.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: redcum-0.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 5.6 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b54bc887f20ee5669401b579f50ea87d9603fab45ce4d521d14033269354f596
|
|
| MD5 |
d0f52cbe53771ef0a19a37ed7c173fc1
|
|
| BLAKE2b-256 |
c6d4a0418e291484bf3006dc17617989a93f9f8d188b2f268109af6603ba13f6
|
File details
Details for the file redcum-0.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: redcum-0.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 5.6 MB
- Tags: CPython 3.11, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1144cf40f6b74a996c355968330a8ba442643c62a315db56f1480b55f6335c7d
|
|
| MD5 |
44cf7dd5a1f5eb636359187587a4b330
|
|
| BLAKE2b-256 |
243657d19f94e482b364c7a0e23b9f77ec66db9ae3c726e1c59cca99170f4a2e
|