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