RustPython/RustPython · error · TypeError
invalid encoding: %r
Error message
invalid encoding: %r
What it means
TypeError raised by _pyio.open() when encoding is given and is not a str (and not None). encoding is only meaningful in text mode and must be a codec name accepted by codecs.lookup(), such as 'utf-8', 'ascii', or 'latin-1'; None selects the platform locale default. The check runs before any file descriptor is opened, so no file is created or truncated as a side effect.
Source
Thrown at Lib/_pyio.py:203
mode, it returns a BufferedReader; in write binary and append binary
modes, it returns a BufferedWriter, and in read/write mode, it returns
a BufferedRandom.
It is also possible to use a string or bytearray as a file for both
reading and writing. For strings StringIO can be used like a file
opened in a text mode, and for bytes a BytesIO can be used like a file
opened in a binary mode.
"""
if not isinstance(file, int):
file = os.fspath(file)
if not isinstance(file, (str, bytes, int)):
raise TypeError("invalid file: %r" % file)
if not isinstance(mode, str):
raise TypeError("invalid mode: %r" % mode)
if not isinstance(buffering, int):
raise TypeError("invalid buffering: %r" % buffering)
if encoding is not None and not isinstance(encoding, str):
raise TypeError("invalid encoding: %r" % encoding)
if errors is not None and not isinstance(errors, str):
raise TypeError("invalid errors: %r" % errors)
modes = set(mode)
if modes - set("axrwb+t") or len(mode) > len(modes):
raise ValueError("invalid mode: %r" % mode)
creating = "x" in modes
reading = "r" in modes
writing = "w" in modes
appending = "a" in modes
updating = "+" in modes
text = "t" in modes
binary = "b" in modes
if text and binary:
raise ValueError("can't have text and binary mode at once")
if creating + reading + writing + appending > 1:
raise ValueError("can't have read/write/append mode at once")
if not (creating or reading or writing or appending):
raise ValueError("must have exactly one of read/write/append mode")View on GitHub (pinned to aaeab4f754)
Solutions
- Pass a codec name string: encoding='utf-8'
- Use None (the default) to select the platform locale encoding
- Validate config values with codecs.lookup(encoding) before forwarding them to open()
Example fix
# before f = open(path, 'r', encoding=65001) # after f = open(path, 'r', encoding='cp65001') # or 'utf-8'
Defensive patterns
Strategy: validation
Validate before calling
import codecs
if encoding is not None:
if not isinstance(encoding, str):
raise TypeError(f'encoding must be a codec name str, got {encoding!r}')
codecs.lookup(encoding) # fails fast on unknown names too
f = open(path, mode, encoding=encoding) Type guard
def is_codec_name(value) -> bool:
return value is None or (isinstance(value, str) and _try(lambda: codecs.lookup(value))) Prevention
- Use codec name strings ('utf-8', 'cp65001'), never numeric code pages
- Validate config-supplied encodings against codecs.lookup() at startup
- Prefer keyword arguments encoding=..., errors=... to avoid slot swaps
When it happens
Trigger: encoding=65001 (a Windows code-page number instead of the string 'cp65001' or 'utf-8'); encoding=['utf-8'] from an over-eager config loader that wraps scalars in lists; the 4th positional argument slot holding a non-string due to argument misplacement.
Common situations: Porting Windows batch-script habits where code pages are integers; YAML/JSON configs that parse values as lists or numbers; mixing up the encoding and errors positional slots when refactoring call signatures.
Related errors
- invalid errors: %r
- invalid mode: %r
- invalid buffering: %r
- invalid file: %r
- can't have text and binary mode at once
AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17).
Data as JSON: /api/errors/9c7be1c629239681.
Report an issue: GitHub.