zmc
2023-12-22 9fdbf60165db0400c2e8e6be2dc6e88138ac719a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import sys
from functools import wraps
from types import ModuleType
import warnings
 
import attr
 
 
# We want our warnings to be visible by default (at least for now), but we
# also want it to be possible to override that using the -W switch. AFAICT
# this means we cannot inherit from DeprecationWarning, because the only way
# to make it visible by default then would be to add our own filter at import
# time, but that would override -W switches...
class TrioDeprecationWarning(FutureWarning):
    """Warning emitted if you use deprecated Trio functionality.
 
    As a young project, Trio is currently quite aggressive about deprecating
    and/or removing functionality that we realize was a bad idea. If you use
    Trio, you should subscribe to `issue #1
    <https://github.com/python-trio/trio/issues/1>`__ to get information about
    upcoming deprecations and other backwards compatibility breaking changes.
 
    Despite the name, this class currently inherits from
    :class:`FutureWarning`, not :class:`DeprecationWarning`, because while
    we're in young-and-aggressive mode we want these warnings to be visible by
    default. You can hide them by installing a filter or with the ``-W``
    switch: see the :mod:`warnings` documentation for details.
 
    """
 
 
def _url_for_issue(issue):
    return "https://github.com/python-trio/trio/issues/{}".format(issue)
 
 
def _stringify(thing):
    if hasattr(thing, "__module__") and hasattr(thing, "__qualname__"):
        return "{}.{}".format(thing.__module__, thing.__qualname__)
    return str(thing)
 
 
def warn_deprecated(thing, version, *, issue, instead, stacklevel=2):
    stacklevel += 1
    msg = "{} is deprecated since Trio {}".format(_stringify(thing), version)
    if instead is None:
        msg += " with no replacement"
    else:
        msg += "; use {} instead".format(_stringify(instead))
    if issue is not None:
        msg += " ({})".format(_url_for_issue(issue))
    warnings.warn(TrioDeprecationWarning(msg), stacklevel=stacklevel)
 
 
# @deprecated("0.2.0", issue=..., instead=...)
# def ...
def deprecated(version, *, thing=None, issue, instead):
    def do_wrap(fn):
        nonlocal thing
 
        @wraps(fn)
        def wrapper(*args, **kwargs):
            warn_deprecated(thing, version, instead=instead, issue=issue)
            return fn(*args, **kwargs)
 
        # If our __module__ or __qualname__ get modified, we want to pick up
        # on that, so we read them off the wrapper object instead of the (now
        # hidden) fn object
        if thing is None:
            thing = wrapper
 
        if wrapper.__doc__ is not None:
            doc = wrapper.__doc__
            doc = doc.rstrip()
            doc += "\n\n"
            doc += ".. deprecated:: {}\n".format(version)
            if instead is not None:
                doc += "   Use {} instead.\n".format(_stringify(instead))
            if issue is not None:
                doc += "   For details, see `issue #{} <{}>`__.\n".format(
                    issue, _url_for_issue(issue)
                )
            doc += "\n"
            wrapper.__doc__ = doc
 
        return wrapper
 
    return do_wrap
 
 
def deprecated_alias(old_qualname, new_fn, version, *, issue):
    @deprecated(version, issue=issue, instead=new_fn)
    @wraps(new_fn, assigned=("__module__", "__annotations__"))
    def wrapper(*args, **kwargs):
        "Deprecated alias."
        return new_fn(*args, **kwargs)
 
    wrapper.__qualname__ = old_qualname
    wrapper.__name__ = old_qualname.rpartition(".")[-1]
    return wrapper
 
 
@attr.s(frozen=True)
class DeprecatedAttribute:
    _not_set = object()
 
    value = attr.ib()
    version = attr.ib()
    issue = attr.ib()
    instead = attr.ib(default=_not_set)
 
 
class _ModuleWithDeprecations(ModuleType):
    def __getattr__(self, name):
        if name in self.__deprecated_attributes__:
            info = self.__deprecated_attributes__[name]
            instead = info.instead
            if instead is DeprecatedAttribute._not_set:
                instead = info.value
            thing = "{}.{}".format(self.__name__, name)
            warn_deprecated(thing, info.version, issue=info.issue, instead=instead)
            return info.value
 
        msg = "module '{}' has no attribute '{}'"
        raise AttributeError(msg.format(self.__name__, name))
 
 
def enable_attribute_deprecations(module_name):
    module = sys.modules[module_name]
    module.__class__ = _ModuleWithDeprecations
    # Make sure that this is always defined so that
    # _ModuleWithDeprecations.__getattr__ can access it without jumping
    # through hoops or risking infinite recursion.
    module.__deprecated_attributes__ = {}