7
High
// The Bug
Fixed pypa/pip#13922 — 11 line bug-fix replacing deprecated locale.getpreferredencoding() with locale.getencoding() on Python 3.11+.
// Root Cause
Python 3.11 deprecated `locale.getpreferredencoding()` with `do_setlocale=False` in favor of the more explicit `locale.getencoding()`. Pip's `Configuration` class and `parse_reqfile` helper both called the deprecated function to determine the system locale encoding when reading config files and requirement files.
The deprecation produces a `DeprecationWarning` at runtime. While the warning is silent by default in CPython, it surfaces in CI pipelines running with `-W error` or `-X dev` mode — breaking builds that treat warnings as errors. The fix is a version-gated switch: use `locale.getencoding()` on Python 3.11+ and fall back to the deprecated path on older interpreters.
### Fix Location
The change touches three distinct sites in `src/pip/_internal/configuration.py`:
1. **`Configuration.__init__`** — reading pip config files with locale-aware encoding
2. **`_decode_req_file`** — decoding requirement file contents when the default encoding fails
3. **Test mocks** — updating the test suite to patch both `getpreferredencoding` and `getencoding`
### Why It Was Missed
The deprecation entered in Python 3.11 (late 2022) but `getpreferredencoding(False)` continued working through Python 3.13 with only a warning. Most test environments ran on Python 3.12+ without `-W error`, so the deprecation flew under the radar. It surfaced only when users upgraded CI runners to Python 3.13 with strict warning policies.
The deprecation produces a `DeprecationWarning` at runtime. While the warning is silent by default in CPython, it surfaces in CI pipelines running with `-W error` or `-X dev` mode — breaking builds that treat warnings as errors. The fix is a version-gated switch: use `locale.getencoding()` on Python 3.11+ and fall back to the deprecated path on older interpreters.
### Fix Location
The change touches three distinct sites in `src/pip/_internal/configuration.py`:
1. **`Configuration.__init__`** — reading pip config files with locale-aware encoding
2. **`_decode_req_file`** — decoding requirement file contents when the default encoding fails
3. **Test mocks** — updating the test suite to patch both `getpreferredencoding` and `getencoding`
### Why It Was Missed
The deprecation entered in Python 3.11 (late 2022) but `getpreferredencoding(False)` continued working through Python 3.13 with only a warning. Most test environments ran on Python 3.12+ without `-W error`, so the deprecation flew under the radar. It surfaced only when users upgraded CI runners to Python 3.13 with strict warning policies.
// The Fix
Diff showing the exact changes made to fix the bug.
@@ -293,7 +293,10 @@ class Configuration:
if os.path.exists(fname):
- locale_encoding = locale.getpreferredencoding(False)
+ if sys.version_info >= (3, 11):
+ locale_encoding = locale.getencoding()
+ else:
+ locale_encoding = locale.getpreferredencoding(False)// Pattern & Takeaways
**Pattern**: Version-gated API migration when a language deprecates a commonly-used standard library function. The fix pattern generalizes to any Python project using `locale.getpreferredencoding()` or similar deprecated stdlib interfaces.
**Key insight**: When a stdlib function enters deprecation, the safest migration path uses a version gate with a fallback. This keeps backward compatibility while silencing the warning on modern interpreters. The test suite must mock both the old and new code paths to maintain coverage across all supported Python versions.
**Code review checklist for stdlib deprecation fixes:**
1. Identify all call sites — a single deprecation can appear in multiple functions within the same module
2. Mock both the old and new APIs in tests — otherwise CI passes on 3.12 but breaks on 3.9
3. Verify the fallback path — `locale.getencoding()` can return `None` on some systems, so handle that case explicitly
**Key insight**: When a stdlib function enters deprecation, the safest migration path uses a version gate with a fallback. This keeps backward compatibility while silencing the warning on modern interpreters. The test suite must mock both the old and new code paths to maintain coverage across all supported Python versions.
**Code review checklist for stdlib deprecation fixes:**
1. Identify all call sites — a single deprecation can appear in multiple functions within the same module
2. Mock both the old and new APIs in tests — otherwise CI passes on 3.12 but breaks on 3.9
3. Verify the fallback path — `locale.getencoding()` can return `None` on some systems, so handle that case explicitly