python/cpython · error · TypeError

category must be a Warning subclass, not '{type(category).__

Error message

category must be a Warning subclass, not '{type(category).__name__}'

What it means

Raised by the pure-Python warnings.warn fallback when the category argument is not a type at all — e.g. a string like 'DeprecationWarning' or an instance like UserWarning('x'). warn() needs a class it can instantiate with the message, so a non-class category fails fast with TypeError. The companion check at the next branch covers classes that are types but not Warning subclasses.

Source

Thrown at Lib/_py_warnings.py:475

    while frame is not None and (
            _is_internal_filename(filename := frame.f_code.co_filename) or
            _is_filename_to_skip(filename, skip_file_prefixes)):
        frame = frame.f_back
    return frame


# Code typically replaced by _warnings
def warn(message, category=None, stacklevel=1, source=None,
         *, skip_file_prefixes=()):
    """Issue a warning, or maybe ignore it or raise an exception."""
    # Check if message is already a Warning object
    if isinstance(message, Warning):
        category = message.__class__
    # Check category argument
    if category is None:
        category = UserWarning
    elif not isinstance(category, type):
        raise TypeError(f"category must be a Warning subclass, not "
                        f"'{type(category).__name__}'")
    elif not issubclass(category, Warning):
        raise TypeError(f"category must be a Warning subclass, not "
                        f"class '{category.__name__}'")
    if not isinstance(skip_file_prefixes, tuple):
        # The C version demands a tuple for implementation performance.
        raise TypeError('skip_file_prefixes must be a tuple of strs.')
    if skip_file_prefixes:
        stacklevel = max(2, stacklevel)
    # Get context information
    try:
        if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)):
            # If frame is too small to care or if the warning originated in
            # internal code, then do not try to hide any frames.
            frame = sys._getframe(stacklevel)
        else:
            frame = sys._getframe(1)
            # Look for one frame less since the above line starts us off.

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Pass the class: warnings.warn('msg', DeprecationWarning)
  2. Resolve names to classes first: importlib + getattr on the warnings module, then verify it is a Warning subclass
  3. If the value is already a Warning instance, pass it as the message: warnings.warn(instance) uses its class

Example fix

# before
warnings.warn('feature X is going away', 'DeprecationWarning')  # TypeError

# after
warnings.warn('feature X is going away', DeprecationWarning)
Defensive patterns

Strategy: type-guard

Validate before calling

import warnings

def warn_with_category(message, category):
    if isinstance(message, Warning):
        return warnings.warn(message)
    if isinstance(category, str):
        category = getattr(warnings, category, None) or __import__('builtins').__dict__.get(category)
    if not (isinstance(category, type) and issubclass(category, Warning)):
        raise TypeError(f'category must be a Warning subclass, got {category!r}')
    return warnings.warn(message, category)

Type guard

def is_warning_class(cat) -> bool:
    return isinstance(cat, type) and issubclass(cat, Warning)

Prevention

When it happens

Trigger: warnings.warn('msg', 'DeprecationWarning'); warnings.warn('msg', SomeWarning('extra')); forwarding a category that arrived as text from config/logging records.

Common situations: String category names from log records, serialization layers, or CLI options passed straight to warn; instantiating the category out of habit; dynamically built warn wrappers that lose track of whether they hold a name or a class.

Related errors


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