1# util/deprecations.py 
    2# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors 
    3# <see AUTHORS file> 
    4# 
    5# This module is part of SQLAlchemy and is released under 
    6# the MIT License: http://www.opensource.org/licenses/mit-license.php 
    7 
    8"""Helpers related to deprecation of functions, methods, classes, other 
    9functionality.""" 
    10 
    11import re 
    12import warnings 
    13 
    14from . import compat 
    15from .langhelpers import _hash_limit_string 
    16from .langhelpers import decorator 
    17from .langhelpers import inject_docstring_text 
    18from .langhelpers import inject_param_text 
    19from .. import exc 
    20 
    21 
    22def warn_deprecated(msg, stacklevel=3): 
    23    warnings.warn(msg, exc.SADeprecationWarning, stacklevel=stacklevel) 
    24 
    25 
    26def warn_deprecated_limited(msg, args, stacklevel=3): 
    27    """Issue a deprecation warning with a parameterized string, 
    28    limiting the number of registrations. 
    29 
    30    """ 
    31    if args: 
    32        msg = _hash_limit_string(msg, 10, args) 
    33    warnings.warn(msg, exc.SADeprecationWarning, stacklevel) 
    34 
    35 
    36def warn_pending_deprecation(msg, stacklevel=3): 
    37    warnings.warn(msg, exc.SAPendingDeprecationWarning, stacklevel=stacklevel) 
    38 
    39 
    40def deprecated_cls(version, message, constructor="__init__"): 
    41    header = ".. deprecated:: %s %s" % (version, (message or "")) 
    42 
    43    def decorate(cls): 
    44        return _decorate_cls_with_warning( 
    45            cls, 
    46            constructor, 
    47            exc.SADeprecationWarning, 
    48            message % dict(func=constructor), 
    49            header, 
    50        ) 
    51 
    52    return decorate 
    53 
    54 
    55def deprecated(version, message=None, add_deprecation_to_docstring=True): 
    56    """Decorates a function and issues a deprecation warning on use. 
    57 
    58    :param version: 
    59      Issue version in the warning. 
    60 
    61    :param message: 
    62      If provided, issue message in the warning.  A sensible default 
    63      is used if not provided. 
    64 
    65    :param add_deprecation_to_docstring: 
    66      Default True.  If False, the wrapped function's __doc__ is left 
    67      as-is.  If True, the 'message' is prepended to the docs if 
    68      provided, or sensible default if message is omitted. 
    69 
    70    """ 
    71 
    72    if add_deprecation_to_docstring: 
    73        header = ".. deprecated:: %s %s" % (version, (message or "")) 
    74    else: 
    75        header = None 
    76 
    77    if message is None: 
    78        message = "Call to deprecated function %(func)s" 
    79 
    80    def decorate(fn): 
    81        return _decorate_with_warning( 
    82            fn, 
    83            exc.SADeprecationWarning, 
    84            message % dict(func=fn.__name__), 
    85            header, 
    86        ) 
    87 
    88    return decorate 
    89 
    90 
    91def deprecated_params(**specs): 
    92    """Decorates a function to warn on use of certain parameters. 
    93 
    94    e.g. :: 
    95 
    96        @deprecated_params( 
    97            weak_identity_map=( 
    98                "0.7", 
    99                "the :paramref:`.Session.weak_identity_map parameter " 
    100                "is deprecated." 
    101            ) 
    102 
    103        ) 
    104 
    105    """ 
    106 
    107    messages = {} 
    108    for param, (version, message) in specs.items(): 
    109        messages[param] = _sanitize_restructured_text(message) 
    110 
    111    def decorate(fn): 
    112        spec = compat.inspect_getfullargspec(fn) 
    113        if spec.defaults is not None: 
    114            defaults = dict( 
    115                zip( 
    116                    spec.args[(len(spec.args) - len(spec.defaults)) :], 
    117                    spec.defaults, 
    118                ) 
    119            ) 
    120            check_defaults = set(defaults).intersection(messages) 
    121            check_kw = set(messages).difference(defaults) 
    122        else: 
    123            check_defaults = () 
    124            check_kw = set(messages) 
    125 
    126        @decorator 
    127        def warned(fn, *args, **kwargs): 
    128            for m in check_defaults: 
    129                if kwargs[m] != defaults[m]: 
    130                    warnings.warn( 
    131                        messages[m], exc.SADeprecationWarning, stacklevel=3 
    132                    ) 
    133            for m in check_kw: 
    134                if m in kwargs: 
    135                    warnings.warn( 
    136                        messages[m], exc.SADeprecationWarning, stacklevel=3 
    137                    ) 
    138 
    139            return fn(*args, **kwargs) 
    140 
    141        doc = fn.__doc__ is not None and fn.__doc__ or "" 
    142        if doc: 
    143            doc = inject_param_text( 
    144                doc, 
    145                { 
    146                    param: ".. deprecated:: %s %s" % (version, (message or "")) 
    147                    for param, (version, message) in specs.items() 
    148                }, 
    149            ) 
    150        decorated = warned(fn) 
    151        decorated.__doc__ = doc 
    152        return decorated 
    153 
    154    return decorate 
    155 
    156 
    157def pending_deprecation( 
    158    version, message=None, add_deprecation_to_docstring=True 
    159): 
    160    """Decorates a function and issues a pending deprecation warning on use. 
    161 
    162    :param version: 
    163      An approximate future version at which point the pending deprecation 
    164      will become deprecated.  Not used in messaging. 
    165 
    166    :param message: 
    167      If provided, issue message in the warning.  A sensible default 
    168      is used if not provided. 
    169 
    170    :param add_deprecation_to_docstring: 
    171      Default True.  If False, the wrapped function's __doc__ is left 
    172      as-is.  If True, the 'message' is prepended to the docs if 
    173      provided, or sensible default if message is omitted. 
    174    """ 
    175 
    176    if add_deprecation_to_docstring: 
    177        header = ".. deprecated:: %s (pending) %s" % (version, (message or "")) 
    178    else: 
    179        header = None 
    180 
    181    if message is None: 
    182        message = "Call to deprecated function %(func)s" 
    183 
    184    def decorate(fn): 
    185        return _decorate_with_warning( 
    186            fn, 
    187            exc.SAPendingDeprecationWarning, 
    188            message % dict(func=fn.__name__), 
    189            header, 
    190        ) 
    191 
    192    return decorate 
    193 
    194 
    195def deprecated_option_value(parameter_value, default_value, warning_text): 
    196    if parameter_value is None: 
    197        return default_value 
    198    else: 
    199        warn_deprecated(warning_text) 
    200        return parameter_value 
    201 
    202 
    203def _sanitize_restructured_text(text): 
    204    def repl(m): 
    205        type_, name = m.group(1, 2) 
    206        if type_ in ("func", "meth"): 
    207            name += "()" 
    208        return name 
    209 
    210    return re.sub(r"\:(\w+)\:`~?(?:_\w+)?\.?(.+?)`", repl, text) 
    211 
    212 
    213def _decorate_cls_with_warning( 
    214    cls, constructor, wtype, message, docstring_header=None 
    215): 
    216    doc = cls.__doc__ is not None and cls.__doc__ or "" 
    217    if docstring_header is not None: 
    218        docstring_header %= dict(func=constructor) 
    219 
    220        doc = inject_docstring_text(doc, docstring_header, 1) 
    221 
    222        if type(cls) is type: 
    223            clsdict = dict(cls.__dict__) 
    224            clsdict["__doc__"] = doc 
    225            cls = type(cls.__name__, cls.__bases__, clsdict) 
    226            constructor_fn = clsdict[constructor] 
    227        else: 
    228            cls.__doc__ = doc 
    229            constructor_fn = getattr(cls, constructor) 
    230 
    231    setattr( 
    232        cls, 
    233        constructor, 
    234        _decorate_with_warning(constructor_fn, wtype, message, None), 
    235    ) 
    236 
    237    return cls 
    238 
    239 
    240def _decorate_with_warning(func, wtype, message, docstring_header=None): 
    241    """Wrap a function with a warnings.warn and augmented docstring.""" 
    242 
    243    message = _sanitize_restructured_text(message) 
    244 
    245    @decorator 
    246    def warned(fn, *args, **kwargs): 
    247        warnings.warn(message, wtype, stacklevel=3) 
    248        return fn(*args, **kwargs) 
    249 
    250    doc = func.__doc__ is not None and func.__doc__ or "" 
    251    if docstring_header is not None: 
    252        docstring_header %= dict(func=func.__name__) 
    253 
    254        doc = inject_docstring_text(doc, docstring_header, 1) 
    255 
    256    decorated = warned(func) 
    257    decorated.__doc__ = doc 
    258    decorated._sa_warn = lambda: warnings.warn(message, wtype, stacklevel=3) 
    259    return decorated