Update python to 3.12 (#4815)

* Update python to 3.12

* Fix tests and deprecations

* Fix other references to 3.11

* build.json doesn't exist
This commit is contained in:
Mike Degatano 2024-01-13 10:35:07 -05:00 committed by GitHub
parent 2a29b801a4
commit 2da27937a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 64 additions and 66 deletions

View File

@ -29,7 +29,7 @@
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"python.pythonPath": "/usr/local/bin/python3", "python.pythonPath": "/usr/local/bin/python3",
"python.formatting.provider": "black", "python.formatting.provider": "black",
"python.formatting.blackArgs": ["--target-version", "py311"], "python.formatting.blackArgs": ["--target-version", "py312"],
"python.formatting.blackPath": "/usr/local/bin/black" "python.formatting.blackPath": "/usr/local/bin/black"
} }
} }

View File

@ -33,7 +33,7 @@ on:
- setup.py - setup.py
env: env:
DEFAULT_PYTHON: "3.11" DEFAULT_PYTHON: "3.12"
BUILD_NAME: supervisor BUILD_NAME: supervisor
BUILD_TYPE: supervisor BUILD_TYPE: supervisor
@ -75,7 +75,7 @@ jobs:
- name: Check if requirements files changed - name: Check if requirements files changed
id: requirements id: requirements
run: | run: |
if [[ "${{ steps.changed_files.outputs.all }}" =~ (requirements.txt|build.json) ]]; then if [[ "${{ steps.changed_files.outputs.all }}" =~ (requirements.txt|build.yaml) ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT" echo "changed=true" >> "$GITHUB_OUTPUT"
fi fi
@ -108,7 +108,7 @@ jobs:
if: needs.init.outputs.requirements == 'true' if: needs.init.outputs.requirements == 'true'
uses: home-assistant/wheels@2024.01.0 uses: home-assistant/wheels@2024.01.0
with: with:
abi: cp311 abi: cp312
tag: musllinux_1_2 tag: musllinux_1_2
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@ -8,7 +8,7 @@ on:
pull_request: ~ pull_request: ~
env: env:
DEFAULT_PYTHON: "3.11" DEFAULT_PYTHON: "3.12"
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
concurrency: concurrency:
@ -88,7 +88,7 @@ jobs:
- name: Run black - name: Run black
run: | run: |
. venv/bin/activate . venv/bin/activate
black --target-version py311 --check supervisor tests setup.py black --target-version py312 --check supervisor tests setup.py
lint-dockerfile: lint-dockerfile:
name: Check Dockerfile name: Check Dockerfile

View File

@ -1,16 +1,16 @@
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 23.12.1
hooks: hooks:
- id: black - id: black
args: args:
- --safe - --safe
- --quiet - --quiet
- --target-version - --target-version
- py311 - py312
files: ^((supervisor|tests)/.+)?[^/]+\.py$ files: ^((supervisor|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 7.0.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
@ -18,17 +18,17 @@ repos:
- pydocstyle==6.3.0 - pydocstyle==6.3.0
files: ^(supervisor|script|tests)/.+\.py$ files: ^(supervisor|script|tests)/.+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0 rev: v4.5.0
hooks: hooks:
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
stages: [manual] stages: [manual]
- id: check-json - id: check-json
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.12.0 rev: 5.13.2
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.15.0 rev: v3.15.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py311-plus] args: [--py312-plus]

View File

@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-hassio-supervisor image: ghcr.io/home-assistant/{arch}-hassio-supervisor
build_from: build_from:
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.11-alpine3.18 aarch64: ghcr.io/home-assistant/aarch64-base-python:3.12-alpine3.18
armhf: ghcr.io/home-assistant/armhf-base-python:3.11-alpine3.18 armhf: ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.18
armv7: ghcr.io/home-assistant/armv7-base-python:3.11-alpine3.18 armv7: ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.18
amd64: ghcr.io/home-assistant/amd64-base-python:3.11-alpine3.18 amd64: ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.18
i386: ghcr.io/home-assistant/i386-base-python:3.11-alpine3.18 i386: ghcr.io/home-assistant/i386-base-python:3.12-alpine3.18
codenotary: codenotary:
signer: notary@home-assistant.io signer: notary@home-assistant.io
base_image: notary@home-assistant.io base_image: notary@home-assistant.io

View File

@ -12,7 +12,7 @@ authors = [
{ name = "The Home Assistant Authors", email = "hello@home-assistant.io" }, { name = "The Home Assistant Authors", email = "hello@home-assistant.io" },
] ]
keywords = ["docker", "home-assistant", "api"] keywords = ["docker", "home-assistant", "api"]
requires-python = ">=3.11.0" requires-python = ">=3.12.0"
[project.urls] [project.urls]
"Homepage" = "https://www.home-assistant.io/" "Homepage" = "https://www.home-assistant.io/"

View File

@ -6,7 +6,7 @@ pre-commit==3.6.0
pydocstyle==6.3.0 pydocstyle==6.3.0
pylint==3.0.3 pylint==3.0.3
pytest-aiohttp==1.0.5 pytest-aiohttp==1.0.5
pytest-asyncio==0.18.3 pytest-asyncio==0.23.3
pytest-cov==4.1.0 pytest-cov==4.1.0
pytest-timeout==2.2.0 pytest-timeout==2.2.0
pytest==7.4.4 pytest==7.4.4

View File

@ -1155,7 +1155,11 @@ class Addon(AddonModel):
def _extract_tarfile(): def _extract_tarfile():
"""Extract tar backup.""" """Extract tar backup."""
with tar_file as backup: with tar_file as backup:
backup.extractall(path=Path(temp), members=secure_path(backup)) backup.extractall(
path=Path(temp),
members=secure_path(backup),
filter="fully_trusted",
)
try: try:
await self.sys_run_in_executor(_extract_tarfile) await self.sys_run_in_executor(_extract_tarfile)

View File

@ -315,7 +315,11 @@ class Backup(CoreSysAttributes):
def _extract_backup(): def _extract_backup():
"""Extract a backup.""" """Extract a backup."""
with tarfile.open(self.tarfile, "r:") as tar: with tarfile.open(self.tarfile, "r:") as tar:
tar.extractall(path=self._tmp.name, members=secure_path(tar)) tar.extractall(
path=self._tmp.name,
members=secure_path(tar),
filter="fully_trusted",
)
await self.sys_run_in_executor(_extract_backup) await self.sys_run_in_executor(_extract_backup)
@ -535,7 +539,9 @@ class Backup(CoreSysAttributes):
gzip=self.compressed, gzip=self.compressed,
bufsize=BUF_SIZE, bufsize=BUF_SIZE,
) as tar_file: ) as tar_file:
tar_file.extractall(path=origin_dir, members=tar_file) tar_file.extractall(
path=origin_dir, members=tar_file, filter="fully_trusted"
)
_LOGGER.info("Restore folder %s done", name) _LOGGER.info("Restore folder %s done", name)
except (tarfile.TarError, OSError) as err: except (tarfile.TarError, OSError) as err:
_LOGGER.warning("Can't restore folder %s: %s", name, err) _LOGGER.warning("Can't restore folder %s: %s", name, err)

View File

@ -1,5 +1,5 @@
"""Bootstrap Supervisor.""" """Bootstrap Supervisor."""
from datetime import datetime from datetime import UTC, datetime
import logging import logging
import os import os
from pathlib import Path, PurePath from pathlib import Path, PurePath
@ -50,7 +50,7 @@ MOUNTS_CREDENTIALS = PurePath(".mounts_credentials")
EMERGENCY_DATA = PurePath("emergency") EMERGENCY_DATA = PurePath("emergency")
ADDON_CONFIGS = PurePath("addon_configs") ADDON_CONFIGS = PurePath("addon_configs")
DEFAULT_BOOT_TIME = datetime.utcfromtimestamp(0).isoformat() DEFAULT_BOOT_TIME = datetime.fromtimestamp(0, UTC).isoformat()
# We filter out UTC because it's the system default fallback # We filter out UTC because it's the system default fallback
# Core also not respect the cotnainer timezone and reset timezones # Core also not respect the cotnainer timezone and reset timezones
@ -164,7 +164,7 @@ class CoreConfig(FileConfiguration):
boot_time = parse_datetime(boot_str) boot_time = parse_datetime(boot_str)
if not boot_time: if not boot_time:
return datetime.utcfromtimestamp(1) return datetime.fromtimestamp(1, UTC)
return boot_time return boot_time
@last_boot.setter @last_boot.setter

View File

@ -1,5 +1,5 @@
"""Read hardware info from system.""" """Read hardware info from system."""
from datetime import datetime from datetime import UTC, datetime
import logging import logging
from pathlib import Path from pathlib import Path
import re import re
@ -55,7 +55,7 @@ class HwHelper(CoreSysAttributes):
_LOGGER.error("Can't found last boot time!") _LOGGER.error("Can't found last boot time!")
return None return None
return datetime.utcfromtimestamp(int(found.group(1))) return datetime.fromtimestamp(int(found.group(1)), UTC)
def hide_virtual_device(self, udev_device: pyudev.Device) -> bool: def hide_virtual_device(self, udev_device: pyudev.Device) -> bool:
"""Small helper to hide not needed Devices.""" """Small helper to hide not needed Devices."""

View File

@ -411,7 +411,11 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes):
def _extract_tarfile(): def _extract_tarfile():
"""Extract tar backup.""" """Extract tar backup."""
with tar_file as backup: with tar_file as backup:
backup.extractall(path=temp_path, members=secure_path(backup)) backup.extractall(
path=temp_path,
members=secure_path(backup),
filter="fully_trusted",
)
try: try:
await self.sys_run_in_executor(_extract_tarfile) await self.sys_run_in_executor(_extract_tarfile)

View File

@ -1,15 +1,12 @@
"""Tools file for Supervisor.""" """Tools file for Supervisor."""
from contextlib import suppress from contextlib import suppress
from datetime import datetime, timedelta, timezone, tzinfo from datetime import UTC, datetime, timedelta, timezone, tzinfo
import re import re
from typing import Any from typing import Any
import zoneinfo import zoneinfo
import ciso8601 import ciso8601
UTC = timezone.utc
# Copyright (c) Django Software Foundation and individual contributors. # Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved. # All rights reserved.
# https://github.com/django/django/blob/master/LICENSE # https://github.com/django/django/blob/master/LICENSE
@ -67,7 +64,7 @@ def utcnow() -> datetime:
def utc_from_timestamp(timestamp: float) -> datetime: def utc_from_timestamp(timestamp: float) -> datetime:
"""Return a UTC time from a timestamp.""" """Return a UTC time from a timestamp."""
return datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC) return datetime.fromtimestamp(timestamp, UTC).replace(tzinfo=UTC)
def get_time_zone(time_zone_str: str) -> tzinfo | None: def get_time_zone(time_zone_str: str) -> tzinfo | None:

View File

@ -110,7 +110,6 @@ async def test_bad_requests(
fail_on_query_string, fail_on_query_string,
api_system, api_system,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
event_loop: asyncio.BaseEventLoop,
) -> None: ) -> None:
"""Test request paths that should be filtered.""" """Test request paths that should be filtered."""
@ -122,7 +121,7 @@ async def test_bad_requests(
man_params = "" man_params = ""
http = urllib3.PoolManager() http = urllib3.PoolManager()
resp = await event_loop.run_in_executor( resp = await asyncio.get_running_loop().run_in_executor(
None, None,
http.request, http.request,
"GET", "GET",

View File

@ -293,7 +293,6 @@ async def fixture_all_dbus_services(
@pytest.fixture @pytest.fixture
async def coresys( async def coresys(
event_loop,
docker, docker,
dbus_session_bus, dbus_session_bus,
all_dbus_services, all_dbus_services,

View File

@ -274,9 +274,7 @@ async def test_exception_conditions(coresys: CoreSys):
await test.execute() await test.execute()
async def test_execution_limit_single_wait( async def test_execution_limit_single_wait(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the single wait job execution limit.""" """Test the single wait job execution limit."""
class TestClass: class TestClass:
@ -302,9 +300,7 @@ async def test_execution_limit_single_wait(
await asyncio.gather(*[test.execute(0.1), test.execute(0.1), test.execute(0.1)]) await asyncio.gather(*[test.execute(0.1), test.execute(0.1), test.execute(0.1)])
async def test_execution_limit_throttle_wait( async def test_execution_limit_throttle_wait(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the throttle wait job execution limit.""" """Test the throttle wait job execution limit."""
class TestClass: class TestClass:
@ -339,7 +335,7 @@ async def test_execution_limit_throttle_wait(
@pytest.mark.parametrize("error", [None, PluginJobError]) @pytest.mark.parametrize("error", [None, PluginJobError])
async def test_execution_limit_throttle_rate_limit( async def test_execution_limit_throttle_rate_limit(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop, error: JobException | None coresys: CoreSys, error: JobException | None
): ):
"""Test the throttle wait job execution limit.""" """Test the throttle wait job execution limit."""
@ -379,9 +375,7 @@ async def test_execution_limit_throttle_rate_limit(
assert test.call == 3 assert test.call == 3
async def test_execution_limit_throttle( async def test_execution_limit_throttle(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the ignore conditions decorator.""" """Test the ignore conditions decorator."""
class TestClass: class TestClass:
@ -414,9 +408,7 @@ async def test_execution_limit_throttle(
assert test.call == 1 assert test.call == 1
async def test_execution_limit_once( async def test_execution_limit_once(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the ignore conditions decorator.""" """Test the ignore conditions decorator."""
class TestClass: class TestClass:
@ -439,7 +431,7 @@ async def test_execution_limit_once(
await asyncio.sleep(sleep) await asyncio.sleep(sleep)
test = TestClass(coresys) test = TestClass(coresys)
run_task = event_loop.create_task(test.execute(0.3)) run_task = asyncio.get_running_loop().create_task(test.execute(0.3))
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
with pytest.raises(JobException): with pytest.raises(JobException):
@ -595,7 +587,7 @@ async def test_host_network(coresys: CoreSys):
assert await test.execute() assert await test.execute()
async def test_job_group_once(coresys: CoreSys, event_loop: asyncio.BaseEventLoop): async def test_job_group_once(coresys: CoreSys):
"""Test job group once execution limitation.""" """Test job group once execution limitation."""
class TestClass(JobGroup): class TestClass(JobGroup):
@ -644,7 +636,7 @@ async def test_job_group_once(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
return True return True
test = TestClass(coresys) test = TestClass(coresys)
run_task = event_loop.create_task(test.execute()) run_task = asyncio.get_running_loop().create_task(test.execute())
await asyncio.sleep(0) await asyncio.sleep(0)
# All methods with group limits should be locked # All methods with group limits should be locked
@ -664,7 +656,7 @@ async def test_job_group_once(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
assert await run_task assert await run_task
async def test_job_group_wait(coresys: CoreSys, event_loop: asyncio.BaseEventLoop): async def test_job_group_wait(coresys: CoreSys):
"""Test job group wait execution limitation.""" """Test job group wait execution limitation."""
class TestClass(JobGroup): class TestClass(JobGroup):
@ -706,6 +698,7 @@ async def test_job_group_wait(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
self.other_count += 1 self.other_count += 1
test = TestClass(coresys) test = TestClass(coresys)
event_loop = asyncio.get_running_loop()
run_task = event_loop.create_task(test.execute()) run_task = event_loop.create_task(test.execute())
await asyncio.sleep(0) await asyncio.sleep(0)
@ -725,7 +718,7 @@ async def test_job_group_wait(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
assert test.other_count == 1 assert test.other_count == 1
async def test_job_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop): async def test_job_cleanup(coresys: CoreSys):
"""Test job is cleaned up.""" """Test job is cleaned up."""
class TestClass: class TestClass:
@ -745,7 +738,7 @@ async def test_job_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop):
return True return True
test = TestClass(coresys) test = TestClass(coresys)
run_task = event_loop.create_task(test.execute()) run_task = asyncio.get_running_loop().create_task(test.execute())
await asyncio.sleep(0) await asyncio.sleep(0)
assert coresys.jobs.jobs == [test.job] assert coresys.jobs.jobs == [test.job]
@ -758,7 +751,7 @@ async def test_job_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop):
assert test.job.done assert test.job.done
async def test_job_skip_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop): async def test_job_skip_cleanup(coresys: CoreSys):
"""Test job is left in job manager when cleanup is false.""" """Test job is left in job manager when cleanup is false."""
class TestClass: class TestClass:
@ -782,7 +775,7 @@ async def test_job_skip_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventL
return True return True
test = TestClass(coresys) test = TestClass(coresys)
run_task = event_loop.create_task(test.execute()) run_task = asyncio.get_running_loop().create_task(test.execute())
await asyncio.sleep(0) await asyncio.sleep(0)
assert coresys.jobs.jobs == [test.job] assert coresys.jobs.jobs == [test.job]
@ -795,9 +788,7 @@ async def test_job_skip_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventL
assert test.job.done assert test.job.done
async def test_execution_limit_group_throttle( async def test_execution_limit_group_throttle(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the group throttle execution limit.""" """Test the group throttle execution limit."""
class TestClass(JobGroup): class TestClass(JobGroup):
@ -844,9 +835,7 @@ async def test_execution_limit_group_throttle(
assert test2.call == 2 assert test2.call == 2
async def test_execution_limit_group_throttle_wait( async def test_execution_limit_group_throttle_wait(coresys: CoreSys):
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
"""Test the group throttle wait job execution limit.""" """Test the group throttle wait job execution limit."""
class TestClass(JobGroup): class TestClass(JobGroup):
@ -897,7 +886,7 @@ async def test_execution_limit_group_throttle_wait(
@pytest.mark.parametrize("error", [None, PluginJobError]) @pytest.mark.parametrize("error", [None, PluginJobError])
async def test_execution_limit_group_throttle_rate_limit( async def test_execution_limit_group_throttle_rate_limit(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop, error: JobException | None coresys: CoreSys, error: JobException | None
): ):
"""Test the group throttle rate limit job execution limit.""" """Test the group throttle rate limit job execution limit."""

View File

@ -21,4 +21,4 @@ commands =
[testenv:black] [testenv:black]
basepython = python3 basepython = python3
commands = commands =
black --target-version py311 --check supervisor tests setup.py black --target-version py312 --check supervisor tests setup.py