python/cpython · error · TypeError

Expected an object of type str for 'message', not {type(mess

Error message

Expected an object of type str for 'message', not {type(message).__name__!r}

What it means

The warnings.deprecated decorator (PEP 702) requires its message argument to be a plain str. Because the message is stored on the wrapped object as __deprecated__ and consumed by static analyzers and IDEs, non-string values (bytes, exceptions, f-string-like objects, None) are rejected immediately with a TypeError in __init__.

Source

Thrown at Lib/_py_warnings.py:791

    The deprecation message passed to the decorator is saved in the
    ``__deprecated__`` attribute on the decorated object.
    If applied to an overload, the decorator
    must be after the ``@overload`` decorator for the attribute to
    exist on the overload as returned by ``get_overloads()``.

    See PEP 702 for details.

    """
    def __init__(
        self,
        message: str,
        /,
        *,
        category: type[Warning] | None = DeprecationWarning,
        stacklevel: int = 1,
    ) -> None:
        if not isinstance(message, str):
            raise TypeError(
                f"Expected an object of type str for 'message', not {type(message).__name__!r}"
            )
        self.message = message
        self.category = category
        self.stacklevel = stacklevel

    def __call__(self, arg, /):
        # Make sure the inner functions created below don't
        # retain a reference to self.
        msg = self.message
        category = self.category
        stacklevel = self.stacklevel
        if category is None:
            arg.__deprecated__ = msg
            return arg
        elif isinstance(arg, type):
            import functools
            from types import MethodType

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Pass an f-string or literal str: @warnings.deprecated('foo() is deprecated; use bar()')
  2. If the message is dynamic, format it to str before decorating: @warnings.deprecated(f'use {replacement}')
  3. If targeting Python < 3.13, use typing_extensions.deprecated, which enforces the same str rule

Example fix

// before
@warnings.deprecated(b'use new_api')
def old_api(): ...

# after
@warnings.deprecated('old_api is deprecated; use new_api')
def old_api(): ...
Defensive patterns

Strategy: type-guard

Validate before calling

import warnings
msg = 'foo() is deprecated; use bar()'
assert isinstance(msg, str), 'deprecated() message must be str'

Type guard

def is_deprecation_message(msg: object) -> bool:
    return isinstance(msg, str) and bool(msg)

Prevention

When it happens

Trigger: @warnings.deprecated(b'use new_api instead'); @warnings.deprecated(None); @warnings.deprecated(some_exception_instance); passing a lazy formatting callable or a translated message object instead of a formatted str.

Common situations: Porting old code that used functools.wraps-style decorators accepting arbitrary objects; messages built from bytes or config blobs; backporting code to runtimes where warnings.deprecated does not exist and substituting a look-alike shim; accidentally passing the docstring keyword instead of message.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/bfec65720fe22348. Report an issue: GitHub.