tantale / deprecated

Python @deprecated decorator to deprecate old python classes, functions or methods.
MIT License
301 stars 32 forks source link

Deprecate a function parameter — Contribution Guide #8

Closed tantale closed 1 year ago

tantale commented 5 years ago

Introduction

This article concerns the addition of one (or more) decorator(s) to deprecate the use of a function parameter. For example:

@deprecated_param(version="0.2.3",
                  reason="you may consider using *styles* instead.",
                  deprecated_args='color background_color')
def paragraph(text, color=None, background_color=None, styles=None):
    styles = styles or {}
    if color:
        styles['color'] = color
    if background_color:
        styles['background-color'] = background_color
    html_styles = " ".join("{k}: {v};".format(k=k, v=v) for k, v in styles.items())
    html_text = xml.sax.saxutils.escape(text)
    return ('<p styles="{html_styles}">{html_text}</p>'
            .format(html_styles=html_styles, html_text=html_text))

Such a decorator could be coded as follows:

import functools

def deprecated_param(version, reason, deprecated_args):
    def decorate(func):
        @functools.wraps(func)
        def call(*args, **kwargs):
            # todo: check deprecated arguments here...
            return func(*args, **kwargs)

        return call

    return decorate

Advantages and disadvantages of the decorator

Using a decorator to deprecate the use of a function parameter is interesting and offers some advantages:

Disadvantages:

The use cases mentioned above fall into 3 categories:

  1. Deleted parameter,
  2. Added parameter,
  3. Modified parameter (default value, modified type or use).

The third case (modified parameter) is the most difficult to specify in general terms.

Examples of deprecations

(This section is incomplete) Before starting a complex implementation, it would be necessary to study how it is done, on one hand in the Standard Library, and on the other hand in the popular libraries of the Open Source world such as: pip, urllib3, boto3, six, requests, setuptools, futures, Flask, Django… At least two questions must be considered regarding deprecation:

Implementation

(This section is incomplete) A parameter deprecation decorator implementation must be flexible. It is necessary to be as flexible as possible and to use what exists for @deprecated.basic.deprecated and @deprecated.sphinx.deprecated. Especially, the use of Wrapt is a must-have. It is also necessary to define test cases that correspond to the most common scenarios. It is also likely that some atypical situations are clearly not implemented. For now, I would like to continue supporting Python 2.7, as it is still used by 25% of users (according to the 2018 State of Developer Ecosystem survey).

kba commented 4 years ago

This looks pretty elegant: https://stackoverflow.com/questions/49802412/how-to-implement-deprecation-in-python-with-argument-alias:

import functools
import warnings

def deprecated_alias(**aliases):
    def deco(f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            rename_kwargs(f.__name__, kwargs, aliases)
            return f(*args, **kwargs)
        return wrapper
    return deco

def rename_kwargs(func_name, kwargs, aliases):
    for alias, new in aliases.items():
        if alias in kwargs:
            if new in kwargs:
                raise TypeError('{} received both {} and {}'.format(
                    func_name, alias, new))
            warnings.warn('{} is deprecated; use {}'.format(alias, new),
                          DeprecationWarning)
            kwargs[new] = kwargs.pop(alias)

class MyClass(object):
    @deprecated_alias(object_id='id_object')
    def __init__(self, id_object):
        self.id = id_object
tantale commented 4 years ago

It’s a good starting point. We also need to add a warning message.

stefano-xy commented 2 years ago

Since it looks like it's taking some time to discuss and implement this improvement, or find someone that could, can I suggest to introduce a function to emit properly a deprecation warning, to be used inside the code for complex situations that can only be represented using runtime checks by the programmer? Those runtime checks are probably already there.

Something like:

deprecation_warning(version=..., reason=...)

That can be used in any place needed?

mjhajharia commented 2 years ago

hi @tantale, consider checking this PR https://github.com/tantale/deprecated/pull/51, if you think I can contribute something similar!