python/cpython · error · TypeError

@deprecated decorator with non-None category must be applied

Error message

@deprecated decorator with non-None category must be applied to a class or callable, not {arg!r}

What it means

warnings.deprecated(category=None) can wrap anything (it is a no-op passthrough used for static typing), but with a non-None category it must install a runtime wrapper that emits a warning on call or instantiation. That is only possible for classes and callables, so decorating any other object (an int, module, instance, property object) raises TypeError at decoration time.

Source

Thrown at Lib/_py_warnings.py:867

            arg.__deprecated__ = __new__.__deprecated__ = msg
            __init_subclass__.__deprecated__ = msg
            return arg
        elif callable(arg):
            import functools
            import inspect

            @functools.wraps(arg)
            def wrapper(*args, **kwargs):
                _wm.warn(msg, category=category, stacklevel=stacklevel + 1)
                return arg(*args, **kwargs)

            if inspect.iscoroutinefunction(arg):
                wrapper = inspect.markcoroutinefunction(wrapper)

            arg.__deprecated__ = wrapper.__deprecated__ = msg
            return wrapper
        else:
            raise TypeError(
                "@deprecated decorator with non-None category must be applied to "
                f"a class or callable, not {arg!r}"
            )


_DEPRECATED_MSG = "{name!r} is deprecated and slated for removal in Python {remove}"


def _deprecated(name, message=_DEPRECATED_MSG, *, remove, _version=sys.version_info):
    """Warn that *name* is deprecated or should be removed.

    RuntimeError is raised if *remove* specifies a major/minor tuple older than
    the current Python version or the same version but past the alpha.

    The *message* argument is formatted with *name* and *remove* as a Python
    version tuple (e.g. (3, 11)).

    """

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Deprecate a getter function that returns the constant, and warn there
  2. If the target is a module, deprecate its public functions/classes individually, or emit warnings.warn at module import in a wrapper module
  3. If you only need static-analysis marking with no runtime warning, call deprecated with category=None on a suitable target, or set __deprecated__ manually on the object

Example fix

// before
LIMIT = 1000
LIMIT = warnings.deprecated('use LIMIT_V2')(LIMIT)  # TypeError

# after
def get_limit():
    warnings.warn('LIMIT is deprecated; use LIMIT_V2', DeprecationWarning, stacklevel=2)
    return 1000
Defensive patterns

Strategy: type-guard

Validate before calling

import typing
def deprecated_or_identity(obj):
    return warnings.deprecated('use something else')(obj) if callable(obj) else obj

Type guard

def is_decoratable(obj: object) -> bool:
    import inspect
    return inspect.isclass(obj) or callable(obj)

Prevention

When it happens

Trigger: @warnings.deprecated('msg') applied to a module-level variable (e.g. an int constant), an imported module object, a dataclass field/property, or the result of another decorator that returned a non-callable; assigning the decorator to an instance attribute.

Common situations: Trying to deprecate a constant or module rather than its accessor function; decorating a property getter inside a class body; stacking decorators in the wrong order so deprecated receives a non-callable intermediate value.

Related errors


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