You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
2 years ago
|
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__ = {}
|