From 20b2f34cc6f6425ac06ee2f302c0e50d0cd68510 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 8 Mar 2018 23:42:16 -0800 Subject: [PATCH] Proof of concept for deprecation of global non-callables. Triggered by the recent discussion on deprecating matplotlib.verbose. This ensures that a deprecation warning is triggered just by trying to access matplotlib.verbose (or importing it). The implementation idea is based on njsmith's metamodule, but without the hacks needed to support Py<3.5. Py3.7+ will provide instance-`__getattr__` on all classes by default, making the `_DeprecatorModuleType` patching unnecessary (but the warning emitter would still need to be implemented, so there is actually fairly limited improvement from that). --- lib/matplotlib/__init__.py | 6 +++++ lib/matplotlib/cbook/deprecation.py | 37 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index cb78d8d1eb06..8706e70da5f0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -143,6 +143,7 @@ from . import cbook from matplotlib.cbook import ( mplDeprecation, dedent, get_label, sanitize_sequence) +from matplotlib.cbook.deprecation import _deprecated_global from matplotlib.rcsetup import defaultParams, validate_backend, cycler import numpy @@ -391,6 +392,11 @@ def ge(self, level): return self.vald[self.level] >= self.vald[level] +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + _deprecated_global("verbose", Verbose(), "2.2") + + def _wrap(fmt, func, level='DEBUG', always=True): """ return a callable function that wraps func and reports its diff --git a/lib/matplotlib/cbook/deprecation.py b/lib/matplotlib/cbook/deprecation.py index 9c8c83225ba7..5ce1e67b7e20 100644 --- a/lib/matplotlib/cbook/deprecation.py +++ b/lib/matplotlib/cbook/deprecation.py @@ -1,5 +1,7 @@ import functools +import sys import textwrap +from types import ModuleType import warnings @@ -221,3 +223,38 @@ def wrapper(*args, **kwargs): return finalize(wrapper, new_doc) return deprecate + + +class _DeprecatorModuleType(ModuleType): + def __getattr__(self, name): + try: + val, message = self._deprecated_dict[name] + warnings.warn(message, MatplotlibDeprecationWarning, stacklevel=2) + return val + except KeyError: + raise AttributeError( + "Module {!r} has no attibute {!r}" + .format(self.__name__, name)) from None + + +def _deprecated_global( + name, obj, since, *, + message="", alternative="", pending=False, obj_type="object", + addendum="", removal=""): + frame = sys._getframe(1) + if frame.f_locals is not frame.f_globals: + raise RuntimeError( + "Matplotlib internal error: cannot globally deprecate object from " + "non-global frame") + mod = sys.modules[frame.f_globals["__name__"]] + if type(mod) == ModuleType: + mod.__class__ = _DeprecatorModuleType + elif mod.__class__ != _DeprecatorModuleType: + warnings.warn("Matplotlib internal error: cannot deprecate global of " + "patched module. Assigning attribute normally.") + mod.__dict__[name] = obj + return + message = _generate_deprecation_message( + since=since, message=message, name=name, alternative=alternative, + pending=pending, obj_type=obj_type, removal=removal) + mod.__dict__.setdefault("_deprecated_dict", {})[name] = (obj, message)