psf/requests · error · InvalidHeader

Header part ({header_part!r}) from {header} must be of type

Error message

Header part ({header_part!r}) from {header} must be of type str or bytes, not {type(header_part)}

What it means

Raised as `requests.exceptions.InvalidHeader` from `_validate_header_part()` in `src/requests/utils.py:1109`. Before sending, requests validates every header name and value via `check_header_validity()`; each part must be `str` or `bytes` so it can be matched against the appropriate regex validator. Any other type (int, None, list, dict, etc.) cannot be validated or serialized onto the wire, so requests fails fast instead of sending a malformed header.

Source

Thrown at src/requests/utils.py:1109

    :param header: tuple, in the format (name, value).
    """
    name, value = header
    _validate_header_part(header, name, 0)
    _validate_header_part(header, value, 1)


def _validate_header_part(
    header: tuple[str | bytes, str | bytes],
    header_part: str | bytes,
    header_validator_index: int,
) -> None:
    if isinstance(header_part, str):
        validator = _HEADER_VALIDATORS_STR[header_validator_index]
    elif isinstance(header_part, bytes):
        # runtime guard for non-str/bytes input
        validator = _HEADER_VALIDATORS_BYTE[header_validator_index]
    else:
        raise InvalidHeader(
            f"Header part ({header_part!r}) from {header} "
            f"must be of type str or bytes, not {type(header_part)}"
        )

    if not validator.match(header_part):  # type: ignore[arg-type]
        header_kind = "name" if header_validator_index == 0 else "value"
        raise InvalidHeader(
            f"Invalid leading whitespace, reserved character(s), or return "
            f"character(s) in header {header_kind}: {header_part!r}"
        )


def urldefragauth(url: str) -> str:
    """
    Given a url remove the fragment and the authentication part.

    :rtype: str
    """

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Convert the offending header value to a string: `headers={'X-Retry-Count': str(3)}` — the exception message shows exactly which part and its type.
  2. If loading headers from config/JSON, coerce all values with `{k: str(v) for k, v in headers.items() if v is not None}`.
  3. To remove a default header on a Session, delete it (`del session.headers['Accept-Encoding']`) rather than setting it to None per-request only works at the Session level — per-request None is invalid.
  4. For multiple values, join them yourself: `', '.join(values)`.

Example fix

# before
requests.get(url, headers={'X-Request-Id': uuid.uuid4(), 'X-Retries': 3})
# after
requests.get(url, headers={'X-Request-Id': str(uuid.uuid4()), 'X-Retries': '3'})
Defensive patterns

Strategy: type-guard

Validate before calling

def headers_are_valid_types(headers):
    return all(isinstance(k, (str, bytes)) and isinstance(v, (str, bytes)) for k, v in headers.items())

Type guard

def is_header_part(value):
    """Requests requires header names/values to be str or bytes."""
    return isinstance(value, (str, bytes))

Try / catch

try:
    resp = requests.get(url, headers=headers)
except requests.exceptions.InvalidHeader as e:
    log.error('Header has wrong type: %s', e)

Prevention

When it happens

Trigger: Passing a non-string header value or name in the `headers=` dict of any request call, e.g. `requests.get(url, headers={'X-Retry-Count': 3})` or `{'Authorization': None}` (when set per-request rather than deleted), or setting `session.headers['X-Id'] = uuid.uuid4()` without str().

Common situations: Numeric values (ports, counts, timestamps) placed directly into headers without str(); values loaded from JSON/YAML config that parse as int/bool/None; passing a `uuid.UUID`, `datetime`, or f-string forgotten object; accidentally assigning a list of values instead of a comma-joined string.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/3463071ff348f5e6.json. Report an issue: GitHub ↗.