Last active
October 23, 2021 11:27
-
-
Save neutrinoceros/3f703d63761794b5e966330d51436b3c to your computer and use it in GitHub Desktop.
Python function decorators to progressively evolve function signatures over a deprecation cycle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import warnings | |
from functools import wraps | |
from types import FunctionType | |
from typing import Dict | |
def future_keyword_only(positions2names:Dict[int, str]): | |
"""Warn users when using a future keyword-only argument as positional. | |
Note that keyword-only arguments are available from Python 3.0 | |
See https://www.python.org/dev/peps/pep-3102/ | |
""" | |
def outer(func:FunctionType): | |
@wraps(func) | |
def inner(*args, **kwargs): | |
for no, name in sorted(positions2names.items()): | |
if len(args) <= no: | |
break | |
value = args[no] | |
msg = ( | |
f"using the {name!r} argument positionally (on position {no}) " | |
"is deprecated and will stop working in a future release. " | |
"Pass the argument as keyword to supress this warning, " | |
f"i.e., use {func.__name__}(..., {name}={value!r}, ...)" | |
) | |
warnings.warn(msg, FutureWarning, stacklevel=2) | |
return func(*args, **kwargs) | |
return inner | |
return outer | |
def future_positional_only(positions2names:Dict[int, str]): | |
"""Warn users when using a future positional-only argument as keyword. | |
Note that positional-only arguments are available from Python 3.8 | |
See https://www.python.org/dev/peps/pep-0570/ | |
""" | |
def outer(func:FunctionType): | |
@wraps(func) | |
def inner(*args, **kwargs): | |
for no, name in sorted(positions2names.items()): | |
if name not in kwargs: | |
continue | |
value = kwargs[name] | |
msg = ( | |
f"using the {name!r} argument as keyword (on position {no}) " | |
"is deprecated and will stop working in a future release. " | |
"Pass the argument as positional to supress this warning, " | |
f"i.e., use {func.__name__}({value!r}, ...)" | |
) | |
warnings.warn(msg, FutureWarning, stacklevel=2) | |
return func(*args, **kwargs) | |
return inner | |
return outer | |
if __name__ == "__main__": | |
# a minimal demo | |
@future_keyword_only({1: "counts"}) | |
@future_positional_only({0: "name"}) | |
def hello(name:str, counts:int = 1): | |
return " ".join([f"Hello {name} !"] * counts) | |
hello("Guido", 1) # warning: counts will become keyword-only | |
hello(name="Matt", counts=3) # warning: name will become positional-only | |
hello("darkness", counts=2) # all clear ! | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment