Add check for packages restricting Python version (#145690)
* Add check for packages restricting Python version * Apply suggestions from code review * until * untilpull/145695/head
parent
7b1dfc35d1
commit
96c9636086
|
@ -222,6 +222,15 @@ class Integration:
|
|||
"""Add a warning."""
|
||||
self.warnings.append(Error(*args, **kwargs))
|
||||
|
||||
def add_warning_or_error(
|
||||
self, warning_only: bool, *args: Any, **kwargs: Any
|
||||
) -> None:
|
||||
"""Add an error or a warning."""
|
||||
if warning_only:
|
||||
self.add_warning(*args, **kwargs)
|
||||
else:
|
||||
self.add_error(*args, **kwargs)
|
||||
|
||||
def load_manifest(self) -> None:
|
||||
"""Load manifest."""
|
||||
manifest_path = self.path / "manifest.json"
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
|
||||
from collections import deque
|
||||
from functools import cache
|
||||
from importlib.metadata import metadata
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
@ -319,6 +320,33 @@ FORBIDDEN_PACKAGE_EXCEPTIONS: dict[str, dict[str, set[str]]] = {
|
|||
},
|
||||
}
|
||||
|
||||
PYTHON_VERSION_CHECK_EXCEPTIONS: dict[str, dict[str, set[str]]] = {
|
||||
# In the form dict("domain": {"package": {"dependency1", "dependency2"}})
|
||||
# - domain is the integration domain
|
||||
# - package is the package (can be transitive) referencing the dependency
|
||||
# - dependencyX should be the name of the referenced dependency
|
||||
"bluetooth": {
|
||||
# https://github.com/hbldh/bleak/pull/1718 (not yet released)
|
||||
"homeassistant": {"bleak"}
|
||||
},
|
||||
"eq3btsmart": {
|
||||
# https://github.com/EuleMitKeule/eq3btsmart/releases/tag/2.0.0
|
||||
"homeassistant": {"eq3btsmart"}
|
||||
},
|
||||
"homekit_controller": {
|
||||
# https://github.com/Jc2k/aiohomekit/issues/456
|
||||
"homeassistant": {"aiohomekit"}
|
||||
},
|
||||
"netatmo": {
|
||||
# https://github.com/jabesq-org/pyatmo/pull/533 (not yet released)
|
||||
"homeassistant": {"pyatmo"}
|
||||
},
|
||||
"python_script": {
|
||||
# Security audits are needed for each Python version
|
||||
"homeassistant": {"restrictedpython"}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||
"""Handle requirements for integrations."""
|
||||
|
@ -489,6 +517,11 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||
)
|
||||
needs_package_version_check_exception = False
|
||||
|
||||
python_version_check_exceptions = PYTHON_VERSION_CHECK_EXCEPTIONS.get(
|
||||
integration.domain, {}
|
||||
)
|
||||
needs_python_version_check_exception = False
|
||||
|
||||
while to_check:
|
||||
package = to_check.popleft()
|
||||
|
||||
|
@ -507,22 +540,32 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
package in packages # Top-level checks only until bleak is resolved
|
||||
and (requires_python := metadata(package)["Requires-Python"])
|
||||
and not all(
|
||||
_is_dependency_version_range_valid(version_part, "SemVer")
|
||||
for version_part in requires_python.split(",")
|
||||
)
|
||||
):
|
||||
needs_python_version_check_exception = True
|
||||
integration.add_warning_or_error(
|
||||
package in python_version_check_exceptions.get("homeassistant", set()),
|
||||
"requirements",
|
||||
f"Version restrictions for Python are too strict ({requires_python}) in {package}",
|
||||
)
|
||||
|
||||
dependencies: dict[str, str] = item["dependencies"]
|
||||
package_exceptions = forbidden_package_exceptions.get(package, set())
|
||||
for pkg, version in dependencies.items():
|
||||
if pkg.startswith("types-") or pkg in FORBIDDEN_PACKAGES:
|
||||
reason = FORBIDDEN_PACKAGES.get(pkg, "not be a runtime dependency")
|
||||
needs_forbidden_package_exceptions = True
|
||||
if pkg in package_exceptions:
|
||||
integration.add_warning(
|
||||
"requirements",
|
||||
f"Package {pkg} should {reason} in {package}",
|
||||
)
|
||||
else:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f"Package {pkg} should {reason} in {package}",
|
||||
)
|
||||
integration.add_warning_or_error(
|
||||
pkg in package_exceptions,
|
||||
"requirements",
|
||||
f"Package {pkg} should {reason} in {package}",
|
||||
)
|
||||
if not check_dependency_version_range(
|
||||
integration,
|
||||
package,
|
||||
|
@ -546,6 +589,12 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||
f"Integration {integration.domain} version restrictions checks have been "
|
||||
"resolved, please remove from `PACKAGE_CHECK_VERSION_RANGE_EXCEPTIONS`",
|
||||
)
|
||||
if python_version_check_exceptions and not needs_python_version_check_exception:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f"Integration {integration.domain} version restrictions for Python have "
|
||||
"been resolved, please remove from `PYTHON_VERSION_CHECK_EXCEPTIONS`",
|
||||
)
|
||||
|
||||
return all_requirements
|
||||
|
||||
|
@ -571,21 +620,16 @@ def check_dependency_version_range(
|
|||
):
|
||||
return True
|
||||
|
||||
if pkg in package_exceptions:
|
||||
integration.add_warning(
|
||||
"requirements",
|
||||
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
||||
)
|
||||
else:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
||||
)
|
||||
integration.add_warning_or_error(
|
||||
pkg in package_exceptions,
|
||||
"requirements",
|
||||
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _is_dependency_version_range_valid(version_part: str, convention: str) -> bool:
|
||||
version_match = PIP_VERSION_RANGE_SEPARATOR.match(version_part)
|
||||
version_match = PIP_VERSION_RANGE_SEPARATOR.match(version_part.strip())
|
||||
operator = version_match.group(1)
|
||||
version = version_match.group(2)
|
||||
|
||||
|
|
Loading…
Reference in New Issue