Skip to content

Commit

Permalink
Merge pull request #3278 from asottile/importlib_spec
Browse files Browse the repository at this point in the history
Fixes for PEP451 import loaders and pytest 5.x
  • Loading branch information
davidism committed Jul 1, 2019
2 parents b9c2267 + a5ecdfa commit 043443d
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Unreleased
requires upgrading to Werkzeug 0.15.5. :issue:`3249`
- ``send_file`` url quotes the ":" and "/" characters for more
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
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ should be closed.
with pytest.raises(sqlite3.ProgrammingError) as e:
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
a message.
Expand Down
2 changes: 1 addition & 1 deletion examples/tutorial/tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def test_get_close_db(app):
with pytest.raises(sqlite3.ProgrammingError) as e:
db.execute('SELECT 1')

assert 'closed' in str(e)
assert 'closed' in str(e.value)


def test_init_db_command(runner, monkeypatch):
Expand Down
61 changes: 46 additions & 15 deletions flask/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,19 +789,38 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
loader.__class__.__name__)


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.split('.')[0]
def _find_package_path(root_mod_name):
"""Find the path where the module's root exists in"""
if sys.version_info >= (3, 4):
import importlib.util

try:
spec = importlib.util.find_spec(root_mod_name)
if spec is None:
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)
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
package_path = os.getcwd()
return os.getcwd()
else:
# For .egg, zipimporter does not have get_filename until Python 2.7.
if hasattr(loader, 'get_filename'):
Expand All @@ -815,17 +834,29 @@ def find_package(import_name):
# Google App Engine's HardenedModulesHook
#
# Fall back to imports.
__import__(import_name)
filename = sys.modules[import_name].__file__
__import__(root_mod_name)
filename = sys.modules[root_mod_name].__file__
package_path = os.path.abspath(os.path.dirname(filename))

# In case the root module is a package we need to chop of the
# rightmost part. This needs to go through a helper function
# because of python 3.3 namespace packages.
if _matching_loader_thinks_module_is_package(
loader, root_mod_name):
if _matching_loader_thinks_module_is_package(loader, root_mod_name):
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)
py_prefix = os.path.abspath(sys.prefix)
if package_path.startswith(py_prefix):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,17 +1219,17 @@ def from_bad_wsgi():

with pytest.raises(TypeError) as e:
c.get('/none')
assert 'returned None' in str(e)
assert 'returned None' in str(e.value)

with pytest.raises(TypeError) as e:
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')

with pytest.raises(TypeError) as e:
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')

Expand Down Expand Up @@ -1622,7 +1622,7 @@ def index():
@app.route('/foo')
def broken():
return 'Meh'
assert 'A setup function was called' in str(e)
assert 'A setup function was called' in str(e.value)

app.debug = False

Expand Down Expand Up @@ -1677,9 +1677,9 @@ def foo():
with client:
with pytest.raises(AssertionError) as e:
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 '
'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)
assert rv.data == b'success'
Expand Down
4 changes: 2 additions & 2 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,8 @@ def index():
def test_send_file_object_without_mimetype(self, app, req_ctx):
with pytest.raises(ValueError) as excinfo:
flask.send_file(StringIO("LOL"))
assert 'Unable to infer MIME-type' in str(excinfo)
assert 'no filename is available' in str(excinfo)
assert 'Unable to infer MIME-type' in str(excinfo.value)
assert 'no filename is available' in str(excinfo.value)

flask.send_file(StringIO("LOL"), attachment_filename='filename')

Expand Down

0 comments on commit 043443d

Please sign in to comment.