From 5ced4e2f3b35bf38bf15a0377b27063a081ff64c Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Mon, 22 May 2023 13:12:34 -0400 Subject: [PATCH] Update to python 3.11 (#4296) --- .github/workflows/builder.yml | 6 +++--- .github/workflows/ci.yaml | 2 +- build.yaml | 10 ++++----- requirements.txt | 2 +- supervisor/addons/__init__.py | 2 +- supervisor/api/ingress.py | 4 ++-- supervisor/backups/manager.py | 2 +- supervisor/core.py | 20 +++++++++++------- supervisor/homeassistant/module.py | 7 ++++++- supervisor/mounts/manager.py | 11 ++++++++-- supervisor/plugins/manager.py | 4 +++- supervisor/store/__init__.py | 2 +- supervisor/updater.py | 2 +- supervisor/utils/codenotary.py | 8 +++---- tests/addons/test_manager.py | 18 ++++++++++++++++ tests/homeassistant/test_module.py | 20 ++++++++++++++++++ tests/plugins/test_plugin_manager.py | 31 ++++++++++++++++++++++++++++ tests/store/test_store_manager.py | 12 +++++++++++ 18 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 tests/homeassistant/test_module.py create mode 100644 tests/plugins/test_plugin_manager.py diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index ec4c77d2b..199e56c32 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -33,12 +33,12 @@ on: - setup.py env: - DEFAULT_PYTHON: "3.10" + DEFAULT_PYTHON: "3.11" BUILD_NAME: supervisor BUILD_TYPE: supervisor concurrency: - group: '${{ github.workflow }}-${{ github.ref }}' + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: @@ -104,7 +104,7 @@ jobs: if: needs.init.outputs.requirements == 'true' uses: home-assistant/wheels@2023.04.0 with: - abi: cp310 + abi: cp311 tag: musllinux_1_2 arch: ${{ matrix.arch }} wheels-key: ${{ secrets.WHEELS_KEY }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 79afc27e0..3b81a569b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: pull_request: ~ env: - DEFAULT_PYTHON: "3.10" + DEFAULT_PYTHON: "3.11" PRE_COMMIT_HOME: ~/.cache/pre-commit DEFAULT_CAS: v1.0.2 diff --git a/build.yaml b/build.yaml index 0c9355a89..1979fdba1 100644 --- a/build.yaml +++ b/build.yaml @@ -1,11 +1,11 @@ image: homeassistant/{arch}-hassio-supervisor shadow_repository: ghcr.io/home-assistant build_from: - aarch64: ghcr.io/home-assistant/aarch64-base-python:3.10-alpine3.16 - armhf: ghcr.io/home-assistant/armhf-base-python:3.10-alpine3.16 - armv7: ghcr.io/home-assistant/armv7-base-python:3.10-alpine3.16 - amd64: ghcr.io/home-assistant/amd64-base-python:3.10-alpine3.16 - i386: ghcr.io/home-assistant/i386-base-python:3.10-alpine3.16 + aarch64: ghcr.io/home-assistant/aarch64-base-python:3.11-alpine3.16 + armhf: ghcr.io/home-assistant/armhf-base-python:3.11-alpine3.16 + armv7: ghcr.io/home-assistant/armv7-base-python:3.11-alpine3.16 + amd64: ghcr.io/home-assistant/amd64-base-python:3.11-alpine3.16 + i386: ghcr.io/home-assistant/i386-base-python:3.11-alpine3.16 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io diff --git a/requirements.txt b/requirements.txt index 1c689d765..8cf614235 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ atomicwrites-homeassistant==1.4.1 attrs==23.1.0 awesomeversion==23.5.0 brotli==1.0.9 -cchardet==2.1.7 ciso8601==2.3.0 colorlog==6.7.0 cpe==1.2.1 @@ -14,6 +13,7 @@ debugpy==1.6.7 deepmerge==1.1.0 dirhash==0.2.1 docker==6.1.2 +faust-cchardet==2.1.18 gitpython==3.1.31 jinja2==3.1.2 pulsectl==23.5.1 diff --git a/supervisor/addons/__init__.py b/supervisor/addons/__init__.py index ac12591b0..bedc597f1 100644 --- a/supervisor/addons/__init__.py +++ b/supervisor/addons/__init__.py @@ -79,7 +79,7 @@ class AddonManager(CoreSysAttributes): tasks = [] for slug in self.data.system: addon = self.local[slug] = Addon(self.coresys, slug) - tasks.append(addon.load()) + tasks.append(self.sys_create_task(addon.load())) # Run initial tasks _LOGGER.info("Found %d installed add-ons", len(tasks)) diff --git a/supervisor/api/ingress.py b/supervisor/api/ingress.py index 4197d6fd2..66cd8496b 100644 --- a/supervisor/api/ingress.py +++ b/supervisor/api/ingress.py @@ -148,8 +148,8 @@ class APIIngress(CoreSysAttributes): # Proxy requests await asyncio.wait( [ - _websocket_forward(ws_server, ws_client), - _websocket_forward(ws_client, ws_server), + self.sys_create_task(_websocket_forward(ws_server, ws_client)), + self.sys_create_task(_websocket_forward(ws_client, ws_server)), ], return_when=asyncio.FIRST_COMPLETED, ) diff --git a/supervisor/backups/manager.py b/supervisor/backups/manager.py index a60ed5f8f..20358e303 100644 --- a/supervisor/backups/manager.py +++ b/supervisor/backups/manager.py @@ -117,7 +117,7 @@ class BackupManager(FileConfiguration, CoreSysAttributes): self._backups[backup.slug] = backup tasks = [ - _load_backup(tar_file) + self.sys_create_task(_load_backup(tar_file)) for path in self.backup_locations for tar_file in path.glob("*.tar") ] diff --git a/supervisor/core.py b/supervisor/core.py index 6ee1848f6..f8e62321c 100644 --- a/supervisor/core.py +++ b/supervisor/core.py @@ -285,9 +285,12 @@ class Core(CoreSysAttributes): async with async_timeout.timeout(10): await asyncio.wait( [ - self.sys_api.stop(), - self.sys_scheduler.shutdown(), - self.sys_docker.unload(), + self.sys_create_task(coro) + for coro in ( + self.sys_api.stop(), + self.sys_scheduler.shutdown(), + self.sys_docker.unload(), + ) ] ) except asyncio.TimeoutError: @@ -298,10 +301,13 @@ class Core(CoreSysAttributes): async with async_timeout.timeout(10): await asyncio.wait( [ - self.sys_websession.close(), - self.sys_ingress.unload(), - self.sys_hardware.unload(), - self.sys_dbus.unload(), + self.sys_create_task(coro) + for coro in ( + self.sys_websession.close(), + self.sys_ingress.unload(), + self.sys_hardware.unload(), + self.sys_dbus.unload(), + ) ] ) except asyncio.TimeoutError: diff --git a/supervisor/homeassistant/module.py b/supervisor/homeassistant/module.py index 0962c3e99..2629530b2 100644 --- a/supervisor/homeassistant/module.py +++ b/supervisor/homeassistant/module.py @@ -258,7 +258,12 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes): async def load(self) -> None: """Prepare Home Assistant object.""" - await asyncio.wait([self.secrets.load(), self.core.load()]) + await asyncio.wait( + [ + self.sys_create_task(self.secrets.load()), + self.sys_create_task(self.core.load()), + ] + ) # Register for events self.sys_bus.register_event(BusEvent.HARDWARE_NEW_DEVICE, self._hardware_events) diff --git a/supervisor/mounts/manager.py b/supervisor/mounts/manager.py index 549c9e885..2d9b29d18 100644 --- a/supervisor/mounts/manager.py +++ b/supervisor/mounts/manager.py @@ -109,11 +109,18 @@ class MountManager(FileConfiguration, CoreSysAttributes): # Bind all media mounts to directories in media if self.media_mounts: - await asyncio.wait([self._bind_media(mount) for mount in self.media_mounts]) + await asyncio.wait( + [ + self.sys_create_task(self._bind_media(mount)) + for mount in self.media_mounts + ] + ) async def reload(self) -> None: """Update mounts info via dbus and reload failed mounts.""" - await asyncio.wait([mount.update() for mount in self.mounts]) + await asyncio.wait( + [self.sys_create_task(mount.update()) for mount in self.mounts] + ) # Try to reload any newly failed mounts and report issues if failure persists new_failures = [ diff --git a/supervisor/plugins/manager.py b/supervisor/plugins/manager.py index fa6bd7f00..bfbd462e8 100644 --- a/supervisor/plugins/manager.py +++ b/supervisor/plugins/manager.py @@ -107,7 +107,9 @@ class PluginManager(CoreSysAttributes): async def repair(self) -> None: """Repair Supervisor plugins.""" - await asyncio.wait([plugin.repair() for plugin in self.all_plugins]) + await asyncio.wait( + [self.sys_create_task(plugin.repair()) for plugin in self.all_plugins] + ) async def shutdown(self) -> None: """Shutdown Supervisor plugin.""" diff --git a/supervisor/store/__init__.py b/supervisor/store/__init__.py index 1ef812adb..18a1e8e8b 100644 --- a/supervisor/store/__init__.py +++ b/supervisor/store/__init__.py @@ -83,7 +83,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration): @Job(conditions=[JobCondition.SUPERVISOR_UPDATED], on_condition=StoreJobError) async def reload(self) -> None: """Update add-ons from repository and reload list.""" - tasks = [repository.update() for repository in self.all] + tasks = [self.sys_create_task(repository.update()) for repository in self.all] if tasks: await asyncio.wait(tasks) diff --git a/supervisor/updater.py b/supervisor/updater.py index c4e8998dc..3959a88e0 100644 --- a/supervisor/updater.py +++ b/supervisor/updater.py @@ -191,7 +191,7 @@ class Updater(FileConfiguration, CoreSysAttributes): Is a coroutine. """ - url = URL_HASSIO_VERSION.format(channel=self.channel) + url = URL_HASSIO_VERSION.format(channel=self.channel.value) machine = self.sys_machine or "default" # Get data diff --git a/supervisor/utils/codenotary.py b/supervisor/utils/codenotary.py index ddb963b7b..9b116f6c4 100644 --- a/supervisor/utils/codenotary.py +++ b/supervisor/utils/codenotary.py @@ -69,14 +69,14 @@ async def cas_validate( async with async_timeout.timeout(15): data, error = await proc.communicate() - except OSError as err: - raise CodeNotaryError( - f"CodeNotary fatal error: {err!s}", _LOGGER.critical - ) from err except asyncio.TimeoutError: raise CodeNotaryBackendError( "Timeout while processing CodeNotary", _LOGGER.warning ) from None + except OSError as err: + raise CodeNotaryError( + f"CodeNotary fatal error: {err!s}", _LOGGER.critical + ) from err # Check if Notarized if proc.returncode != 0 and not data: diff --git a/tests/addons/test_manager.py b/tests/addons/test_manager.py index c5a02a187..02d2caf19 100644 --- a/tests/addons/test_manager.py +++ b/tests/addons/test_manager.py @@ -18,6 +18,7 @@ from supervisor.exceptions import ( DockerAPIError, DockerNotFound, ) +from supervisor.plugins.dns import PluginDns from supervisor.utils import check_exception_chain from tests.common import load_json_fixture @@ -164,3 +165,20 @@ async def test_addon_uninstall_removes_discovery( assert coresys.addons.installed == [] assert coresys.discovery.list_messages == [] + + +async def test_load( + coresys: CoreSys, install_addon_ssh: Addon, caplog: pytest.LogCaptureFixture +): + """Test addon manager load.""" + caplog.clear() + + with patch.object(DockerInterface, "attach") as attach, patch.object( + PluginDns, "write_hosts" + ) as write_hosts: + await coresys.addons.load() + + attach.assert_called_once_with(version=AwesomeVersion("9.2.1")) + write_hosts.assert_called_once() + + assert "Found 1 installed add-ons" in caplog.text diff --git a/tests/homeassistant/test_module.py b/tests/homeassistant/test_module.py new file mode 100644 index 000000000..090229771 --- /dev/null +++ b/tests/homeassistant/test_module.py @@ -0,0 +1,20 @@ +"""Test Homeassistant module.""" + +from pathlib import Path +from unittest.mock import patch + +from supervisor.coresys import CoreSys +from supervisor.docker.interface import DockerInterface + + +async def test_load(coresys: CoreSys, tmp_supervisor_data: Path): + """Test homeassistant module load.""" + with open(tmp_supervisor_data / "homeassistant" / "secrets.yaml", "w") as secrets: + secrets.write("hello: world\n") + + with patch.object(DockerInterface, "attach") as attach: + await coresys.homeassistant.load() + + attach.assert_called_once() + + assert coresys.homeassistant.secrets.secrets == {"hello": "world"} diff --git a/tests/plugins/test_plugin_manager.py b/tests/plugins/test_plugin_manager.py new file mode 100644 index 000000000..46c3d75a0 --- /dev/null +++ b/tests/plugins/test_plugin_manager.py @@ -0,0 +1,31 @@ +"""Test plugin manager.""" + +from unittest.mock import patch + +from supervisor.coresys import CoreSys +from supervisor.docker.interface import DockerInterface + + +def mock_awaitable_bool(value: bool): + """Return a mock of an awaitable bool.""" + + async def _mock_bool(*args, **kwargs) -> bool: + return value + + return _mock_bool + + +async def test_repair(coresys: CoreSys): + """Test repair.""" + with patch.object(DockerInterface, "install") as install: + # If instance exists, repair does nothing + with patch.object(DockerInterface, "exists", new=mock_awaitable_bool(True)): + await coresys.plugins.repair() + + install.assert_not_called() + + # If not, repair installs the image + with patch.object(DockerInterface, "exists", new=mock_awaitable_bool(False)): + await coresys.plugins.repair() + + assert install.call_count == len(coresys.plugins.all_plugins) diff --git a/tests/store/test_store_manager.py b/tests/store/test_store_manager.py index d7def10dc..2348440bb 100644 --- a/tests/store/test_store_manager.py +++ b/tests/store/test_store_manager.py @@ -15,6 +15,7 @@ from supervisor.exceptions import AddonsNotSupportedError, StoreJobError from supervisor.homeassistant.module import HomeAssistant from supervisor.store import StoreManager from supervisor.store.addon import AddonStore +from supervisor.store.git import GitRepo from supervisor.store.repository import Repository from tests.common import load_yaml_fixture @@ -231,3 +232,14 @@ async def test_install_unavailable_addon( await coresys.addons.install("local_ssh") assert log in caplog.text + + +async def test_reload(coresys: CoreSys): + """Test store reload.""" + await coresys.store.load() + assert len(coresys.store.all) == 4 + + with patch.object(GitRepo, "pull") as git_pull: + await coresys.store.reload() + + assert git_pull.call_count == 3