python/cpython · error · TypeError

category must be a Warning subclass, not class '{category.__

Error message

category must be a Warning subclass, not class '{category.__name__}'

What it means

Raised by the pure-Python warnings.warn fallback when category is a class but does not subclass Warning. warn() instantiates category(message) to build the Warning object, so arbitrary exception classes (ValueError, Exception, custom error hierarchies) are rejected with TypeError naming the offending class.

Source

Thrown at Lib/_py_warnings.py:478

        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.
            for x in range(stacklevel-1):
                frame = _next_external_frame(frame, skip_file_prefixes)
                if frame is None:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Define the warning as a Warning subclass: class MyWarning(UserWarning): ... and pass that
  2. Pick a built-in category that fits (UserWarning, DeprecationWarning, RuntimeWarning, FutureWarning, ResourceWarning)
  3. If you actually want an exception, use raise, not warnings.warn

Example fix

# before
class ConfigError(Exception): ...
warnings.warn('unknown key', ConfigError)  # TypeError: not a Warning subclass

# after
class ConfigWarning(UserWarning): ...
warnings.warn('unknown key', ConfigWarning)
Defensive patterns

Strategy: type-guard

Validate before calling

def as_warning_category(cat):
    if isinstance(cat, type) and issubclass(cat, Warning):
        return cat
    raise TypeError(f'{cat!r} is not a Warning subclass; derive it from Warning/UserWarning')

import warnings
warnings.warn('deprecated', as_warning_category(cfg_category))

Type guard

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

Prevention

When it happens

Trigger: warnings.warn('bad input', ValueError); warnings.warn('retrying', MyException) where MyException extends Exception; mixing up exception-raising and warning-issuing paths in shared helper code.

Common situations: Libraries that 'sometimes warn, sometimes raise' and pass the same exception class to both; new team members assuming warn accepts exceptions; refactoring raises into warns without changing the class; custom exceptions meant to double as warning categories.

Related errors


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