Source code for statsmodels.tools.docstring_helpers

"""
Vendored docstring decorators — previously imported from pandas.util._decorators.
No longer depend on pandas internals.

Originally derived from matplotlib.docstring (1.1.0).
Vendored to remove statsmodels' dependency on pandas private API.
"""

from __future__ import annotations

from functools import wraps
from textwrap import dedent
from typing import Any, Callable, Mapping, TypeVar
import warnings

F = TypeVar("F", bound=Callable[..., Any])


def deprecate_kwarg(
    old_arg_name: str,
    new_arg_name: str | None,
    mapping: Mapping[Any, Any] | Callable[[Any], Any] | None = None,
    stacklevel: int = 2,
) -> Callable[[F], F]:
    """
    Decorator to deprecate a keyword argument of a function.

    Vendored from pandas.util._decorators to remove dependency on
    pandas private API.

    Parameters
    ----------
    old_arg_name : str
        Name of argument in function to deprecate.
    new_arg_name : str or None
        Name of preferred argument in function. Use None to raise
        warning that ``old_arg_name`` keyword is deprecated with
        no replacement.
    mapping : dict or callable, optional
        If mapping is present, use it to translate old arguments to
        new arguments. A callable must do its own value checking;
        values not found in a dict will be forwarded unchanged.
    stacklevel : int, default 2
        Stack level for the warning.

    Examples
    --------
    The following deprecates 'cols', using 'columns' instead:

    >>> @deprecate_kwarg(old_arg_name='cols', new_arg_name='columns')
    ... def f(columns=''):
    ...     print(columns)
    """
    if mapping is not None and not hasattr(mapping, "get") and not callable(mapping):
        raise TypeError(
            "mapping from old to new argument values must be dict or callable!"
        )

    def _deprecate_kwarg(func: F) -> F:
        @wraps(func)
        def wrapper(*args, **kwargs):
            __tracebackhide__ = True
            old_arg_value = kwargs.pop(old_arg_name, None)

            if old_arg_value is not None:
                if new_arg_name is None:
                    msg = (
                        f"the {old_arg_name!r} keyword is deprecated and "
                        "will be removed in a future version. Please take "
                        f"steps to stop the use of {old_arg_name!r}"
                    )
                    warnings.warn(msg, FutureWarning, stacklevel=stacklevel)
                    kwargs[old_arg_name] = old_arg_value
                    return func(*args, **kwargs)

                elif mapping is not None:
                    if callable(mapping):
                        new_arg_value = mapping(old_arg_value)
                    else:
                        new_arg_value = mapping.get(old_arg_value, old_arg_value)
                    msg = (
                        f"the {old_arg_name}={old_arg_value!r} keyword is "
                        f"deprecated, use "
                        f"{new_arg_name}={new_arg_value!r} instead."
                    )
                else:
                    new_arg_value = old_arg_value
                    msg = (
                        f"the {old_arg_name!r} keyword is deprecated, "
                        f"use {new_arg_name!r} instead."
                    )

                warnings.warn(msg, FutureWarning, stacklevel=stacklevel)
                if kwargs.get(new_arg_name) is not None:
                    msg = (
                        f"Can only specify {old_arg_name!r} "
                        f"or {new_arg_name!r}, not both."
                    )
                    raise TypeError(msg)
                kwargs[new_arg_name] = new_arg_value
            return func(*args, **kwargs)

        return wrapper

    return _deprecate_kwarg


[docs] class Appender: """ A function decorator that will append an addendum to the docstring of the target function. This decorator is robust even if func.__doc__ is None (for example, if -OO was passed to the interpreter). Usage: construct an Appender with a string to be joined to the original docstring. An optional 'join' parameter may be supplied which will be used to join the docstring and addendum. e.g. add_copyright = Appender("Copyright (c) 2009", join='\n') @add_copyright def my_dog(has='fleas'): "This docstring will have a copyright below" pass Parameters ---------- addendum : str or None String to append to the wrapped function's docstring. join : str, optional A string placed between the original docstring and the addendum. Default is "". indents : int, optional Number of indents (4-space blocks) added to all lines of the addendum. Default is 0. """ addendum: str | None def __init__(self, addendum: str | None, join: str = "", indents: int = 0) -> None: if indents > 0: self.addendum = indent(addendum, indents=indents) else: self.addendum = addendum self.join = join def __call__(self, func): func.__doc__ = func.__doc__ if func.__doc__ else "" self.addendum = self.addendum if self.addendum else "" docitems = [func.__doc__, self.addendum] func.__doc__ = dedent(self.join.join(docitems)) return func
[docs] class Substitution: """ A decorator to take a function's docstring and perform string substitution on it. This decorator is robust even if func.__doc__ is None (for example, if -OO was passed to the interpreter). Usage: construct a Substitution with a sequence or dictionary suitable for performing substitution; then decorate a suitable function with the constructed object. e.g. sub_author_name = Substitution(author='Jason') @sub_author_name def some_function(x): "%(author)s wrote this function" # note that some_function.__doc__ is now "Jason wrote this function" One can also use positional arguments: sub_first_last_names = Substitution('Edgar Allen', 'Poe') @sub_first_last_names def some_function(x): "%s %s wrote the Raven" Parameters ---------- *args : str Positional arguments for %s-style substitution. **kwargs : str Keyword arguments for %(name)s-style substitution. Cannot be combined with positional args. """ def __init__(self, *args: object, **kwargs: object) -> None: if args and kwargs: raise AssertionError("Only positional or keyword args are allowed") self.params: tuple | dict = args or kwargs def __call__(self, func): func.__doc__ = func.__doc__ and func.__doc__ % self.params return func
[docs] def update(self, *args: object, **kwargs: object) -> None: """ Update self.params with supplied args. Only valid when Substitution was constructed with keyword arguments (i.e. self.params is a dict). No-op for positional (tuple) params. """ if isinstance(self.params, dict): self.params.update(*args, **kwargs)
[docs] def indent(text: str | None, indents: int = 1) -> str: """ Add indentation to each line of text. Uses 4-space blocks per indent level, matching pandas' original behavior. Parameters ---------- text : str or None The text to indent. Returns "" if None or empty. indents : int, optional Number of 4-space indent levels to add. Default is 1. Returns ------- str Indented text, or "" if input was None/empty. """ if not text or not isinstance(text, str): return "" jointext = "".join(["\n"] + [" "] * indents) return jointext.join(text.split("\n"))