CodeVetter · Repo Unpack · Behavior traces
psf/requests
Three flows carry most of the library's behaviour: the one-shot `requests.get()` path, the redirect-following loop inside `Session.send`, and the digest-auth 401 retry. All three converge on `Session.send` → `HTTPAdapter.send` → `build_response`. The redirect loop is a generator (`resolve_redirects`) that is fully drained inside `send`, which is why history arrives pre-assembled on the returned `Response`. The digest flow is unusual in that it re-enters the transport from a response hook (`r.connection.send(prep)`), bypassing `Session.send` entirely.
- Import trace: `requests/__init__.py` runs `check_compatibility` → optional `pyopenssl.inject_into_urllib3` → `warnings.simplefilter('ignore', DependencyWarning)` → re-exports → `logging.getLogger(__name__).addHandler(NullHandler())` → `simplefilter('default', FileModeWarning, append=True)`.
- One-shot GET trace: `api.get()` → `api.request()` opens `Session()` in a `with` → `Session.request()` → builds `Request` → `Session.prepare_request()` → `merge_environment_settings()` → `Session.send()` → returns, and `__exit__` calls `Session.close()` closing every adapter.
- Preparation trace: `prepare_request` → `PreparedRequest.prepare()` → `prepare_method` (uppercases) → `prepare_url` (IDNA + `requote_uri(urlunparse(...))`) → `prepare_headers` (`check_header_validity` per header) → `prepare_cookies` (sets `Cookie` header once) → `prepare_body` → `prepare_auth` → `prepare_hooks`.
- Send trace: `Session.send` defaults stream/verify/cert, rejects a bare `Request` with `ValueError("You can only send PreparedRequests.")`, resolves the adapter by longest matching prefix via `get_adapter`, times the call with `preferred_clock` into `r.elapsed`, then dispatches the `response` hook.
- Adapter trace: `HTTPAdapter.send` → `get_connection_with_tls_context` → `cert_verify` (sets `conn.ca_certs`/`cert_file`, raising `OSError` if any path is missing) → `request_url` (full URL for proxied HTTP, path-only otherwise) → `add_headers` (no-op hook) → `conn.urlopen(...)` → `build_response`.
- Redirect trace: `Session.send` calls `resolve_redirects(r, request)` and drains the generator into `history`; the generator per hop consumes `resp.content`, checks `len(resp.history) >= self.max_redirects` raising `TooManyRedirects`, calls `resp.close()`, normalises the Location, and copies the request.
- On a non-307/308 redirect the loop strips `Content-Length`, `Content-Type` and `Transfer-Encoding` and blanks `prepared_request.body`, then always pops the `Cookie` header before re-deriving it from the jar.
- Method rewriting per hop is `rebuild_method`: 303 → GET (unless HEAD), 302 → GET (unless HEAD, 'what browsers do, despite standards'), and 301 on a POST → GET.
- Auth rewriting per hop is `rebuild_auth` → `should_strip_auth`, which drops `Authorization` on any hostname change but deliberately preserves it for http:80 → https:443 on the same host for backwards compatibility.
- Body rewind trace: a redirect with `_body_position` set and a length header triggers `rewind_body(prepared_request)`; a prior failed `tell()` stores a sentinel `object()` so the rewind raises `UnrewindableBodyError` instead of hanging the connection.
- Non-following trace: when `allow_redirects=False`, `Session.send` primes `r._next` by taking one item from `resolve_redirects(..., yield_requests=True)` inside a `try/except StopIteration`, which is what backs `Response.next`.
- Digest trace: `HTTPDigestAuth.__call__` registers `handle_401` and `handle_redirect` as response hooks and records `r.body.tell()`; on a 4xx `handle_401` parses `www-authenticate`, consumes and closes the response, copies the request, builds the `Authorization` header and re-sends via `r.connection.send(prep, **kwargs)`, appending the original to `_r.history`.
- Digest state is per-thread: `init_per_thread_state()` lazily populates `threading.local()` with `last_nonce`, `nonce_count`, `chal`, `pos` and `num_401_calls`, and the retry is capped at `num_401_calls < 2`.
- Streaming-read trace: `iter_content` builds a generator that prefers `self.raw.stream(chunk_size, decode_content=True)`, translating `ProtocolError`→`ChunkedEncodingError`, `DecodeError`→`ContentDecodingError`, `ReadTimeoutError`→`ConnectionError` and urllib3 `SSLError`→`requests.SSLError`, then sets `_content_consumed`.
- Teardown trace: `Session.close()` iterates adapters calling `HTTPAdapter.close()`, which does `poolmanager.clear()` and clears every cached proxy manager.