diff --git a/sklearn/_build_utils/deprecated_modules.py b/sklearn/_build_utils/deprecated_modules.py index ccb84fb0876fa..9ff7c7f224710 100644 --- a/sklearn/_build_utils/deprecated_modules.py +++ b/sklearn/_build_utils/deprecated_modules.py @@ -270,14 +270,21 @@ _FILE_CONTENT_TEMPLATE = """ # THIS FILE WAS AUTOMATICALLY GENERATED BY deprecated_modules.py - -from .{new_module_name} import * # noqa +import sys +from . import {new_module_name} +from {relative_dots}externals._pep562 import Pep562 from {relative_dots}utils.deprecation import _raise_dep_warning_if_not_pytest deprecated_path = '{deprecated_path}' correct_import_path = '{correct_import_path}' _raise_dep_warning_if_not_pytest(deprecated_path, correct_import_path) + +def __getattr__(name): + return getattr({new_module_name}, name) + +if not sys.version_info >= (3, 7): + Pep562(__name__) """ diff --git a/sklearn/externals/_pep562.py b/sklearn/externals/_pep562.py new file mode 100644 index 0000000000000..86d374960b49f --- /dev/null +++ b/sklearn/externals/_pep562.py @@ -0,0 +1,58 @@ +""" +Backport of PEP 562. + +https://pypi.org/search/?q=pep562 + +Licensed under MIT +Copyright (c) 2018 Isaac Muse + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" +from __future__ import unicode_literals +import sys + +__all__ = ('Pep562',) + + +class Pep562(object): + """ + Backport of PEP 562 . + + Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`. + The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed. + """ + + def __init__(self, name): + """Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7.""" + + self._module = sys.modules[name] + self._get_attr = getattr(self._module, '__getattr__', None) + self._get_dir = getattr(self._module, '__dir__', None) + sys.modules[name] = self + + def __dir__(self): + """Return the overridden `dir` if one was provided, else apply `dir` to the module.""" + + return self._get_dir() if self._get_dir else dir(self._module) + + def __getattr__(self, name): + """Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present.""" + + try: + return getattr(self._module, name) + except AttributeError: + if self._get_attr: + return self._get_attr(name) + raise diff --git a/sklearn/tests/test_docstring_parameters.py b/sklearn/tests/test_docstring_parameters.py index f7b168f66feb4..31c7d268737b9 100644 --- a/sklearn/tests/test_docstring_parameters.py +++ b/sklearn/tests/test_docstring_parameters.py @@ -16,6 +16,7 @@ from sklearn.utils._testing import _get_func_name from sklearn.utils._testing import ignore_warnings from sklearn.utils.deprecation import _is_deprecated +from sklearn.externals._pep562 import Pep562 import pytest @@ -145,6 +146,13 @@ def test_tabs(): # because we don't import mod = importlib.import_module(modname) + + # TODO: Remove when minimum python version is 3.7 + # unwrap to get module because Pep562 backport wraps the original + # module + if isinstance(mod, Pep562): + mod = mod._module + try: source = inspect.getsource(mod) except IOError: # user probably should have run "make clean"