← board

mimic_codecs — the stdlib-C edge the html5lib ladder starts on

The surface, measured — not a guess at "all of codecs"

Everything webencodings touches, and nothing else:

codecs.lookup(name) -> CodecInfo        codecs.register(search_function)
codecs.CodecInfo(...)
codecs.charmap_build(decoding_table)
codecs.charmap_decode(input, errors, decoding_table)
codecs.charmap_encode(input, errors, encoding_table)
base classes: Codec, IncrementalDecoder, IncrementalEncoder,
              StreamReader, StreamWriter

The charmap trio is the substantive part and it is small: a 256-entry table maps bytes to code points and back, with an errors policy (strict raising, replace, ignore) — the same shape the RTL's existing encoding work already has to reason about.

lookup needs a registry keyed by the normalised encoding name, seeded with whatever encodings we can actually decode, plus register so a library can add its own search function (which is exactly how webencodings installs x-user-defined).

Naming

mimic_codecs, per devdocs/dev/python-compat-tiers.md: import codecs RESOLVES to it through the import resolver, so no file in the tree carries the stdlib's name and --no-shims can make the substitution an error.

Why it is worth doing beyond one library

Class 4 is the recurring cost every Python app pays — codecs re zlib json hashlib socket ssl — and it is the same list for every app, not a per-library problem. codecs is the one the html5lib ladder hits FIRST, and it is the smallest of them.

Gate

make lib-test green, plus the real forcing test: webencodings/__init__.py and x_user_defined.py compile with pxx, and a .npy that decodes and encodes through x-user-defined matches CPython byte for byte.

2026-08-15 (Track B) — lib/rtl/mimic_codecs.pas lands, CPython-diffed

import codecs resolves to it through the resolver's mimic_ fallback (the compile prints note: codecs -> mimic_codecs (shim, subset)), so no file in the tree carries the stdlib's name and --no-shims still turns the substitution into an error.

The surface, as built

Exactly the measured list in this ticket, re-checked against library_candidates/webencodings rather than taken from the ticket text: charmap_build / charmap_decode / charmap_encode, lookup / register / CodecInfo, and the five base classes Codec, IncrementalEncoder, IncrementalDecoder, StreamReader, StreamWriter. Added beyond it, because they are one line each once the tables exist and encoding-detection code reaches for them by name: codecs.encode / codecs.decode and the five BOM_* constants.

Design notes worth keeping:

Gate

test/lib_codecs.npy runs unchanged under python3 and lib-test diffs the two outputs — the nilsh pattern, and the only way to know a charmap codec is right rather than merely self-consistent. It covers the x-user-defined round trip (built the way x_user_defined.py builds it), the three error policies including the UnicodeDecodeError raise, and name normalisation folding case/underscore/ hyphen together. Output identical to CPython. make lib-test GREEN with lib-test: mimic_codecs matches CPython in the log.

The forcing test: HALF of it passes, and the other half is not this lane's

This ticket's gate asks for three things. Measured, on pinned v339:

Resolved rather than parked, because the deliverable — the shim — is complete, gated and CPython-diffed, and nothing further about codecs is what the ladder is waiting on. The remaining distance is tracked on [[feature-nilpy-thirdparty-libraries-as-targets]], which now records that a shim only reveals the next wall and that its per-file table is the FIRST wall, not the remaining work.

Also closes [[feature-nilpy-codecs-shim]], which asks for the same unit from the other direction and was opened first.

Log