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.
Metadata
Release files for RedCum 0.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| redcum-0.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.17+ x86-64 | Details |
| redcum-0.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.17+ x86-64 | Details |
| redcum-0.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.17+ x86-64 | Details |
Total release size: 16.9 MB
Release files / redcum-0.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | redcum-0.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
6503dfa06c18450d6630a31e72a2b7d08dddf3154ea7e12275cdb01f6345138a
|
|
BLAKE2b-256 checksum How to use checksums |
f10b4125f193947b3d1d1c4045a07444b06930d97c1391c9a4f4dca5e3ed2575
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / redcum-0.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | redcum-0.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
b54bc887f20ee5669401b579f50ea87d9603fab45ce4d521d14033269354f596
|
|
BLAKE2b-256 checksum How to use checksums |
c6d4a0418e291484bf3006dc17617989a93f9f8d188b2f268109af6603ba13f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / redcum-0.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | redcum-0.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 5.6 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
1144cf40f6b74a996c355968330a8ba442643c62a315db56f1480b55f6335c7d
|
|
BLAKE2b-256 checksum How to use checksums |
243657d19f94e482b364c7a0e23b9f77ec66db9ae3c726e1c59cca99170f4a2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|