mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Catch blocking version pinning in dependencies early (#145364)
* Catch upper bindings in dependencies early * One more * Apply suggestions from code review
This commit is contained in:
parent
f36ee88a87
commit
9e7ae1daa4
@ -22,6 +22,19 @@ from script.gen_requirements_all import (
|
|||||||
|
|
||||||
from .model import Config, Integration
|
from .model import Config, Integration
|
||||||
|
|
||||||
|
PACKAGE_CHECK_VERSION_RANGE = {
|
||||||
|
"aiohttp": "SemVer",
|
||||||
|
# https://github.com/iMicknl/python-overkiz-api/issues/1644
|
||||||
|
# "attrs": "CalVer"
|
||||||
|
"grpcio": "SemVer",
|
||||||
|
"mashumaro": "SemVer",
|
||||||
|
"pydantic": "SemVer",
|
||||||
|
"pyjwt": "SemVer",
|
||||||
|
"pytz": "CalVer",
|
||||||
|
"typing_extensions": "SemVer",
|
||||||
|
"yarl": "SemVer",
|
||||||
|
}
|
||||||
|
|
||||||
PACKAGE_REGEX = re.compile(
|
PACKAGE_REGEX = re.compile(
|
||||||
r"^(?:--.+\s)?([-_,\.\w\d\[\]]+)(==|>=|<=|~=|!=|<|>|===)*(.*)$"
|
r"^(?:--.+\s)?([-_,\.\w\d\[\]]+)(==|>=|<=|~=|!=|<|>|===)*(.*)$"
|
||||||
)
|
)
|
||||||
@ -175,7 +188,7 @@ def get_pipdeptree() -> dict[str, dict[str, Any]]:
|
|||||||
"key": "flake8-docstrings",
|
"key": "flake8-docstrings",
|
||||||
"package_name": "flake8-docstrings",
|
"package_name": "flake8-docstrings",
|
||||||
"installed_version": "1.5.0"
|
"installed_version": "1.5.0"
|
||||||
"dependencies": {"flake8"}
|
"dependencies": {"flake8": ">=1.2.3, <4.5.0"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@ -191,7 +204,9 @@ def get_pipdeptree() -> dict[str, dict[str, Any]]:
|
|||||||
):
|
):
|
||||||
deptree[item["package"]["key"]] = {
|
deptree[item["package"]["key"]] = {
|
||||||
**item["package"],
|
**item["package"],
|
||||||
"dependencies": {dep["key"] for dep in item["dependencies"]},
|
"dependencies": {
|
||||||
|
dep["key"]: dep["required_version"] for dep in item["dependencies"]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return deptree
|
return deptree
|
||||||
|
|
||||||
@ -222,8 +237,8 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
dependencies: set[str] = item["dependencies"]
|
dependencies: dict[str, str] = item["dependencies"]
|
||||||
for pkg in dependencies:
|
for pkg, version in dependencies.items():
|
||||||
if pkg.startswith("types-") or pkg in FORBIDDEN_PACKAGES:
|
if pkg.startswith("types-") or pkg in FORBIDDEN_PACKAGES:
|
||||||
if package in FORBIDDEN_PACKAGE_EXCEPTIONS:
|
if package in FORBIDDEN_PACKAGE_EXCEPTIONS:
|
||||||
integration.add_warning(
|
integration.add_warning(
|
||||||
@ -235,12 +250,62 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||||||
"requirements",
|
"requirements",
|
||||||
f"Package {pkg} should not be a runtime dependency in {package}",
|
f"Package {pkg} should not be a runtime dependency in {package}",
|
||||||
)
|
)
|
||||||
|
check_dependency_version_range(integration, package, pkg, version)
|
||||||
|
|
||||||
to_check.extend(dependencies)
|
to_check.extend(dependencies)
|
||||||
|
|
||||||
return all_requirements
|
return all_requirements
|
||||||
|
|
||||||
|
|
||||||
|
def check_dependency_version_range(
|
||||||
|
integration: Integration, source: str, pkg: str, version: str
|
||||||
|
) -> None:
|
||||||
|
"""Check requirement version range.
|
||||||
|
|
||||||
|
We want to avoid upper version bounds that are too strict for common packages.
|
||||||
|
"""
|
||||||
|
if version == "Any" or (convention := PACKAGE_CHECK_VERSION_RANGE.get(pkg)) is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not all(
|
||||||
|
_is_dependency_version_range_valid(version_part, convention)
|
||||||
|
for version_part in version.split(";", 1)[0].split(",")
|
||||||
|
):
|
||||||
|
integration.add_error(
|
||||||
|
"requirements",
|
||||||
|
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_dependency_version_range_valid(version_part: str, convention: str) -> bool:
|
||||||
|
version_match = PIP_VERSION_RANGE_SEPARATOR.match(version_part)
|
||||||
|
operator = version_match.group(1)
|
||||||
|
version = version_match.group(2)
|
||||||
|
|
||||||
|
if operator in (">", ">=", "!="):
|
||||||
|
# Lower version binding and version exclusion are fine
|
||||||
|
return True
|
||||||
|
|
||||||
|
if convention == "SemVer":
|
||||||
|
if operator == "==":
|
||||||
|
# Explicit version with wildcard is allowed only on major version
|
||||||
|
# e.g. ==1.* is allowed, but ==1.2.* is not
|
||||||
|
return version.endswith(".*") and version.count(".") == 1
|
||||||
|
|
||||||
|
awesome = AwesomeVersion(version)
|
||||||
|
if operator in ("<", "<="):
|
||||||
|
# Upper version binding only allowed on major version
|
||||||
|
# e.g. <=3 is allowed, but <=3.1 is not
|
||||||
|
return awesome.section(1) == 0 and awesome.section(2) == 0
|
||||||
|
|
||||||
|
if operator == "~=":
|
||||||
|
# Compatible release operator is only allowed on major or minor version
|
||||||
|
# e.g. ~=1.2 is allowed, but ~=1.2.3 is not
|
||||||
|
return awesome.section(2) == 0
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def install_requirements(integration: Integration, requirements: set[str]) -> bool:
|
def install_requirements(integration: Integration, requirements: set[str]) -> bool:
|
||||||
"""Install integration requirements.
|
"""Install integration requirements.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user