mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add check for packages restricting Python version (#145690)
* Add check for packages restricting Python version * Apply suggestions from code review * until * until
This commit is contained in:
parent
7b1dfc35d1
commit
96c9636086
@ -222,6 +222,15 @@ class Integration:
|
|||||||
"""Add a warning."""
|
"""Add a warning."""
|
||||||
self.warnings.append(Error(*args, **kwargs))
|
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:
|
def load_manifest(self) -> None:
|
||||||
"""Load manifest."""
|
"""Load manifest."""
|
||||||
manifest_path = self.path / "manifest.json"
|
manifest_path = self.path / "manifest.json"
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import cache
|
from functools import cache
|
||||||
|
from importlib.metadata import metadata
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
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:
|
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||||
"""Handle requirements for integrations."""
|
"""Handle requirements for integrations."""
|
||||||
@ -489,6 +517,11 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||||||
)
|
)
|
||||||
needs_package_version_check_exception = False
|
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:
|
while to_check:
|
||||||
package = to_check.popleft()
|
package = to_check.popleft()
|
||||||
|
|
||||||
@ -507,19 +540,29 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||||||
)
|
)
|
||||||
continue
|
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"]
|
dependencies: dict[str, str] = item["dependencies"]
|
||||||
package_exceptions = forbidden_package_exceptions.get(package, set())
|
package_exceptions = forbidden_package_exceptions.get(package, set())
|
||||||
for pkg, version in dependencies.items():
|
for pkg, version in dependencies.items():
|
||||||
if pkg.startswith("types-") or pkg in FORBIDDEN_PACKAGES:
|
if pkg.startswith("types-") or pkg in FORBIDDEN_PACKAGES:
|
||||||
reason = FORBIDDEN_PACKAGES.get(pkg, "not be a runtime dependency")
|
reason = FORBIDDEN_PACKAGES.get(pkg, "not be a runtime dependency")
|
||||||
needs_forbidden_package_exceptions = True
|
needs_forbidden_package_exceptions = True
|
||||||
if pkg in package_exceptions:
|
integration.add_warning_or_error(
|
||||||
integration.add_warning(
|
pkg in package_exceptions,
|
||||||
"requirements",
|
|
||||||
f"Package {pkg} should {reason} in {package}",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
integration.add_error(
|
|
||||||
"requirements",
|
"requirements",
|
||||||
f"Package {pkg} should {reason} in {package}",
|
f"Package {pkg} should {reason} in {package}",
|
||||||
)
|
)
|
||||||
@ -546,6 +589,12 @@ def get_requirements(integration: Integration, packages: set[str]) -> set[str]:
|
|||||||
f"Integration {integration.domain} version restrictions checks have been "
|
f"Integration {integration.domain} version restrictions checks have been "
|
||||||
"resolved, please remove from `PACKAGE_CHECK_VERSION_RANGE_EXCEPTIONS`",
|
"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
|
return all_requirements
|
||||||
|
|
||||||
@ -571,13 +620,8 @@ def check_dependency_version_range(
|
|||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if pkg in package_exceptions:
|
integration.add_warning_or_error(
|
||||||
integration.add_warning(
|
pkg in package_exceptions,
|
||||||
"requirements",
|
|
||||||
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
integration.add_error(
|
|
||||||
"requirements",
|
"requirements",
|
||||||
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
f"Version restrictions for {pkg} are too strict ({version}) in {source}",
|
||||||
)
|
)
|
||||||
@ -585,7 +629,7 @@ def check_dependency_version_range(
|
|||||||
|
|
||||||
|
|
||||||
def _is_dependency_version_range_valid(version_part: str, convention: str) -> bool:
|
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)
|
operator = version_match.group(1)
|
||||||
version = version_match.group(2)
|
version = version_match.group(2)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user