Fixes for PEP451 import loaders and pytest 5.x

- pytest 5.x drops python2 compatibility and therefore only implements PEP 451
- pytest 5.x made the repr of `ExcInfo` less confusing (fixed tests depending
  on the old format)
This commit is contained in:
Anthony Sottile 2019-07-01 07:23:03 -07:00 committed by David Lord
parent b9c2267272
commit a5ecdfa7a5
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
6 changed files with 57 additions and 25 deletions

View File

@ -10,6 +10,7 @@ Unreleased
requires upgrading to Werkzeug 0.15.5. :issue:`3249` requires upgrading to Werkzeug 0.15.5. :issue:`3249`
- ``send_file`` url quotes the ":" and "/" characters for more - ``send_file`` url quotes the ":" and "/" characters for more
compatible UTF-8 filename support in some browsers. :issue:`3074` compatible UTF-8 filename support in some browsers. :issue:`3074`
- Fixes for PEP451 import loaders and pytest 5.x. :issue:`3275`
Version 1.0.3 Version 1.0.3

View File

@ -188,7 +188,7 @@ should be closed.
with pytest.raises(sqlite3.ProgrammingError) as e: with pytest.raises(sqlite3.ProgrammingError) as e:
db.execute('SELECT 1') db.execute('SELECT 1')
assert 'closed' in str(e) assert 'closed' in str(e.value)
The ``init-db`` command should call the ``init_db`` function and output The ``init-db`` command should call the ``init_db`` function and output
a message. a message.

View File

@ -12,7 +12,7 @@ def test_get_close_db(app):
with pytest.raises(sqlite3.ProgrammingError) as e: with pytest.raises(sqlite3.ProgrammingError) as e:
db.execute('SELECT 1') db.execute('SELECT 1')
assert 'closed' in str(e) assert 'closed' in str(e.value)
def test_init_db_command(runner, monkeypatch): def test_init_db_command(runner, monkeypatch):

View File

@ -789,19 +789,38 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
loader.__class__.__name__) loader.__class__.__name__)
def find_package(import_name): def _find_package_path(root_mod_name):
"""Finds a package and returns the prefix (or None if the package is """Find the path where the module's root exists in"""
not installed) as well as the folder that contains the package or if sys.version_info >= (3, 4):
module as a tuple. The package path returned is the module that would import importlib.util
have to be added to the pythonpath in order to make it possible to
import the module. The prefix is the path below which a UNIX like try:
folder structure exists (lib, share etc.). spec = importlib.util.find_spec(root_mod_name)
""" if spec is None:
root_mod_name = import_name.split('.')[0] raise ValueError("not found")
# ImportError: the machinery told us it does not exist
# ValueError:
# - the module name was invalid
# - the module name is __main__
# - *we* raised `ValueError` due to `spec` being `None`
except (ImportError, ValueError):
pass # handled below
else:
# namespace package
if spec.origin in {"namespace", None}:
return os.path.dirname(next(iter(spec.submodule_search_locations)))
# a package (with __init__.py)
elif spec.submodule_search_locations:
return os.path.dirname(os.path.dirname(spec.origin))
# just a normal module
else:
return os.path.dirname(spec.origin)
# we were unable to find the `package_path` using PEP 451 loaders
loader = pkgutil.get_loader(root_mod_name) loader = pkgutil.get_loader(root_mod_name)
if loader is None or import_name == '__main__': if loader is None or root_mod_name == '__main__':
# import name is not found, or interactive/main module # import name is not found, or interactive/main module
package_path = os.getcwd() return os.getcwd()
else: else:
# For .egg, zipimporter does not have get_filename until Python 2.7. # For .egg, zipimporter does not have get_filename until Python 2.7.
if hasattr(loader, 'get_filename'): if hasattr(loader, 'get_filename'):
@ -815,17 +834,29 @@ def find_package(import_name):
# Google App Engine's HardenedModulesHook # Google App Engine's HardenedModulesHook
# #
# Fall back to imports. # Fall back to imports.
__import__(import_name) __import__(root_mod_name)
filename = sys.modules[import_name].__file__ filename = sys.modules[root_mod_name].__file__
package_path = os.path.abspath(os.path.dirname(filename)) package_path = os.path.abspath(os.path.dirname(filename))
# In case the root module is a package we need to chop of the # In case the root module is a package we need to chop of the
# rightmost part. This needs to go through a helper function # rightmost part. This needs to go through a helper function
# because of python 3.3 namespace packages. # because of python 3.3 namespace packages.
if _matching_loader_thinks_module_is_package( if _matching_loader_thinks_module_is_package(loader, root_mod_name):
loader, root_mod_name):
package_path = os.path.dirname(package_path) package_path = os.path.dirname(package_path)
return package_path
def find_package(import_name):
"""Finds a package and returns the prefix (or None if the package is
not installed) as well as the folder that contains the package or
module as a tuple. The package path returned is the module that would
have to be added to the pythonpath in order to make it possible to
import the module. The prefix is the path below which a UNIX like
folder structure exists (lib, share etc.).
"""
root_mod_name, _, _ = import_name.partition('.')
package_path = _find_package_path(root_mod_name)
site_parent, site_folder = os.path.split(package_path) site_parent, site_folder = os.path.split(package_path)
py_prefix = os.path.abspath(sys.prefix) py_prefix = os.path.abspath(sys.prefix)
if package_path.startswith(py_prefix): if package_path.startswith(py_prefix):

View File

@ -1219,17 +1219,17 @@ def test_response_type_errors():
with pytest.raises(TypeError) as e: with pytest.raises(TypeError) as e:
c.get('/none') c.get('/none')
assert 'returned None' in str(e) assert 'returned None' in str(e.value)
with pytest.raises(TypeError) as e: with pytest.raises(TypeError) as e:
c.get('/small_tuple') c.get('/small_tuple')
assert 'tuple must have the form' in str(e) assert 'tuple must have the form' in str(e.value)
pytest.raises(TypeError, c.get, '/large_tuple') pytest.raises(TypeError, c.get, '/large_tuple')
with pytest.raises(TypeError) as e: with pytest.raises(TypeError) as e:
c.get('/bad_type') c.get('/bad_type')
assert 'it was a bool' in str(e) assert 'it was a bool' in str(e.value)
pytest.raises(TypeError, c.get, '/bad_wsgi') pytest.raises(TypeError, c.get, '/bad_wsgi')
@ -1622,7 +1622,7 @@ def test_debug_mode_complains_after_first_request(app, client):
@app.route('/foo') @app.route('/foo')
def broken(): def broken():
return 'Meh' return 'Meh'
assert 'A setup function was called' in str(e) assert 'A setup function was called' in str(e.value)
app.debug = False app.debug = False
@ -1677,9 +1677,9 @@ def test_routing_redirect_debugging(app, client):
with client: with client:
with pytest.raises(AssertionError) as e: with pytest.raises(AssertionError) as e:
client.post('/foo', data={}) client.post('/foo', data={})
assert 'http://localhost/foo/' in str(e) assert 'http://localhost/foo/' in str(e.value)
assert ('Make sure to directly send ' assert ('Make sure to directly send '
'your POST-request to this URL') in str(e) 'your POST-request to this URL') in str(e.value)
rv = client.get('/foo', data={}, follow_redirects=True) rv = client.get('/foo', data={}, follow_redirects=True)
assert rv.data == b'success' assert rv.data == b'success'

View File

@ -468,8 +468,8 @@ class TestSendfile(object):
def test_send_file_object_without_mimetype(self, app, req_ctx): def test_send_file_object_without_mimetype(self, app, req_ctx):
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
flask.send_file(StringIO("LOL")) flask.send_file(StringIO("LOL"))
assert 'Unable to infer MIME-type' in str(excinfo) assert 'Unable to infer MIME-type' in str(excinfo.value)
assert 'no filename is available' in str(excinfo) assert 'no filename is available' in str(excinfo.value)
flask.send_file(StringIO("LOL"), attachment_filename='filename') flask.send_file(StringIO("LOL"), attachment_filename='filename')