Beispiel #1
0
        def make_msg(style=None):
            if replaced_by is not None:
                try:
                    doc_url = ' (see: {})'.format(get_doc_url(replaced_by))
                except Exception:
                    doc_url = ''

                replacement_msg = ', use {} instead{}'.format(
                    getname(replaced_by, style=style),
                    doc_url,
                )
            else:
                replacement_msg = ''

            if removed_in:
                removal_msg = ' and will be removed in version {}'.format(
                    format_version(removed_in))
            else:
                removal_msg = ''

            return '{name} is deprecated{remove}{replace}{msg}'.format(
                name=getname(obj, style=style, abbrev=True),
                replace=replacement_msg,
                remove=removal_msg,
                msg=': ' + msg if msg else '',
            )
Beispiel #2
0
    def make_msg(deprecated_obj,
                 parameter=None,
                 style=None,
                 show_doc_url=True):
        if replaced_by is not None:
            doc_url = ''
            if show_doc_url:
                with contextlib.suppress(Exception):
                    doc_url = ' (see: {})'.format(get_doc_url(replaced_by))

            replacement_msg = ', use {} instead{}'.format(
                getname(replaced_by, style=style),
                doc_url,
            )
        else:
            replacement_msg = ''

        if removed_in:
            removal_msg = ' and will be removed in version {}'.format(
                format_version(removed_in))
        else:
            removal_msg = ''

        name = getname(deprecated_obj, style=style, abbrev=True)
        if parameter:
            if style == 'rst':
                parameter = '``{}``'.format(parameter)
            name = '{} parameter of {}'.format(parameter, name)

        return '{name} is deprecated{remove}{replace}{msg}'.format(
            name=name,
            replace=replacement_msg,
            remove=removal_msg,
            msg=': ' + msg if msg else '',
        )
Beispiel #3
0
    def make_entry(entry):
        msg = entry.get('msg') or ''
        removed_in = entry.get('removed_in')
        if removed_in is None:
            removed_in = ''
        else:
            removed_in = '*Removed in: {}*\n\n'.format(format_version(removed_in))

        name = get_sphinx_name(entry['obj'], style='rst')
        replaced_by = entry.get('replaced_by')
        if replaced_by is None:
            replaced_by = ''
        else:
            replaced_by = '*Replaced by:* {}\n\n'.format(get_sphinx_name(replaced_by, style='rst'))

        return "* - {name}{msg}{replaced_by}{removed_in}".format(
            name=indent(name + '\n\n'),
            msg=indent(msg + '\n\n' if msg else ''),
            replaced_by=indent(replaced_by),
            removed_in=indent(removed_in),
        )
Beispiel #4
0
    def make_table(entries, removed_in):
        entries = '\n'.join(
            make_entry(entry)
            for entry in sorted(entries, key=itemgetter('name'))
        )
        if removed_in:
            if removed_in > lisa.version.version_tuple:
                remove = 'to be removed'
            else:
                remove = 'removed'
            removed_in = ' {} in {}'.format(remove, format_version(removed_in))
        else:
            removed_in = ''

        table = ".. list-table:: Deprecated names{removed_in}\n    :align: left{entries}".format(
            entries=indent('\n\n' + entries),
            removed_in=removed_in,
        )
        header = 'Deprecated names{}'.format(removed_in)
        header += '\n' + '+' * len(header)

        return header + '\n\n' + table
Beispiel #5
0
    def decorator(obj):
        obj_name = getname(obj)

        if removed_in and current_version >= removed_in:
            raise DeprecationWarning(
                '{name} was marked as being removed in version {removed_in} but is still present in current version {version}'
                .format(
                    name=obj_name,
                    removed_in=format_version(removed_in),
                    version=format_version(current_version),
                ))

        def make_msg(style=None):
            if replaced_by is not None:
                try:
                    doc_url = ' (see: {})'.format(get_doc_url(replaced_by))
                except Exception:
                    doc_url = ''

                replacement_msg = ', use {} instead{}'.format(
                    getname(replaced_by, style=style),
                    doc_url,
                )
            else:
                replacement_msg = ''

            if removed_in:
                removal_msg = ' and will be removed in version {}'.format(
                    format_version(removed_in))
            else:
                removal_msg = ''

            return '{name} is deprecated{remove}{replace}{msg}'.format(
                name=getname(obj, style=style, abbrev=True),
                replace=replacement_msg,
                remove=removal_msg,
                msg=': ' + msg if msg else '',
            )

        # stacklevel != 1 breaks the filtering for warnings emitted by APIs
        # called from external modules, like __init_subclass__ that is called
        # from other modules like abc.py
        def wrap_func(func, stacklevel=1):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                warnings.warn(make_msg(),
                              DeprecationWarning,
                              stacklevel=stacklevel)
                return func(*args, **kwargs)

            return wrapper

        # Make sure we don't accidentally override an existing entry
        assert obj_name not in DEPRECATED_MAP
        DEPRECATED_MAP[obj_name] = {
            'obj': obj,
            'replaced_by': replaced_by,
            'msg': msg,
            'removed_in': removed_in,
            'deprecated_in': deprecated_in,
        }

        # For classes, wrap __new__ and update docstring
        if isinstance(obj, type):
            # Warn on instance creation
            obj.__new__ = wrap_func(obj.__new__)
            # Will show the warning when the class is subclassed
            # in Python >= 3.6
            obj.__init_subclass__ = wrap_func(obj.__init_subclass__)
            return_obj = obj
            update_doc_of = obj

        elif isinstance(obj, property):
            # Since we cannot update the property itself, replace it with a new
            # one that uses a wrapped getter. This should be safe as properties
            # seems to be immutable, so there is no risk of somebody
            # monkey-patching the object and us throwing away the extra
            # attributes.
            # Note that this will only wrap accessors that are visible at the
            # time the decorator is applied.
            obj = property(
                fget=wrap_func(obj.fget, stacklevel=2),
                fset=wrap_func(obj.fset, stacklevel=2),
                fdel=wrap_func(obj.fdel, stacklevel=2),
            )
            return_obj = obj
            update_doc_of = obj

        elif isinstance(obj, (staticmethod, classmethod)):
            func = obj.__func__
            stacklevel = get_meth_stacklevel(func.__name__)
            func = wrap_func(func, stacklevel=stacklevel)
            # Build a new staticmethod/classmethod with the updated function
            return_obj = obj.__class__(func)
            # Updating the __doc__ of the staticmethod/classmethod itself will
            # have no effect, so update the doc of the underlying function
            update_doc_of = func

        # For other callables, emit the warning when called
        else:
            stacklevel = get_meth_stacklevel(obj.__name__)
            return_obj = wrap_func(obj, stacklevel=stacklevel)
            update_doc_of = return_obj

        doc = inspect.getdoc(update_doc_of) or ''
        update_doc_of.__doc__ = doc + '\n\n' + textwrap.dedent("""
        .. attention::

            .. deprecated:: {deprecated_in}

            {msg}
        """.format(
            deprecated_in=deprecated_in if deprecated_in else '<unknown>',
            msg=make_msg(style='rst'),
        )).strip()

        return return_obj
Beispiel #6
0
    def decorator(obj):
        obj_name = getname(obj)

        if removed_in and current_version >= removed_in:
            raise DeprecationWarning(
                '{name} was marked as being removed in version {removed_in} but is still present in current version {version}'
                .format(
                    name=obj_name,
                    removed_in=format_version(removed_in),
                    version=format_version(current_version),
                ))

        # stacklevel != 1 breaks the filtering for warnings emitted by APIs
        # called from external modules, like __init_subclass__ that is called
        # from other modules like abc.py
        if parameter:

            def wrap_func(func, stacklevel=1):
                sig = inspect.signature(func)

                @functools.wraps(func)
                def wrapper(*args, **kwargs):
                    kwargs = sig.bind(*args, **kwargs).arguments
                    if parameter in kwargs:
                        warnings.warn(make_msg(obj, parameter),
                                      DeprecationWarning,
                                      stacklevel=stacklevel)
                    return func(**kwargs)

                return wrapper
        else:

            def wrap_func(func, stacklevel=1):
                @functools.wraps(func)
                def wrapper(*args, **kwargs):
                    warnings.warn(make_msg(obj),
                                  DeprecationWarning,
                                  stacklevel=stacklevel)
                    return func(*args, **kwargs)

                return wrapper

            # Make sure we don't accidentally override an existing entry
            assert obj_name not in DEPRECATED_MAP
            DEPRECATED_MAP[obj_name] = {
                'obj': obj,
                'replaced_by': replaced_by,
                'msg': msg,
                'removed_in': removed_in,
                'deprecated_in': deprecated_in,
            }

        # For classes, wrap __new__ and update docstring
        if isinstance(obj, type):
            # Warn on instance creation
            obj.__init__ = wrap_func(obj.__init__)
            # Will show the warning when the class is subclassed
            # in Python >= 3.6 . Earlier versions of Python don't have
            # object.__init_subclass__
            if hasattr(obj, '__init_subclass__'):
                obj.__init_subclass__ = wrap_func(obj.__init_subclass__)
            return_obj = obj
            update_doc_of = obj

        elif isinstance(obj, property):
            # Since we cannot update the property itself, replace it with a new
            # one that uses a wrapped getter. This should be safe as properties
            # seems to be immutable, so there is no risk of somebody
            # monkey-patching the object and us throwing away the extra
            # attributes.
            # Note that this will only wrap accessors that are visible at the
            # time the decorator is applied.
            obj = property(
                fget=wrap_func(obj.fget, stacklevel=2),
                fset=wrap_func(obj.fset, stacklevel=2),
                fdel=wrap_func(obj.fdel, stacklevel=2),
            )
            return_obj = obj
            update_doc_of = obj

        elif isinstance(obj, (staticmethod, classmethod)):
            func = obj.__func__
            stacklevel = get_meth_stacklevel(func.__name__)
            func = wrap_func(func, stacklevel=stacklevel)
            # Build a new staticmethod/classmethod with the updated function
            return_obj = obj.__class__(func)
            # Updating the __doc__ of the staticmethod/classmethod itself will
            # have no effect, so update the doc of the underlying function
            update_doc_of = func

        # For other callables, emit the warning when called
        else:
            stacklevel = get_meth_stacklevel(obj.__name__)
            return_obj = wrap_func(obj, stacklevel=stacklevel)
            update_doc_of = return_obj

        extra_doc = textwrap.dedent("""
        .. attention::

            .. deprecated:: {deprecated_in}

            {msg}
        """.format(
            deprecated_in=deprecated_in if deprecated_in else '<unknown>',
            # The documentation already creates references to the replacement,
            # so we can avoid downloading the inventory for nothing.
            msg=make_msg(obj, parameter, style='rst', show_doc_url=False),
        )).strip()
        doc = inspect.getdoc(update_doc_of) or ''

        # Update the description of the parameter in the right spot in the docstring
        if parameter:

            # Split into chunks of restructured text at boundaries such as
            # ":param foo: ..." or ":type foo: ..."
            blocks = []
            curr_block = []
            for line in doc.splitlines(keepends=True):
                if re.match(r'\s*:', line):
                    curr_block = []
                    blocks.append(curr_block)

                curr_block.append(line)

            # Add the extra bits in the right block and join lines of the block
            def update_block(block):
                if re.match(':param\s+{}'.format(re.escape(parameter)),
                            block[0]):
                    if len(block) > 1:
                        indentation = re.match(r'^(\s*)', block[-1]).group(0)
                    else:
                        indentation = ' ' * 4
                    block.append('\n' +
                                 textwrap.indent(extra_doc, indentation) +
                                 '\n')
                return ''.join(block)

            doc = ''.join(map(update_block, blocks))

        # Otherwise just append the extra bits at the end of the docstring
        else:
            doc += '\n\n' + extra_doc

        update_doc_of.__doc__ = doc
        return return_obj