neutron/neutron/common/_deprecate.py

170 lines
6.4 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Provide a deprecation method for globals.
NOTE: This module may be a candidate for adoption by debtcollector.
"""
import inspect
import sys
import debtcollector
from neutron._i18n import _
class _MovedGlobals(object):
"""Override a module to deprecate moved globals.
This class is used when globals (attributes of a module) need to be
marked as deprecated. It can be used in either or both of two ways:
1. By specifying a default new module, all accesses to a global in
the source module will emit a warning if the global does not exist
in the source module and it does exist in the new module. This way
is intended to be used when many globals are moved from one module
to another.
2. By explicitly deprecating individual globals with the _moved_global()
function, see below.
This class must be called from the last line in a module, as follows:
``_deprecate._MovedGlobals(default_new_module)``
or
``_deprecate._MovedGlobals()``
Args:
:param default_new_module: The default new location for moved globals
:type default_new_module: module or None
Attributes:
:ivar _mg__my_globals: The current vars() of the source module
:type _mg__my_globals: dict
:ivar _mg__default_new_mod: The default location for moved globals
:type _mg__default_new_mod: module or None
:ivar _mg__old_ref: The original reference to the source module
:type _mg__old_ref: module
:cvar _mg__moves: Moves (and renames) not involving default_new_module
:type _mg__moves: dict
NOTE: An instance of _MovedGlobals overrides the module it is called
from, so instance and class variables appear in the module namespace.
To prevent collisions with existing globals, the instance and class
variable names here are prefixed with ``_mg__``.
"""
# Here we store individual moves and renames. This is a dict where
# key = (old_module, old_name)
# value = (new_module, new_name)
# If new_module is the same as old_module then it is a rename in place.
_mg__moves = {}
def __init__(self, default_new_module=None):
# To avoid infinite recursion at inspect.getsourcelines() below we
# must initialize self._mg__my_globals early here.
self._mg__my_globals = {}
self._mg__default_new_mod = default_new_module
caller_frame = inspect.stack()[1][0]
caller_line = inspect.getframeinfo(caller_frame).lineno
source_module = inspect.getmodule(caller_frame)
src_mod_last_line = len(inspect.getsourcelines(source_module)[0])
if caller_line < src_mod_last_line:
raise SystemExit(_("_MovedGlobals() not called from last "
"line in %s") % source_module.__file__)
self._mg__my_globals = vars(source_module)
# When we return from here we override the sys.modules[] entry
# for the source module with this instance. We must keep a
# reference to the original module to prevent it from being
# garbage collected.
self._mg__old_ref = source_module
sys.modules[source_module.__name__] = self
def __getattr__(self, name):
value = self._mg__my_globals.get(name)
if not name.startswith("__") and not inspect.ismodule(value):
old_module = self._mg__old_ref
specified_move = self._mg__moves.get((old_module, name))
if specified_move:
new_module, new_name = specified_move
else:
new_module, new_name = self._mg__default_new_mod, name
if new_module and new_name in vars(new_module):
old_location = '%s.%s' % (old_module.__name__, name)
new_location = '%s.%s' % (new_module.__name__, new_name)
changed = 'renamed' if old_module == new_module else 'moved'
debtcollector.deprecate(
old_location,
message='%s to %s' % (changed, new_location),
stacklevel=4)
return vars(new_module)[new_name]
try:
return self._mg__my_globals[name]
except KeyError:
raise AttributeError(
_("'module' object has no attribute '%s'") % name)
def __setattr__(self, name, val):
if name.startswith('_mg__'):
return super(_MovedGlobals, self).__setattr__(name, val)
self._mg__my_globals[name] = val
def __delattr__(self, name):
if name not in self._mg__my_globals:
raise AttributeError(
_("'module' object has no attribute '%s'") % name)
self._mg__my_globals.pop(name)
def _moved_global(old_name, new_module=None, new_name=None):
"""Deprecate a single attribute in a module.
This function is used to move an attribute to a module that differs
from _mg__default_new_mod in _MovedGlobals. It also handles renames.
NOTE: This function has no effect if _MovedGlobals() is not called
at the end of the module containing the attribute.
[TODO(HenryG): Figure out a way of asserting on this.]
:param old_name: The name of the attribute that was moved/renamed.
:type old_name: str
:param new_module: The new module where the attribute is now.
:type new_module: module
:param new_name: The new name of the attribute.
:type new_name: str
"""
if not (new_module or new_name):
raise AssertionError(_("'new_module' and 'new_name' "
"must not be both None"))
if isinstance(new_module, _MovedGlobals):
# The new module has been shimmed, get the original
new_module = new_module._mg__old_ref
old_module = inspect.getmodule(inspect.stack()[1][0]) # caller's module
new_module = new_module or old_module
new_name = new_name or old_name
_MovedGlobals._mg__moves[(old_module, old_name)] = (new_module, new_name)