From b7412b0679f77f459efc3b06641b6775db594817 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 21 Jan 2025 11:57:30 +0100 Subject: [PATCH] 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 --- .devcontainer/devcontainer.json | 7 +++++-- .github/workflows/builder.yml | 2 +- .github/workflows/ci.yaml | 2 +- .pre-commit-config.yaml | 4 ++-- build.yaml | 10 +++++----- pyproject.toml | 9 +++++---- requirements_tests.txt | 2 +- supervisor/api/host.py | 4 ++-- supervisor/utils/systemd_journal.py | 2 +- tests/api/test_proxy.py | 2 +- tests/conftest.py | 2 +- tests/dbus_service_mocks/base.py | 3 +-- tests/utils/test_systemd_journal.py | 15 +++++---------- 13 files changed, 31 insertions(+), 33 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 45bb7ffcc..0dfbbb941 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Supervisor dev", - "image": "ghcr.io/home-assistant/devcontainer:supervisor", + "image": "ghcr.io/home-assistant/devcontainer:2-supervisor", "containerEnv": { "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" + ] } diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 9c6c04150..c01301879 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -33,7 +33,7 @@ on: - setup.py env: - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.13" BUILD_NAME: supervisor BUILD_TYPE: supervisor diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 640784b49..52c3b83e8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ on: pull_request: ~ env: - DEFAULT_PYTHON: "3.12" + DEFAULT_PYTHON: "3.13" PRE_COMMIT_CACHE: ~/.cache/pre-commit concurrency: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f963fe64b..58a805704 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.9.1 hooks: - id: ruff args: @@ -8,7 +8,7 @@ repos: - id: ruff-format files: ^((supervisor|tests)/.+)?[^/]+\.py$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-executables-have-shebangs stages: [manual] diff --git a/build.yaml b/build.yaml index 4678df0a1..e6aec407e 100644 --- a/build.yaml +++ b/build.yaml @@ -1,10 +1,10 @@ image: ghcr.io/home-assistant/{arch}-hassio-supervisor build_from: - aarch64: ghcr.io/home-assistant/aarch64-base-python:3.12-alpine3.20 - armhf: ghcr.io/home-assistant/armhf-base-python:3.12-alpine3.20 - armv7: ghcr.io/home-assistant/armv7-base-python:3.12-alpine3.20 - amd64: ghcr.io/home-assistant/amd64-base-python:3.12-alpine3.20 - i386: ghcr.io/home-assistant/i386-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.13-alpine3.21 + armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.21 + amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21 + i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.21 codenotary: signer: notary@home-assistant.io base_image: notary@home-assistant.io diff --git a/pyproject.toml b/pyproject.toml index 06349071f..f63723386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ { name = "The Home Assistant Authors", email = "hello@home-assistant.io" }, ] keywords = ["docker", "home-assistant", "api"] -requires-python = ">=3.12.0" +requires-python = ">=3.13.0" [project.urls] "Homepage" = "https://www.home-assistant.io/" @@ -31,7 +31,7 @@ include-package-data = true include = ["supervisor*"] [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 # any too bad. Override on command line as appropriate. jobs = 2 @@ -147,7 +147,7 @@ disable = [ # "pointless-statement", # B018, ruff catches new occurrences, needs more work "raise-missing-from", # TRY200 # "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-format-string-argument", #F507 "unused-format-string-key", # F504 @@ -223,6 +223,7 @@ testpaths = ["tests"] norecursedirs = [".git"] 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" +asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" filterwarnings = [ "error", @@ -289,7 +290,7 @@ lint.select = [ "T20", # flake8-print "TID251", # Banned imports "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 "W", # pycodestyle ] diff --git a/requirements_tests.txt b/requirements_tests.txt index aa149dd9d..41d5d995b 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -3,7 +3,7 @@ coverage==7.6.10 pre-commit==4.1.0 pylint==3.3.3 pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.6 +pytest-asyncio==0.25.2 pytest-cov==6.0.0 pytest-timeout==2.3.1 pytest==8.3.4 diff --git a/supervisor/api/host.py b/supervisor/api/host.py index 5a67d3b45..7d2cc6650 100644 --- a/supervisor/api/host.py +++ b/supervisor/api/host.py @@ -239,12 +239,12 @@ class APIHost(CoreSysAttributes): # return 2 lines at minimum. lines = max(2, lines) # 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: range_header = request.headers.get(RANGE) else: 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( diff --git a/supervisor/utils/systemd_journal.py b/supervisor/utils/systemd_journal.py index 5898d8115..00a8d7b85 100644 --- a/supervisor/utils/systemd_journal.py +++ b/supervisor/utils/systemd_journal.py @@ -61,7 +61,7 @@ def journal_verbose_formatter(entries: dict[str, str]) -> str: async def journal_logs_reader( 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. Returns a generator of (cursor, formatted_entry) tuples. diff --git a/tests/api/test_proxy.py b/tests/api/test_proxy.py index 0da8675cc..033585a2a 100644 --- a/tests/api/test_proxy.py +++ b/tests/api/test_proxy.py @@ -18,7 +18,7 @@ from supervisor.api.proxy import APIProxy from supervisor.const import ATTR_ACCESS_TOKEN -def id_generator() -> Generator[int, None, None]: +def id_generator() -> Generator[int]: """Generate IDs for WS messages.""" i = 0 while True: diff --git a/tests/conftest.py b/tests/conftest.py index 23fd12b67..1046773ff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -117,7 +117,7 @@ async def docker() -> DockerAPI: @pytest.fixture(scope="session") -def dbus_session() -> Generator[str, None, None]: +def dbus_session() -> Generator[str]: """Start a dbus session. Returns session address. diff --git a/tests/dbus_service_mocks/base.py b/tests/dbus_service_mocks/base.py index 41efa3fe5..ceca9be05 100644 --- a/tests/dbus_service_mocks/base.py +++ b/tests/dbus_service_mocks/base.py @@ -2,7 +2,7 @@ import asyncio from functools import wraps -from typing import Any, no_type_check_decorator +from typing import Any from dbus_fast import Message 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) - @no_type_check_decorator def decorator(func): calls: list[list[Any]] = [] diff --git a/tests/utils/test_systemd_journal.py b/tests/utils/test_systemd_journal.py index 26b21e97c..7d683c027 100644 --- a/tests/utils/test_systemd_journal.py +++ b/tests/utils/test_systemd_journal.py @@ -50,9 +50,9 @@ def test_format_verbose_timestamp(): "MESSAGE": "x", } formatted = journal_verbose_formatter(fields) - assert formatted.startswith( - "1970-01-01 00:00:00.001 " - ), f"Invalid log timestamp: {formatted}" + assert formatted.startswith("1970-01-01 00:00:00.001 "), ( + f"Invalid log timestamp: {formatted}" + ) def test_format_verbose(): @@ -143,10 +143,7 @@ async def test_parsing_two_messages(): """Test reading multiple messages.""" journal_logs, stream = _journal_logs_mock() stream.feed_data( - b"MESSAGE=Hello, world!\n" - b"ID=1\n\n" - b"MESSAGE=Hello again, world!\n" - b"ID=2\n\n" + b"MESSAGE=Hello, world!\nID=1\n\nMESSAGE=Hello again, world!\nID=2\n\n" ) stream.feed_eof() @@ -184,9 +181,7 @@ async def test_parsing_malformed_binary_message(): """Test that malformed binary message raises MalformedBinaryEntryError.""" journal_logs, stream = _journal_logs_mock() stream.feed_data( - b"ID=1\n" - b"MESSAGE\n\x0d\x00\x00\x00\x00\x00\x00\x00Hello, world!" - b"AFTER=after\n\n" + b"ID=1\nMESSAGE\n\x0d\x00\x00\x00\x00\x00\x00\x00Hello, world!AFTER=after\n\n" ) with pytest.raises(MalformedBinaryEntryError):