Update Python to 3.13 (#5564)

* Bump Supervisor to Python 3.13

* Update ruff configuration to 0.9.1

Adjust pyproject.toml for ruff 0.9.1. Also make sure that latest version
of ruff is used in pre-commit.

* Set default configuration for pytest-asyncio

* Run ruff check

* Drop deprecated decorator no_type_check_decorator

The upstream PR (https://github.com/python/cpython/issues/106309) says
this never got really implemented by type checkers.

* Bump devcontainer to latest release
This commit is contained in:
Stefan Agner 2025-01-21 11:57:30 +01:00 committed by GitHub
parent fff3bfd01e
commit b7412b0679
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 31 additions and 33 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "Supervisor dev", "name": "Supervisor dev",
"image": "ghcr.io/home-assistant/devcontainer:supervisor", "image": "ghcr.io/home-assistant/devcontainer:2-supervisor",
"containerEnv": { "containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
}, },
@ -44,5 +44,8 @@
} }
} }
}, },
"mounts": ["type=volume,target=/var/lib/docker"] "mounts": [
"type=volume,target=/var/lib/docker",
"type=volume,target=/mnt/supervisor"
]
} }

View File

@ -33,7 +33,7 @@ on:
- setup.py - setup.py
env: env:
DEFAULT_PYTHON: "3.12" DEFAULT_PYTHON: "3.13"
BUILD_NAME: supervisor BUILD_NAME: supervisor
BUILD_TYPE: supervisor BUILD_TYPE: supervisor

View File

@ -8,7 +8,7 @@ on:
pull_request: ~ pull_request: ~
env: env:
DEFAULT_PYTHON: "3.12" DEFAULT_PYTHON: "3.13"
PRE_COMMIT_CACHE: ~/.cache/pre-commit PRE_COMMIT_CACHE: ~/.cache/pre-commit
concurrency: concurrency:

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7 rev: v0.9.1
hooks: hooks:
- id: ruff - id: ruff
args: args:
@ -8,7 +8,7 @@ repos:
- id: ruff-format - id: ruff-format
files: ^((supervisor|tests)/.+)?[^/]+\.py$ files: ^((supervisor|tests)/.+)?[^/]+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v5.0.0
hooks: hooks:
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
stages: [manual] stages: [manual]

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.12-alpine3.20 aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21
armhf: ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.20 armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.21
armv7: ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.20 armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.21
amd64: ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.20 amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21
i386: ghcr.io/home-assistant/i386-base-python:3.12-alpine3.20 i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.21
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.12.0" requires-python = ">=3.13.0"
[project.urls] [project.urls]
"Homepage" = "https://www.home-assistant.io/" "Homepage" = "https://www.home-assistant.io/"
@ -31,7 +31,7 @@ include-package-data = true
include = ["supervisor*"] include = ["supervisor*"]
[tool.pylint.MAIN] [tool.pylint.MAIN]
py-version = "3.12" py-version = "3.13"
# Use a conservative default here; 2 should speed up most setups and not hurt # Use a conservative default here; 2 should speed up most setups and not hurt
# any too bad. Override on command line as appropriate. # any too bad. Override on command line as appropriate.
jobs = 2 jobs = 2
@ -147,7 +147,7 @@ disable = [
# "pointless-statement", # B018, ruff catches new occurrences, needs more work # "pointless-statement", # B018, ruff catches new occurrences, needs more work
"raise-missing-from", # TRY200 "raise-missing-from", # TRY200
# "redefined-builtin", # A001, ruff is way more stricter, needs work # "redefined-builtin", # A001, ruff is way more stricter, needs work
"try-except-raise", # TRY302 "try-except-raise", # TRY203
"unused-argument", # ARG001, we don't use it "unused-argument", # ARG001, we don't use it
"unused-format-string-argument", #F507 "unused-format-string-argument", #F507
"unused-format-string-key", # F504 "unused-format-string-key", # F504
@ -223,6 +223,7 @@ testpaths = ["tests"]
norecursedirs = [".git"] norecursedirs = [".git"]
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s" log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S" log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto" asyncio_mode = "auto"
filterwarnings = [ filterwarnings = [
"error", "error",
@ -289,7 +290,7 @@ lint.select = [
"T20", # flake8-print "T20", # flake8-print
"TID251", # Banned imports "TID251", # Banned imports
"TRY004", # Prefer TypeError exception for invalid type "TRY004", # Prefer TypeError exception for invalid type
"TRY302", # Remove exception handler; error is immediately re-raised "TRY203", # Remove exception handler; error is immediately re-raised
"UP", # pyupgrade "UP", # pyupgrade
"W", # pycodestyle "W", # pycodestyle
] ]

View File

@ -3,7 +3,7 @@ coverage==7.6.10
pre-commit==4.1.0 pre-commit==4.1.0
pylint==3.3.3 pylint==3.3.3
pytest-aiohttp==1.0.5 pytest-aiohttp==1.0.5
pytest-asyncio==0.23.6 pytest-asyncio==0.25.2
pytest-cov==6.0.0 pytest-cov==6.0.0
pytest-timeout==2.3.1 pytest-timeout==2.3.1
pytest==8.3.4 pytest==8.3.4

View File

@ -239,12 +239,12 @@ class APIHost(CoreSysAttributes):
# return 2 lines at minimum. # return 2 lines at minimum.
lines = max(2, lines) lines = max(2, lines)
# entries=cursor[[:num_skip]:num_entries] # entries=cursor[[:num_skip]:num_entries]
range_header = f"entries=:-{lines-1}:{'' if follow else lines}" range_header = f"entries=:-{lines - 1}:{'' if follow else lines}"
elif RANGE in request.headers: elif RANGE in request.headers:
range_header = request.headers.get(RANGE) range_header = request.headers.get(RANGE)
else: else:
range_header = ( range_header = (
f"entries=:-{DEFAULT_LINES-1}:{'' if follow else DEFAULT_LINES}" f"entries=:-{DEFAULT_LINES - 1}:{'' if follow else DEFAULT_LINES}"
) )
async with self.sys_host.logs.journald_logs( async with self.sys_host.logs.journald_logs(

View File

@ -61,7 +61,7 @@ def journal_verbose_formatter(entries: dict[str, str]) -> str:
async def journal_logs_reader( async def journal_logs_reader(
journal_logs: ClientResponse, log_formatter: LogFormatter = LogFormatter.PLAIN journal_logs: ClientResponse, log_formatter: LogFormatter = LogFormatter.PLAIN
) -> AsyncGenerator[(str | None, str), None]: ) -> AsyncGenerator[str | None, str]:
"""Read logs from systemd journal line by line, formatted using the given formatter. """Read logs from systemd journal line by line, formatted using the given formatter.
Returns a generator of (cursor, formatted_entry) tuples. Returns a generator of (cursor, formatted_entry) tuples.

View File

@ -18,7 +18,7 @@ from supervisor.api.proxy import APIProxy
from supervisor.const import ATTR_ACCESS_TOKEN from supervisor.const import ATTR_ACCESS_TOKEN
def id_generator() -> Generator[int, None, None]: def id_generator() -> Generator[int]:
"""Generate IDs for WS messages.""" """Generate IDs for WS messages."""
i = 0 i = 0
while True: while True:

View File

@ -117,7 +117,7 @@ async def docker() -> DockerAPI:
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def dbus_session() -> Generator[str, None, None]: def dbus_session() -> Generator[str]:
"""Start a dbus session. """Start a dbus session.
Returns session address. Returns session address.

View File

@ -2,7 +2,7 @@
import asyncio import asyncio
from functools import wraps from functools import wraps
from typing import Any, no_type_check_decorator from typing import Any
from dbus_fast import Message from dbus_fast import Message
from dbus_fast.aio.message_bus import MessageBus from dbus_fast.aio.message_bus import MessageBus
@ -51,7 +51,6 @@ def dbus_method(name: str = None, disabled: bool = False, track_obj_path: bool =
""" """
orig_decorator = method(name=name, disabled=disabled) orig_decorator = method(name=name, disabled=disabled)
@no_type_check_decorator
def decorator(func): def decorator(func):
calls: list[list[Any]] = [] calls: list[list[Any]] = []

View File

@ -50,9 +50,9 @@ def test_format_verbose_timestamp():
"MESSAGE": "x", "MESSAGE": "x",
} }
formatted = journal_verbose_formatter(fields) formatted = journal_verbose_formatter(fields)
assert formatted.startswith( assert formatted.startswith("1970-01-01 00:00:00.001 "), (
"1970-01-01 00:00:00.001 " f"Invalid log timestamp: {formatted}"
), f"Invalid log timestamp: {formatted}" )
def test_format_verbose(): def test_format_verbose():
@ -143,10 +143,7 @@ async def test_parsing_two_messages():
"""Test reading multiple messages.""" """Test reading multiple messages."""
journal_logs, stream = _journal_logs_mock() journal_logs, stream = _journal_logs_mock()
stream.feed_data( stream.feed_data(
b"MESSAGE=Hello, world!\n" b"MESSAGE=Hello, world!\nID=1\n\nMESSAGE=Hello again, world!\nID=2\n\n"
b"ID=1\n\n"
b"MESSAGE=Hello again, world!\n"
b"ID=2\n\n"
) )
stream.feed_eof() stream.feed_eof()
@ -184,9 +181,7 @@ async def test_parsing_malformed_binary_message():
"""Test that malformed binary message raises MalformedBinaryEntryError.""" """Test that malformed binary message raises MalformedBinaryEntryError."""
journal_logs, stream = _journal_logs_mock() journal_logs, stream = _journal_logs_mock()
stream.feed_data( stream.feed_data(
b"ID=1\n" b"ID=1\nMESSAGE\n\x0d\x00\x00\x00\x00\x00\x00\x00Hello, world!AFTER=after\n\n"
b"MESSAGE\n\x0d\x00\x00\x00\x00\x00\x00\x00Hello, world!"
b"AFTER=after\n\n"
) )
with pytest.raises(MalformedBinaryEntryError): with pytest.raises(MalformedBinaryEntryError):