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

  1. Pass a codec name string: encoding='utf-8'
  2. Use None (the default) to select the platform locale encoding
  3. 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

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


AI-assisted analysis of RustPython/RustPython@aaeab4f754 (2026-08-17). Data as JSON: /api/errors/9c7be1c629239681. Report an issue: GitHub.