diff --git a/CHANGES.rst b/CHANGES.rst index 73b5eb98..59193bf7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -94,6 +94,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 diff --git a/docs/tutorial/tests.rst b/docs/tutorial/tests.rst index 656f014c..e8b14e4c 100644 --- a/docs/tutorial/tests.rst +++ b/docs/tutorial/tests.rst @@ -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. diff --git a/examples/tutorial/tests/test_db.py b/examples/tutorial/tests/test_db.py index 86d2c0e9..31c6c57c 100644 --- a/examples/tutorial/tests/test_db.py +++ b/examples/tutorial/tests/test_db.py @@ -13,7 +13,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): diff --git a/src/flask/helpers.py b/src/flask/helpers.py index b121d040..3f401a5b 100644 --- a/src/flask/helpers.py +++ b/src/flask/helpers.py @@ -610,7 +610,7 @@ def send_file( "filename": unicodedata.normalize("NFKD", attachment_filename).encode( "ascii", "ignore" ), - 'filename*': "UTF-8''%s" % url_quote(attachment_filename, safe=b""), + "filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=b""), } else: filenames = {"filename": attachment_filename} @@ -847,19 +847,38 @@ def _matching_loader_thinks_module_is_package(loader, mod_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"): @@ -873,8 +892,8 @@ 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 @@ -883,6 +902,19 @@ def find_package(import_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): diff --git a/tests/test_basic.py b/tests/test_basic.py index b46a7908..8ebdaba4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1220,21 +1220,17 @@ def test_response_type_errors(): 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 "object is not callable" not in str(e) - assert "it was a bool" in str(e) + assert "it was a bool" in str(e.value) pytest.raises(TypeError, c.get, "/bad_wsgi") @@ -1636,7 +1632,7 @@ def test_debug_mode_complains_after_first_request(app, client): 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 @@ -1691,8 +1687,10 @@ def test_routing_redirect_debugging(app, client): with client: with pytest.raises(AssertionError) as e: client.post("/foo", data={}) - assert "http://localhost/foo/" in str(e) - assert ("Make sure to directly send your POST-request to this URL") 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.value + ) rv = client.get("/foo", data={}, follow_redirects=True) assert rv.data == b"success" diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3d4b2782..78a56221 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -483,8 +483,8 @@ class TestSendfile(object): 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")