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,
"python.pythonPath": "/usr/local/bin/python3",
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--target-version", "py311"],
"python.formatting.blackArgs": ["--target-version", "py312"],
"python.formatting.blackPath": "/usr/local/bin/black"
}
}

View File

@ -33,7 +33,7 @@ on:
- setup.py
env:
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.12"
BUILD_NAME: supervisor
BUILD_TYPE: supervisor
@ -75,7 +75,7 @@ jobs:
- name: Check if requirements files changed
id: requirements
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"
fi
@ -108,7 +108,7 @@ jobs:
if: needs.init.outputs.requirements == 'true'
uses: home-assistant/wheels@2024.01.0
with:
abi: cp311
abi: cp312
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}

View File

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

View File

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

View File

@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-hassio-supervisor
build_from:
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.11-alpine3.18
armhf: ghcr.io/home-assistant/armhf-base-python:3.11-alpine3.18
armv7: ghcr.io/home-assistant/armv7-base-python:3.11-alpine3.18
amd64: ghcr.io/home-assistant/amd64-base-python:3.11-alpine3.18
i386: ghcr.io/home-assistant/i386-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.12-alpine3.18
armv7: ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.18
amd64: ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.18
i386: ghcr.io/home-assistant/i386-base-python:3.12-alpine3.18
codenotary:
signer: 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" },
]
keywords = ["docker", "home-assistant", "api"]
requires-python = ">=3.11.0"
requires-python = ">=3.12.0"
[project.urls]
"Homepage" = "https://www.home-assistant.io/"

View File

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

View File

@ -1155,7 +1155,11 @@ class Addon(AddonModel):
def _extract_tarfile():
"""Extract tar 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:
await self.sys_run_in_executor(_extract_tarfile)

View File

@ -315,7 +315,11 @@ class Backup(CoreSysAttributes):
def _extract_backup():
"""Extract a backup."""
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)
@ -535,7 +539,9 @@ class Backup(CoreSysAttributes):
gzip=self.compressed,
bufsize=BUF_SIZE,
) 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)
except (tarfile.TarError, OSError) as err:
_LOGGER.warning("Can't restore folder %s: %s", name, err)

View File

@ -1,5 +1,5 @@
"""Bootstrap Supervisor."""
from datetime import datetime
from datetime import UTC, datetime
import logging
import os
from pathlib import Path, PurePath
@ -50,7 +50,7 @@ MOUNTS_CREDENTIALS = PurePath(".mounts_credentials")
EMERGENCY_DATA = PurePath("emergency")
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
# Core also not respect the cotnainer timezone and reset timezones
@ -164,7 +164,7 @@ class CoreConfig(FileConfiguration):
boot_time = parse_datetime(boot_str)
if not boot_time:
return datetime.utcfromtimestamp(1)
return datetime.fromtimestamp(1, UTC)
return boot_time
@last_boot.setter

View File

@ -1,5 +1,5 @@
"""Read hardware info from system."""
from datetime import datetime
from datetime import UTC, datetime
import logging
from pathlib import Path
import re
@ -55,7 +55,7 @@ class HwHelper(CoreSysAttributes):
_LOGGER.error("Can't found last boot time!")
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:
"""Small helper to hide not needed Devices."""

View File

@ -411,7 +411,11 @@ class HomeAssistant(FileConfiguration, CoreSysAttributes):
def _extract_tarfile():
"""Extract tar 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:
await self.sys_run_in_executor(_extract_tarfile)

View File

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

View File

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

View File

@ -293,7 +293,6 @@ async def fixture_all_dbus_services(
@pytest.fixture
async def coresys(
event_loop,
docker,
dbus_session_bus,
all_dbus_services,
@ -590,7 +589,7 @@ async def backups(
) -> list[Backup]:
"""Create and return mock backups."""
for i in range(request.param if hasattr(request, "param") else 5):
slug = f"sn{i+1}"
slug = f"sn{i + 1}"
temp_tar = Path(tmp_path, f"{slug}.tar")
with SecureTarFile(temp_tar, "w"):
pass

View File

@ -274,9 +274,7 @@ async def test_exception_conditions(coresys: CoreSys):
await test.execute()
async def test_execution_limit_single_wait(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_single_wait(coresys: CoreSys):
"""Test the single wait job execution limit."""
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)])
async def test_execution_limit_throttle_wait(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_throttle_wait(coresys: CoreSys):
"""Test the throttle wait job execution limit."""
class TestClass:
@ -339,7 +335,7 @@ async def test_execution_limit_throttle_wait(
@pytest.mark.parametrize("error", [None, PluginJobError])
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."""
@ -379,9 +375,7 @@ async def test_execution_limit_throttle_rate_limit(
assert test.call == 3
async def test_execution_limit_throttle(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_throttle(coresys: CoreSys):
"""Test the ignore conditions decorator."""
class TestClass:
@ -414,9 +408,7 @@ async def test_execution_limit_throttle(
assert test.call == 1
async def test_execution_limit_once(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_once(coresys: CoreSys):
"""Test the ignore conditions decorator."""
class TestClass:
@ -439,7 +431,7 @@ async def test_execution_limit_once(
await asyncio.sleep(sleep)
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)
with pytest.raises(JobException):
@ -595,7 +587,7 @@ async def test_host_network(coresys: CoreSys):
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."""
class TestClass(JobGroup):
@ -644,7 +636,7 @@ async def test_job_group_once(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
return True
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)
# 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
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."""
class TestClass(JobGroup):
@ -706,6 +698,7 @@ async def test_job_group_wait(coresys: CoreSys, event_loop: asyncio.BaseEventLoo
self.other_count += 1
test = TestClass(coresys)
event_loop = asyncio.get_running_loop()
run_task = event_loop.create_task(test.execute())
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
async def test_job_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop):
async def test_job_cleanup(coresys: CoreSys):
"""Test job is cleaned up."""
class TestClass:
@ -745,7 +738,7 @@ async def test_job_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventLoop):
return True
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)
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
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."""
class TestClass:
@ -782,7 +775,7 @@ async def test_job_skip_cleanup(coresys: CoreSys, event_loop: asyncio.BaseEventL
return True
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)
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
async def test_execution_limit_group_throttle(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_group_throttle(coresys: CoreSys):
"""Test the group throttle execution limit."""
class TestClass(JobGroup):
@ -844,9 +835,7 @@ async def test_execution_limit_group_throttle(
assert test2.call == 2
async def test_execution_limit_group_throttle_wait(
coresys: CoreSys, event_loop: asyncio.BaseEventLoop
):
async def test_execution_limit_group_throttle_wait(coresys: CoreSys):
"""Test the group throttle wait job execution limit."""
class TestClass(JobGroup):
@ -897,7 +886,7 @@ async def test_execution_limit_group_throttle_wait(
@pytest.mark.parametrize("error", [None, PluginJobError])
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."""

View File

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