fix use of importlib.util.find_spec

This commit is contained in:
David Lord 2023-06-09 09:34:42 -07:00
parent c8cf4694c6
commit bda295d37f
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
3 changed files with 40 additions and 82 deletions

View File

@ -5,6 +5,7 @@ Unreleased
- Python 3.12 compatibility. - Python 3.12 compatibility.
- Update Werkzeug requirement to >=2.3.5. - Update Werkzeug requirement to >=2.3.5.
- Refactor how an app's root and instance paths are determined. :issue:`5160`
Version 2.3.2 Version 2.3.2

View File

@ -575,13 +575,20 @@ def get_root_path(import_name: str) -> str:
return os.path.dirname(os.path.abspath(mod.__file__)) return os.path.dirname(os.path.abspath(mod.__file__))
# Next attempt: check the loader. # Next attempt: check the loader.
spec = importlib.util.find_spec(import_name) try:
loader = spec.loader if spec is not None else None spec = importlib.util.find_spec(import_name)
if spec is None:
raise ValueError
except (ImportError, ValueError):
loader = None
else:
loader = spec.loader
# Loader does not exist or we're referring to an unloaded main # Loader does not exist or we're referring to an unloaded main
# module or a main module without path (interactive sessions), go # module or a main module without path (interactive sessions), go
# with the current working directory. # with the current working directory.
if loader is None or import_name == "__main__": if loader is None:
return os.getcwd() return os.getcwd()
if hasattr(loader, "get_filename"): if hasattr(loader, "get_filename"):

View File

@ -779,31 +779,6 @@ def _endpoint_from_view_func(view_func: t.Callable) -> str:
return view_func.__name__ return view_func.__name__
def _matching_loader_thinks_module_is_package(loader, mod_name):
"""Attempt to figure out if the given name is a package or a module.
:param: loader: The loader that handled the name.
:param mod_name: The name of the package or module.
"""
# Use loader.is_package if it's available.
if hasattr(loader, "is_package"):
return loader.is_package(mod_name)
cls = type(loader)
# NamespaceLoader doesn't implement is_package, but all names it
# loads must be packages.
if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
return True
# Otherwise we need to fail with an error that explains what went
# wrong.
raise AttributeError(
f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
f" import hooks."
)
def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool: def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
# Path.is_relative_to doesn't exist until Python 3.9 # Path.is_relative_to doesn't exist until Python 3.9
try: try:
@ -822,64 +797,39 @@ def _find_package_path(import_name):
if root_spec is None: if root_spec is None:
raise ValueError("not found") 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 `root_spec` being `None`
except (ImportError, ValueError): except (ImportError, ValueError):
pass # handled below # ImportError: the machinery told us it does not exist
else: # ValueError:
# namespace package # - the module name was invalid
if root_spec.origin in {"namespace", None}: # - the module name is __main__
package_spec = importlib.util.find_spec(import_name) # - we raised `ValueError` due to `root_spec` being `None`
if package_spec is not None and package_spec.submodule_search_locations:
# Pick the path in the namespace that contains the submodule.
package_path = pathlib.Path(
os.path.commonpath(package_spec.submodule_search_locations)
)
search_locations = (
location
for location in root_spec.submodule_search_locations
if _path_is_relative_to(package_path, location)
)
else:
# Pick the first path.
search_locations = iter(root_spec.submodule_search_locations)
return os.path.dirname(next(search_locations))
# a package (with __init__.py)
elif root_spec.submodule_search_locations:
return os.path.dirname(os.path.dirname(root_spec.origin))
# just a normal module
else:
return os.path.dirname(root_spec.origin)
# we were unable to find the `package_path` using PEP 451 loaders
spec = importlib.util.find_spec(root_mod_name)
loader = spec.loader if spec is not None else None
if loader is None or root_mod_name == "__main__":
# import name is not found, or interactive/main module
return os.getcwd() return os.getcwd()
if hasattr(loader, "get_filename"): if root_spec.origin in {"namespace", None}:
filename = loader.get_filename(root_mod_name) # namespace package
elif hasattr(loader, "archive"): package_spec = importlib.util.find_spec(import_name)
# zipimporter's loader.archive points to the .zip file.
filename = loader.archive if package_spec is not None and package_spec.submodule_search_locations:
# Pick the path in the namespace that contains the submodule.
package_path = pathlib.Path(
os.path.commonpath(package_spec.submodule_search_locations)
)
search_location = next(
location
for location in root_spec.submodule_search_locations
if _path_is_relative_to(package_path, location)
)
else:
# Pick the first path.
search_location = root_spec.submodule_search_locations[0]
return os.path.dirname(search_location)
elif root_spec.submodule_search_locations:
# package with __init__.py
return os.path.dirname(os.path.dirname(root_spec.origin))
else: else:
# At least one loader is missing both get_filename and archive: # module
# Google App Engine's HardenedModulesHook, use __file__. return os.path.dirname(root_spec.origin)
filename = importlib.import_module(root_mod_name).__file__
package_path = os.path.abspath(os.path.dirname(filename))
# If the imported name is a package, filename is currently pointing
# to the root of the package, need to get the current directory.
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: str): def find_package(import_name: str):