Restore support for packages being installed from urls with fragments (#109267)

pull/109294/head
J. Nick Koston 2024-01-31 21:56:57 -10:00 committed by GitHub
parent 5d3364521f
commit 8afcd53af6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 5 deletions

View File

@ -9,6 +9,7 @@ import os
from pathlib import Path from pathlib import Path
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
import sys import sys
from urllib.parse import urlparse
from packaging.requirements import InvalidRequirement, Requirement from packaging.requirements import InvalidRequirement, Requirement
@ -40,14 +41,32 @@ def is_installed(requirement_str: str) -> bool:
expected input is a pip compatible package specifier (requirement string) expected input is a pip compatible package specifier (requirement string)
e.g. "package==1.0.0" or "package>=1.0.0,<2.0.0" e.g. "package==1.0.0" or "package>=1.0.0,<2.0.0"
For backward compatibility, it also accepts a URL with a fragment
e.g. "git+https://github.com/pypa/pip#pip>=1"
Returns True when the requirement is met. Returns True when the requirement is met.
Returns False when the package is not installed or doesn't meet req. Returns False when the package is not installed or doesn't meet req.
""" """
try: try:
req = Requirement(requirement_str) req = Requirement(requirement_str)
except InvalidRequirement: except InvalidRequirement:
_LOGGER.error("Invalid requirement '%s'", requirement_str) if "#" not in requirement_str:
return False _LOGGER.error("Invalid requirement '%s'", requirement_str)
return False
# This is likely a URL with a fragment
# example: git+https://github.com/pypa/pip#pip>=1
# fragment support was originally used to install zip files, and
# we no longer do this in Home Assistant. However, custom
# components started using it to install packages from git
# urls which would make it would be a breaking change to
# remove it.
try:
req = Requirement(urlparse(requirement_str).fragment)
except InvalidRequirement:
_LOGGER.error("Invalid requirement '%s'", requirement_str)
return False
try: try:
if (installed_version := version(req.name)) is None: if (installed_version := version(req.name)) is None:

View File

@ -217,7 +217,7 @@ async def test_async_get_user_site(mock_env_copy) -> None:
assert ret == os.path.join(deps_dir, "lib_dir") assert ret == os.path.join(deps_dir, "lib_dir")
def test_check_package_global() -> None: def test_check_package_global(caplog: pytest.LogCaptureFixture) -> None:
"""Test for an installed package.""" """Test for an installed package."""
pkg = metadata("homeassistant") pkg = metadata("homeassistant")
installed_package = pkg["name"] installed_package = pkg["name"]
@ -229,10 +229,19 @@ def test_check_package_global() -> None:
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}") assert not package.is_installed(f"{installed_package}<{installed_version}")
assert package.is_installed("-1 invalid_package") is False
assert "Invalid requirement '-1 invalid_package'" in caplog.text
def test_check_package_zip() -> None:
"""Test for an installed zip package.""" def test_check_package_fragment(caplog: pytest.LogCaptureFixture) -> None:
"""Test for an installed package with a fragment."""
assert not package.is_installed(TEST_ZIP_REQ) assert not package.is_installed(TEST_ZIP_REQ)
assert package.is_installed("git+https://github.com/pypa/pip#pip>=1")
assert not package.is_installed("git+https://github.com/pypa/pip#-1 invalid")
assert (
"Invalid requirement 'git+https://github.com/pypa/pip#-1 invalid'"
in caplog.text
)
def test_get_is_installed() -> None: def test_get_is_installed() -> None: