cheat sheet
requests
Package-level reference for the requests HTTP client on PyPI — install variants, version policy, extras, and alternatives.
requests
What it is
requests is a synchronous HTTP client library created by Kenneth Reitz in 2011 and now maintained under the Python Software Foundation umbrella. It wraps urllib3 with a high-level, human-friendly API and has consistently ranked as the single most-downloaded package on PyPI for over a decade.
Reach for requests when you need a stable, blocking HTTP client with broad ecosystem support — almost every Python SDK, scraper, and integration tutorial assumes it. Reach for httpx instead when you need async/await or HTTP/2.
Install
pip install requests
Output: (none — exits 0 on success)
uv add requests
Output: dependency resolved + added to pyproject.toml
poetry add requests
Output: updated lockfile + virtualenv install
pip install "requests[security]" # legacy extra — see notes
Output: installs requests plus optional security backports (no-op on modern Python)
Versioning & Python support
- Current stable line is the
2.xseries (as of mid-2026). The project has stayed on2.xsince 2014 — major-version bumps are rare and treated as breaking. - Supports Python 3.8+ on recent releases. Older releases support 3.7 and earlier; check the changelog for the exact floor.
- Loose semver — patch releases are bug fixes, minor releases may deprecate but rarely break import-level API.
- Python 2.7 support was dropped in
2.28(2022).
Package metadata
- Maintainer: Python Software Foundation /
psforg on GitHub - Project home: github.com/psf/requests
- Docs: requests.readthedocs.io
- PyPI: pypi.org/project/requests
- License: Apache-2.0
- Governance: PSF-stewarded, community-maintained
- First released: 2011
- Downloads: in the hundreds of millions per month — consistently #1 on PyPI
Optional dependencies & extras
requests[security]— historically pulled inpyOpenSSL,cryptography,idna. Now mostly a no-op on Python 3.x because the stdlibsslmodule is sufficient. Kept for backward compatibility.requests[socks]— installsPySocksfor SOCKS4/SOCKS5 proxy support (socks5://user:pass@host:port).requests[use_chardet_on_py3]— installschardetfor response-encoding detection instead of the defaultcharset-normalizer.
Core dependencies pulled in automatically:
urllib3— connection pooling, retries, TLScertifi— bundled CA certificate storecharset-normalizer— response encoding detectionidna— internationalized domain name handling
Alternatives
| Package | Trade-off |
|---|---|
httpx | Async-first with sync API; HTTP/2 support. Use when you need async/await or HTTP/2. |
urllib3 | Lower-level — direct connection-pool and retry control. Use only to bypass requests' abstractions. |
aiohttp | Async client + server combined. Heavier than httpx for client-only use. |
niquests | Drop-in requests fork with HTTP/2, HTTP/3, async, and DNS-over-HTTPS. Use when you want the requests API plus modern transport. |
urllib (stdlib) | Zero-dependency. Use only when you cannot add a third-party package. |
Common gotchas
- Synchronous only. No
async/awaitsupport — calls block the event loop. In FastAPI / asyncio contexts, usehttpx.AsyncClientinstead. certifiCA bundle, not system trust store. TLS verification usescertifi's bundled CAs. If you need OS-managed certs (corporate CA, mitmproxy), installpip-system-certsor setREQUESTS_CA_BUNDLE.- Vendored
urllib3history. Old releases vendoredurllib3underrequests.packages.urllib3; modern releases import the top-level one. Avoid the legacy import path — it's a compatibility shim, not the real module. - Connection-pool default is 10 per host. For high-concurrency workers, mount a custom
HTTPAdapter(pool_connections=N, pool_maxsize=N)on aSession. Sessionis not safe to share across threads for all operations. Reading is generally fine, butmount(), header mutation, and cookie jar updates during requests can race. Prefer one session per thread.- No retry by default. Failed requests raise immediately. Wire
urllib3.util.Retryinto anHTTPAdapterto get exponential backoff. - Implicit JSON encoding charset is UTF-8. Posting bytes that aren't UTF-8 via
json=will silently re-encode — passdata=with an explicitContent-Typeheader instead.
Real-world recipes
Concrete, copy-paste solutions to problems that come up the first week any team puts requests into production. Each focuses on package-level concerns (pool tuning, adapter mounts, transport-level configuration) rather than the day-to-day API patterns covered in the companion article.
Recipe 1 — Production-grade session with pool sizing, retries, and timeouts wired together.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
def make_session(pool_size: int = 50) -> requests.Session:
s = requests.Session()
retry = Retry(
total=5,
backoff_factor=0.5,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=frozenset(["GET", "HEAD", "PUT", "DELETE", "OPTIONS", "TRACE", "POST"]),
respect_retry_after_header=True,
)
adapter = HTTPAdapter(
pool_connections=pool_size,
pool_maxsize=pool_size,
max_retries=retry,
pool_block=False,
)
s.mount("http://", adapter)
s.mount("https://", adapter)
s.headers.update({"User-Agent": "myapp/1.0 (+contact: ops@example.com)"})
return s
Output: session ready for use; per-request timeout=(connect, read) still required.
Recipe 2 — Paginated cursor-based API client iterator.
def iter_pages(session, url, params=None):
params = dict(params or {})
while url:
r = session.get(url, params=params, timeout=(5, 30))
r.raise_for_status()
body = r.json()
yield from body["items"]
url = body.get("next_url")
params = None # cursor embedded in next_url
Output: lazy generator; one HTTP request per page, no extra memory beyond a page.
Recipe 3 — File upload with progress reporting via MultipartEncoderMonitor.
# pip install requests-toolbelt
from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor
def upload(session, url, path):
encoder = MultipartEncoder(fields={"file": (path, open(path, "rb"), "application/octet-stream")})
def callback(monitor):
pct = monitor.bytes_read * 100 // monitor.len
print(f"\r{pct}%", end="", flush=True)
monitor = MultipartEncoderMonitor(encoder, callback)
r = session.post(url, data=monitor, headers={"Content-Type": monitor.content_type}, timeout=(5, 600))
r.raise_for_status()
return r.json()
Output: progress percentages stream to stdout; final JSON response returned.
Recipe 4 — Mounting a per-host adapter for one slow upstream.
s = make_session()
slow_adapter = HTTPAdapter(
pool_connections=10, pool_maxsize=10,
max_retries=Retry(total=2, backoff_factor=2.0),
)
s.mount("https://slow-api.example.com/", slow_adapter)
Output: other hosts keep the fast default; only slow-api.example.com uses the patient adapter.
Security considerations
requests ships secure defaults, but each is overridable — and the override often ends up in production by accident. Treat every TLS, redirect, and authentication setting as load-bearing.
- Never set
verify=Falsein production. It silently disables certificate validation and prints aInsecureRequestWarningthat gets filtered out of structured logs. If you need a private CA, setverify="/path/to/ca-bundle.pem"or theREQUESTS_CA_BUNDLEenv var. certifiis the cert store, not the OS trust store. Corporate MITM proxies and self-signed certs require either bundling them into a custom PEM or installingpip-system-certsto redirect lookups to the OS keychain.- Redirects follow by default and forward auth headers to same-host targets only. A redirect to a different host strips
AuthorizationandCookieheaders since2.x, but cross-protocol downgrades (HTTPS → HTTP) still happen unless you wire aredirect_hookor setallow_redirects=False. - CRLF injection through header values. Pre-
2.32releases were vulnerable to header smuggling via newlines in user-supplied header values; modern releases reject them. Pin to a recent patch level — see GitHub security advisories. - Proxy URL leaks credentials in logs. If your proxy URL embeds
user:pass@host, the password ends up inreq.proxiesand any logger that pickles the request prints it. Strip to aProxyManageror load via env var. charset-normalizerruns untrusted input through a heuristic. For very large responses, prefer settingresponse.encodingexplicitly rather than letting it sniff.- Pin
urllib3.requestsdoes not cap upstreamurllib3tightly; a major bump inurllib3(the1.x → 2.xswitch in 2023 broke many downstream users) can change retry semantics and proxy handling.
Testing & CI integration
The library is sync, so test doubles are simple — the choice is whether to mock at the Session level, the HTTP-call level, or via a recorded VCR cassette.
# pytest + responses (recommended for unit tests)
# pip install responses
import responses, requests
@responses.activate
def test_fetch_user():
responses.add(responses.GET, "https://api.example.com/users/42",
json={"id": 42, "name": "Alice Dev"}, status=200)
r = requests.get("https://api.example.com/users/42", timeout=5)
assert r.json()["name"] == "Alice Dev"
assert len(responses.calls) == 1
Output: test passes; HTTP traffic intercepted, no socket opened.
# requests-mock — pytest fixture style
# pip install requests-mock
def test_with_fixture(requests_mock):
requests_mock.get("https://api.example.com/health", text="ok")
assert requests.get("https://api.example.com/health", timeout=5).text == "ok"
Output: the requests_mock fixture is provided by the package — no decorator needed.
# vcrpy — record real traffic once, replay forever
# pip install vcrpy
import vcr
@vcr.use_cassette("cassettes/users.yaml")
def test_real_user():
r = requests.get("https://api.example.com/users/42", timeout=5)
assert r.status_code == 200
Output: first run records to YAML; subsequent runs replay offline. Useful for end-to-end style integration tests.
CI guidance: pin urllib3 and certifi in requirements.txt (not just requests) — a fresh resolver run can pull in a new urllib3 major that breaks tests with subtle retry-behaviour differences.
Troubleshooting common errors
| Error / Symptom | Likely cause | Fix |
|---|---|---|
requests.exceptions.SSLError: certificate verify failed | Private CA or MITM proxy | Set REQUESTS_CA_BUNDLE or verify="/path/to/bundle.pem"; install pip-system-certs for OS trust store. |
requests.exceptions.ConnectionError: HTTPSConnectionPool ... Max retries exceeded | DNS, firewall, or pool exhausted | Check name resolution; increase pool_maxsize; lower concurrency. |
requests.exceptions.ReadTimeout | Server slow or hung | Increase the read half of timeout=(connect, read); investigate upstream latency. |
urllib3.exceptions.NewConnectionError: [Errno 111] Connection refused | Wrong port or service down | Verify endpoint with curl -v; check service health. |
requests.exceptions.TooManyRedirects | Loop or unbounded chain | Set allow_redirects=False and inspect the Location header; bump s.max_redirects if legitimate. |
requests.exceptions.ChunkedEncodingError: Response ended prematurely | Connection closed mid-stream | Add retries on ChunkedEncodingError; consider stream=True and explicit iter_content. |
RequestsDependencyWarning: urllib3 (X.Y.Z) or chardet (...) doesn't match a supported version | Incompatible urllib3 in env | Re-pin urllib3 to the range listed in requests' setup metadata. |
requests.exceptions.MissingSchema | URL without http(s):// | Prepend the scheme; common after urljoin on a bare path. |
| Slow connection setup on cold start | TLS handshake every request | Reuse a Session; do not create one per request. |
LocationParseError: Failed to parse: ... | Whitespace or unicode in URL | quote() the path; strip incoming user input before passing to requests. |
Performance tuning
requests is sync, so its perf knobs are about avoiding waste — pool reuse, connection keep-alive, response streaming. The library can't compete with httpx (HTTP/2) or aiohttp (async) on pure throughput; tune for predictable latency instead.
- One
Sessionper process, mounted with a sized adapter. A newSession()per call pays full TLS handshake (~50-300 ms on first request) and skips keep-alive. pool_connections×pool_maxsize= total open sockets. Both should match your worker concurrency. The defaults (10 / 10) bottleneck multi-threaded workers fast.stream=Truefor responses > a few MB. Default reads the entire body into memory;iter_content(chunk_size=8192)lets you process incrementally.Connection: keep-aliveis implicit. As long as you reuse aSession, subsequent requests to the same host reuse the TCP+TLS state. The host must sendConnection: keep-alive(or omitConnection: close) for this to work.requests-cache— drop-in middleware that caches responses to SQLite / Redis. Massive win for read-heavy clients hitting idempotent endpoints.niquests— a drop-in fork with HTTP/2, HTTP/3, and async. Use when you've outgrownrequestsperf but don't want to rewrite tohttpx.- Compression.
Accept-Encoding: gzip, deflateis auto-added;requestsdecompresses transparently. Addbrotli(viapip install brotli) forbr-aware servers. - Avoid
json.loads(response.text). Useresponse.json()which decodes from bytes — one fewer copy and uses the cached charset.
Version migration guide
requests has been on 2.x since 2014. The library is stable enough that "migration" usually means a transitive-dep bump (especially urllib3) rather than requests itself. The list below is the gotcha list for users moving across recent minor releases.
2.28— dropped Python 2.7. Code targeting Python 2 needs a hard fork or replacement.2.30— switched the default character-detection backend fromchardettocharset-normalizer. The[use_chardet_on_py3]extra restores the old behavior. Most code is unaffected.2.31— security fix forProxy-Authorizationheader forwarding on redirects. Pin to>=2.31if you proxy auth.2.32— security fix for CRLF injection in user-supplied headers. The library now rejects newlines in header values; code that built headers withf"value\r\n..."(always a bug) now raises.urllib3 1.x → 2.x(2023) —requestsworks with both, but theurllib3major bump changed retry-after-header parsing, default certificate validation, and thepool_connectionssemantics slightly. Re-run integration tests when crossing this boundary.
The often-discussed requests 3.0 plan has been on the roadmap for years; treat the 2.x series as the production line indefinitely.
# Defensive pinning for production
pip install 'requests>=2.32,<3' 'urllib3>=2.0,<3' 'certifi>=2024.2'
Output: ranges that pick up security patches without surprise major bumps.
Ecosystem integrations
The requests ecosystem is so broad that almost any Python SDK either uses it or copies its API shape. Below are the integrations most teams reach for:
requests-toolbelt— official extension pack: streaming uploads, multipart progress, source-address binding, advanced sessions.requests-cache— transparent caching to SQLite, Redis, MongoDB; massive win for read-heavy clients.requests-mock/responses— test doubles;responsesintegrates withpytest,requests-mockprovides a fixture.vcrpy— record real HTTP traffic once, replay forever; good for integration-style tests.requests-oauthlib— OAuth1 and OAuth2 client flows.requests-html— adds HTML/CSS-selector parsing on top ofrequests(long-tail maintained).niquests— drop-in API-compatible fork with HTTP/2, HTTP/3, and async.pip-system-certs— redirectsrequests/urllib3to the OS trust store (useful behind corporate proxies).opentelemetry-instrumentation-requests— auto-tracing for OTel exporters.sentry-sdk— automatic request breadcrumbs and error capture.
When NOT to use this
requests is the right default, but a handful of situations call for something else:
- Inside
async defhandlers (FastAPI, Litestar, asyncio jobs) —requestsblocks the event loop. Usehttpx.AsyncClientinstead. - HTTP/2 or HTTP/3 required —
requestsis HTTP/1.1 only. Usehttpx[http2]orniquests. - Tens of thousands of concurrent outbound calls — sync model can't keep up. Switch to
aiohttporhttpx. - WebSockets —
requestshas no WS support. Usewebsocketsorwebsocket-client. - Streaming uploads with progress — usable, but
requests-toolbelt'sMultipartEncoderis the practical path (recipe above). - Tight memory or zero-dep constraints —
urllib(stdlib) is uglier but ships with Python.
Compatibility matrix
| Python | requests line | Notes |
|---|---|---|
| 2.7 | 2.27 and earlier | Final Python-2-compatible release; do not use for new projects. |
| 3.7 | up to 2.31 | Floor dropped in 2.32. |
| 3.8 | 2.28+ | Current minimum on recent releases. |
| 3.9 | 2.28+ | Stable. |
| 3.10 | 2.28+ | Stable; pattern matching usable in callers. |
| 3.11 | 2.31+ | Best perf via stdlib improvements. |
| 3.12 | 2.32+ | distutils removal handled. |
| 3.13 | 2.32+ | Latest stable; free-threaded build untested. |
Critical dependency pairs:
urllib3 1.xworks with allrequests 2.x; some retry semantics differ at the boundary.urllib3 2.xrequiresrequests >= 2.30and OpenSSL 1.1.1+ at runtime.certifiis unversioned inrequestsdeps — a stalecertificache silently misses new CA roots. Refresh annually.charset-normalizer >= 3shipped a major rewrite with subtle detection differences; pin minimums if you depend on exact charset-detection results.
See also
- Python: requests — API tutorial, examples, recipes
- Concept: HTTP — protocol fundamentals
- Packages: pip-httpx — the async-capable alternative
- Linux: curl — the upstream CLI inspiration