Commit b37a9033 authored by Jason Madden's avatar Jason Madden

Drop the deprecated ability to use absolute paths when giving an importable setting.

parent 55f5b4c4
...@@ -31,6 +31,11 @@ ...@@ -31,6 +31,11 @@
- Improve safety of handling exceptions during interpreter shutdown. - Improve safety of handling exceptions during interpreter shutdown.
See :issue:`1295` reported by BobDenar1212. See :issue:`1295` reported by BobDenar1212.
- Remove the deprecated ability to specify ``GEVENT_RESOLVER`` and
other importable settings as a ``path/to/a/package.module.item``.
This had race conditions and didn't work with complicated resolver
implementations. Place the required package or module on `sys.path`
first.
1.3.7 (2018-10-12) 1.3.7 (2018-10-12)
================== ==================
......
...@@ -12,7 +12,6 @@ from __future__ import print_function, absolute_import, division ...@@ -12,7 +12,6 @@ from __future__ import print_function, absolute_import, division
import importlib import importlib
import os import os
import sys
import textwrap import textwrap
from gevent._compat import string_types from gevent._compat import string_types
...@@ -210,78 +209,60 @@ class Config(object): ...@@ -210,78 +209,60 @@ class Config(object):
class ImportableSetting(object): class ImportableSetting(object):
def _import(self, path, _NONE=object): def _import_one_of(self, candidates):
# pylint:disable=too-many-branches assert isinstance(candidates, list)
if isinstance(path, list): if not candidates:
if not path: raise ImportError('Cannot import from empty list')
raise ImportError('Cannot import from empty list: %r' % (path, ))
for item in path[:-1]: for item in candidates[:-1]:
try: try:
return self._import(item) return self._import_one(item)
except ImportError: except ImportError:
pass pass
return self._import(path[-1]) return self._import_one(candidates[-1])
def _import_one(self, path, _MISSING=object()):
if not isinstance(path, string_types): if not isinstance(path, string_types):
return path return path
if '.' not in path: if '.' not in path or '/' in path:
raise ImportError("Cannot import %r. " raise ImportError("Cannot import %r. "
"Required format: [path/][package.]module.class. " "Required format: [package.]module.class. "
"Or choose from %r" "Or choose from %r"
% (path, list(self.shortname_map))) % (path, list(self.shortname_map)))
if '/' in path:
# This is dangerous, subject to race conditions, and
# may not work properly for things like namespace packages
import warnings
warnings.warn("Absolute paths are deprecated and will be removed in 1.4."
"Please put the package on sys.path first",
DeprecationWarning)
package_path, path = path.rsplit('/', 1)
sys.path = [package_path] + sys.path
else:
package_path = None
try:
module, item = path.rsplit('.', 1) module, item = path.rsplit('.', 1)
module = importlib.import_module(module) module = importlib.import_module(module)
x = getattr(module, item, _NONE) x = getattr(module, item, _MISSING)
if x is _NONE: if x is _MISSING:
raise ImportError('Cannot import %r from %r' % (item, module)) raise ImportError('Cannot import %r from %r' % (item, module))
return x return x
finally:
if package_path:
try:
sys.path.remove(package_path)
except ValueError: # pragma: no cover
pass
shortname_map = {} shortname_map = {}
def validate(self, value): def validate(self, value):
if isinstance(value, type): if isinstance(value, type):
return value return value
return self._import([self.shortname_map.get(x, x) for x in value]) return self._import_one_of([self.shortname_map.get(x, x) for x in value])
def get_options(self): def get_options(self):
result = {} result = {}
for name, val in self.shortname_map.items(): for name, val in self.shortname_map.items():
try: try:
result[name] = self._import(val) result[name] = self._import_one(val)
except ImportError as e: except ImportError as e:
import traceback
traceback.print_exc()
result[name] = e result[name] = e
return result return result
class BoolSettingMixin(object): class BoolSettingMixin(object):
validate = staticmethod(validate_bool) validate = staticmethod(validate_bool)
# Don't do string-to-list conversion. # Don't do string-to-list conversion.
_convert = staticmethod(convert_str_value_as_is) _convert = staticmethod(convert_str_value_as_is)
class IntSettingMixin(object): class IntSettingMixin(object):
# Don't do string-to-list conversion. # Don't do string-to-list conversion.
def _convert(self, value): def _convert(self, value):
......
...@@ -120,28 +120,35 @@ class TestImportableSetting(unittest.TestCase): ...@@ -120,28 +120,35 @@ class TestImportableSetting(unittest.TestCase):
i = _config.ImportableSetting() i = _config.ImportableSetting()
with self.assertRaisesRegex(ImportError, with self.assertRaisesRegex(ImportError,
"Cannot import from empty list"): "Cannot import from empty list"):
i._import([]) i._import_one_of([])
def test_path(self): def test_path_not_supported(self):
import warnings import warnings
i = _config.ImportableSetting() i = _config.ImportableSetting()
path = list(sys.path) path = list(sys.path)
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always") warnings.simplefilter("always")
with self.assertRaisesRegex(ImportError, with self.assertRaisesRegex(ImportError,
"Cannot import 'no_such_module'"): "Cannot import 'foo/bar/gevent.no_such_module'"):
i._import('foo/bar/gevent.no_such_module') i._import_one('foo/bar/gevent.no_such_module')
# We restored the path # We restored the path
self.assertEqual(path, sys.path) self.assertEqual(path, sys.path)
self.assertEqual(len(w), 1) # We did not issue a warning
self.assertEqual(w[0].category, DeprecationWarning) self.assertEqual(len(w), 0)
self.assertIn('Absolute paths', str(w[0].message))
def test_non_string(self): def test_non_string(self):
i = _config.ImportableSetting() i = _config.ImportableSetting()
self.assertIs(i._import(self), self) self.assertIs(i._import_one(self), self)
def test_get_options(self):
i = _config.ImportableSetting()
self.assertEqual({}, i.get_options())
i.shortname_map = {'foo': 'bad/path'}
options = i.get_options()
self.assertIn('foo', options)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment