psf/requests
Treat this repo as a stability-first, extremely high-blast-radius library: the maintainers protect version, changelog, workflows and pyproject behind CODEOWNERS, and the project has a written policy banning unsupervised agentic contributions. Read `sessions.py`, `models.py` and `adapters.py` before touching anything; almost every non-obvious branch in them encodes a specific GitHub issue number that you should look up before 'simplifying' it. Local loop is `make init` then `make test`; before pushing, run `pre-commit run --all-files` and `pyright src/requests/`, since lint and typecheck are separate required CI gates. The biggest trap is that `tests/` is excluded from pyright but `src/requests/` is checked in `strict` mode, so type annotations in library code must be complete and the codebase is dense with deliberate `# type: ignore` comments carrying explanatory reasons — preserve them.
- `.github/AI_POLICY.md` states 'Absolutely no unsupervised agentic tools' and requires a human copyright owner per contribution with no LLM bots in `Co-authored-by:` — do not open PRs against this upstream without explicit human sponsorship.
- Read first, in order: `src/requests/api.py` (surface), `src/requests/sessions.py` (orchestration), `src/requests/models.py` (value objects), `src/requests/adapters.py` (transport). These four contain the whole request lifecycle.
- Local setup and test: `make init` installs `requirements-dev.txt` (editable `.[socks]`, pytest, pytest-httpbin, httpbin, trustme); `make test` runs `pytest tests`; `make ci` matches CI exactly.
- Two additional gates must pass before merge: `pre-commit run --all-files` (ruff-check --fix and ruff-format) and `python -m pyright src/requests/` under `typeCheckingMode = "strict"`.
- Style is enforced by ruff with rule sets E/W/F/I/UP/T10 targeting py310, ignoring E203/E501/UP031, with per-file `E402,F401` exemptions for `src/requests/__init__.py` and `src/requests/compat.py` — do not 'clean up' those unused imports, they are the public re-export surface.
- `src/requests/_types.py` is explicitly documented as not part of the public API; it hosts the `RequestKwargs`/`GetKwargs`/`PostKwargs`/`DataKwargs` TypedDicts consumed via `Unpack` in `api.py` and `sessions.py`, so adding a request kwarg means editing both the runtime signature and the TypedDict.
- `_types.is_prepared()` is a typing-only narrowing helper that returns `True` unconditionally at runtime; the many `assert _is_prepared(request)` lines are type assertions, not validation — do not convert them into runtime checks.
- Safe edit boundary: new transport behaviour belongs in an `HTTPAdapter` subclass or the documented override methods, not in `Session.send`, because `Session.send` is also reached by `resolve_redirects` and any change there multiplies across every redirect hop. (inferred)
- Trap: `prepare_cookies` can only meaningfully run once per PreparedRequest — cookielib will not regenerate the header if `Cookie` already exists, which is why `resolve_redirects` pops the header before re-preparing.
- Trap: branch comments cite issue numbers as the rationale (e.g. #1084 for the 307/308 carve-out, #3490 for the purged headers, #1704 for POST-301→GET, #3772 for digest-only-on-4xx, #5229 for the `fileno` AttributeError). Look these up before changing the branch.
- Trap: `compat.py` rebinds `str` and `bytes` to the builtins and exports `basestring = (str, bytes)`; modules importing from it (`auth.py`, `utils.py`, `adapters.py`) rely on these shadows, so removing them is a wide breaking change.
- Tests requiring network-like behaviour need the local httpbin fixture; use the `httpbin` fixture (which yields a URL-joining callable, not a string) rather than hardcoding `https://httpbin.org`.
- New doctests are load-bearing: because `--doctest-modules` is on, any `>>>` example added to a library docstring will be executed by `make test` and must actually pass.
- Version bumps live in `src/requests/__version__.py` (both `__version__` and the hex `__build__`), are resolved dynamically by setuptools, and are CODEOWNERS-gated — never bump as part of a feature PR.
- Compatibility floor is Python 3.10 enforced in three places — `requires-python`, the `setup.py` guard that prints an error and exits 1, and ruff's `target-version = "py310"` — so no 3.11+ syntax.
- Behaviour must hold across both urllib3 major versions and with no charset library at all; CI has dedicated `urllib3 1.x` and `No Character Detection` jobs, and `compat.is_urllib3_1` exists for version-conditional code.