diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0f060724994202e759d1e2f2f649b25a70be8528..e337c019f52de37bd8fd98da3ba5e3cba38186bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,7 +46,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - pip install -U pip==20.1.1 setuptools + pip install -U pip setuptools pip install -r requirements.txt -r requirements_test.txt # Uninstalling typing as a workaround. Eventually we should make sure # all our dependencies drop typing. @@ -603,7 +603,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - pip install -U pip==20.1.1 setuptools wheel + pip install -U pip setuptools wheel pip install -r requirements_all.txt pip install -r requirements_test.txt # Uninstalling typing as a workaround. Eventually we should make sure diff --git a/homeassistant/components/ezviz/camera.py b/homeassistant/components/ezviz/camera.py index e7e6725e45571a381d65de612347fa9a2e1c2ed3..701af451496a567400a43aa5c08e4de15014543b 100644 --- a/homeassistant/components/ezviz/camera.py +++ b/homeassistant/components/ezviz/camera.py @@ -2,6 +2,7 @@ import asyncio import logging +# pylint: disable=import-error from haffmpeg.tools import IMAGE_JPEG, ImageFrame from pyezviz.camera import EzvizCamera from pyezviz.client import EzvizClient, PyEzvizError diff --git a/homeassistant/components/ezviz/manifest.json b/homeassistant/components/ezviz/manifest.json index 651fd77619cdcb02b4f876f81e42cab3ec0d9390..03bdfc5217c8bf40f5513c610f80e6b0ae101e4d 100644 --- a/homeassistant/components/ezviz/manifest.json +++ b/homeassistant/components/ezviz/manifest.json @@ -1,4 +1,5 @@ { + "disabled": "Dependency contains code that breaks Home Assistant.", "domain": "ezviz", "name": "Ezviz", "documentation": "https://www.home-assistant.io/integrations/ezviz", diff --git a/homeassistant/loader.py b/homeassistant/loader.py index b82f2c0109a994a117fd07a788f7a12621eb621a..c5027710c4781415c20d5dc9c49660befa21e5b9 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -271,6 +271,11 @@ class Integration: """Return name.""" return cast(str, self.manifest["name"]) + @property + def disabled(self) -> Optional[str]: + """Return reason integration is disabled.""" + return cast(Optional[str], self.manifest.get("disabled")) + @property def domain(self) -> str: """Return domain.""" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c6a543e94d7f40885f0bd88f5d65d60c8b7e3f97..261dd5dd34dd05ebebd1dc17cbbed9ea6dae5dfb 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -44,3 +44,6 @@ enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome pycrypto==1000000000.0.0 + +# This is built-in and breaks pip if installed +uuid==1000000000.0.0 diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 578cd33b0979d843cbba1021b95edfa2b93e8c9c..341229a83b1b3200b5256016310b9a8bf16c9ced 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -124,6 +124,10 @@ async def _async_setup_component( log_error("Integration not found.") return False + if integration.disabled: + log_error(f"dependency is disabled - {integration.disabled}") + return False + # Validate all dependencies exist and there are no circular dependencies if not await integration.resolve_dependencies(): return False diff --git a/requirements_all.txt b/requirements_all.txt index e543e32559276180bf798259fb8ced2ad5b61a4b..de5c87a07c764cbe8a4a92253afcae8ef9781694 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1334,9 +1334,6 @@ pyephember==0.3.1 # homeassistant.components.everlights pyeverlights==0.1.0 -# homeassistant.components.ezviz -pyezviz==0.1.5 - # homeassistant.components.fido pyfido==2.1.1 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index b851983b6f692ee2fe2fc88d914b8836b05fd612..25d4d3d3e7e3dfd1a4c16eb8aa04cbd83ec9c31d 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -71,6 +71,9 @@ enum34==1000000000.0.0 # This is a old unmaintained library and is replaced with pycryptodome pycrypto==1000000000.0.0 + +# This is built-in and breaks pip if installed +uuid==1000000000.0.0 """ IGNORE_PRE_COMMIT_HOOK_ID = ( @@ -178,6 +181,9 @@ def gather_requirements_from_manifests(errors, reqs): errors.append(f"The manifest for integration {domain} is invalid.") continue + if integration.disabled: + continue + process_requirements( errors, integration.requirements, f"homeassistant.components.{domain}", reqs ) diff --git a/script/hassfest/manifest.py b/script/hassfest/manifest.py index cb592b63b5340f1609df4fdcd820f1e1cab85b7e..cd3895f5f2041d856791ebc05f02b3fb63f9c4c2 100644 --- a/script/hassfest/manifest.py +++ b/script/hassfest/manifest.py @@ -54,6 +54,7 @@ MANIFEST_SCHEMA = vol.Schema( vol.Optional("dependencies"): [str], vol.Optional("after_dependencies"): [str], vol.Required("codeowners"): [str], + vol.Optional("disabled"): str, } ) diff --git a/script/hassfest/model.py b/script/hassfest/model.py index bb438f4d84f62ca5851e282ff5a9d87255d9b03c..c993689aaab4d54b530e38d1ea258fa3329a376f 100644 --- a/script/hassfest/model.py +++ b/script/hassfest/model.py @@ -73,6 +73,11 @@ class Integration: """Integration domain.""" return self.path.name + @property + def disabled(self) -> Optional[str]: + """List of disabled.""" + return self.manifest.get("disabled") + @property def requirements(self) -> List[str]: """List of requirements.""" diff --git a/tests/test_setup.py b/tests/test_setup.py index abd9cecd9ac6d71a5192a06c3bd86641ff64abe5..8651308572a0b6bfdd529b62c7403678fa2e723a 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -583,3 +583,15 @@ async def test_parallel_entry_setup(hass): await setup.async_setup_component(hass, "comp", {}) assert calls == [1, 2, 1, 2] + + +async def test_integration_disabled(hass, caplog): + """Test we can disable an integration.""" + disabled_reason = "Dependency contains code that breaks Home Assistant" + mock_integration( + hass, + MockModule("test_component1", partial_manifest={"disabled": disabled_reason}), + ) + result = await setup.async_setup_component(hass, "test_component1", {}) + assert not result + assert disabled_reason in caplog.text