Ensure startup can proceed when there is package metadata cruft (#47706)

If a package fails to install or partially installed importlib
version can return None. We now try pkg_resources first, then
try importlib, and handle the case where version unexpectedly
returns None
This commit is contained in:
J. Nick Koston 2021-03-10 21:12:02 -10:00 committed by GitHub
parent 00bd591238
commit 9a686d148e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 2 deletions

View file

@ -34,6 +34,9 @@ def is_installed(package: str) -> bool:
Returns False when the package is not installed or doesn't meet req.
"""
try:
pkg_resources.get_distribution(package)
return True
except (pkg_resources.ResolutionError, pkg_resources.ExtractionError):
req = pkg_resources.Requirement.parse(package)
except ValueError:
# This is a zip file. We no longer use this in Home Assistant,
@ -41,7 +44,14 @@ def is_installed(package: str) -> bool:
req = pkg_resources.Requirement.parse(urlparse(package).fragment)
try:
return version(req.project_name) in req
installed_version = version(req.project_name)
# This will happen when an install failed or
# was aborted while in progress see
# https://github.com/home-assistant/core/issues/47699
if installed_version is None:
_LOGGER.error("Installed version for %s resolved to None", req.project_name) # type: ignore
return False
return installed_version in req
except PackageNotFoundError:
return False

View file

@ -239,10 +239,55 @@ async def test_async_get_user_site(mock_env_copy):
def test_check_package_global():
"""Test for an installed package."""
installed_package = list(pkg_resources.working_set)[0].project_name
first_package = list(pkg_resources.working_set)[0]
installed_package = first_package.project_name
installed_version = first_package.version
assert package.is_installed(installed_package)
assert package.is_installed(f"{installed_package}=={installed_version}")
assert package.is_installed(f"{installed_package}>={installed_version}")
assert package.is_installed(f"{installed_package}<={installed_version}")
assert not package.is_installed(f"{installed_package}<{installed_version}")
def test_check_package_version_does_not_match():
"""Test for version mismatch."""
installed_package = list(pkg_resources.working_set)[0].project_name
assert not package.is_installed(f"{installed_package}==999.999.999")
assert not package.is_installed(f"{installed_package}>=999.999.999")
def test_check_package_zip():
"""Test for an installed zip package."""
assert not package.is_installed(TEST_ZIP_REQ)
def test_get_distribution_falls_back_to_version():
"""Test for get_distribution failing and fallback to version."""
first_package = list(pkg_resources.working_set)[0]
installed_package = first_package.project_name
installed_version = first_package.version
with patch(
"homeassistant.util.package.pkg_resources.get_distribution",
side_effect=pkg_resources.ExtractionError,
):
assert package.is_installed(installed_package)
assert package.is_installed(f"{installed_package}=={installed_version}")
assert package.is_installed(f"{installed_package}>={installed_version}")
assert package.is_installed(f"{installed_package}<={installed_version}")
assert not package.is_installed(f"{installed_package}<{installed_version}")
def test_check_package_previous_failed_install():
"""Test for when a previously install package failed and left cruft behind."""
first_package = list(pkg_resources.working_set)[0]
installed_package = first_package.project_name
installed_version = first_package.version
with patch(
"homeassistant.util.package.pkg_resources.get_distribution",
side_effect=pkg_resources.ExtractionError,
), patch("homeassistant.util.package.version", return_value=None):
assert not package.is_installed(installed_package)
assert not package.is_installed(f"{installed_package}=={installed_version}")