Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 1 addition & 3 deletions benchmarks/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,9 +754,7 @@ def build_meta(args: argparse.Namespace) -> dict[str, Any]:
"""Metadata describing the code and machine the run was made on."""
return {
"benchmark_version": 1,
"timestamp": datetime_module.datetime.now(
datetime_module.timezone.utc
).isoformat(),
"timestamp": datetime_module.datetime.now(datetime_module.UTC).isoformat(),
"commit": git_commit(),
"label": args.label,
"python": sys.version.split()[0],
Expand Down
2 changes: 1 addition & 1 deletion docs/source/intro/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Installation Guide
Before We Begin
===============

HAP-python requires Python 3.4+.
HAP-python requires Python 3.11+.
This guide will cover the current version of Raspbian and Ubuntu LTS.
It is somewhat safe to assume the process for newer versions of Ubuntu
will work.
Expand Down
10 changes: 2 additions & 8 deletions pyhap/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import logging
import os
import struct
import sys
from types import MappingProxyType
from typing import TYPE_CHECKING, Any
from uuid import UUID
Expand All @@ -50,11 +49,6 @@
from pyhap.camera_multi_tier import MultiTierController
from pyhap.camera_webrtc import WebRTCController

if sys.version_info >= (3, 11):
from asyncio import timeout as async_timeout
else:
from async_timeout import timeout as async_timeout

SETUP_TYPES = {
"SESSION_ID": b"\x01",
"STATUS": b"\x02",
Expand Down Expand Up @@ -2263,10 +2257,10 @@ async def stop_stream(self, session_info: dict[str, Any]) -> None:
try:
with contextlib.suppress(ProcessLookupError):
ffmpeg_process.terminate()
async with async_timeout(2.0):
async with asyncio.timeout(2.0):
_, stderr = await ffmpeg_process.communicate()
logger.debug("Stream command stderr: %s", stderr)
except asyncio.TimeoutError:
except TimeoutError:
logger.error(
"Timeout while waiting for the stream process "
"to terminate. Trying with kill."
Expand Down
2 changes: 1 addition & 1 deletion pyhap/camera_multi_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ async def handle_end(self, session: MultiTierSession) -> None:
process.terminate()
try:
await asyncio.wait_for(process.wait(), 2.0)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning("[%s] Killing the stream process", session.id)
with contextlib.suppress(ProcessLookupError):
process.kill()
Expand Down
2 changes: 1 addition & 1 deletion pyhap/camera_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ async def _release(self, session: _S, hooks: list[asyncio.Task[Any]]) -> bool:
)
try:
await asyncio.wait_for(self._release_hook(session), self.hook_timeout)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning("[%s] Releasing the session timed out", session.id)
return False
# The delegate may fail in many ways, the session is gone anyway.
Expand Down
5 changes: 1 addition & 4 deletions pyhap/camera_talkback.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,7 @@ def _run(self) -> None:
try:
data, addr = self._sock.recvfrom(2048)
except TimeoutError:
# socket.timeout aliases TimeoutError from Python 3.10 on, but
# is an OSError subclass on 3.9, which this project supports,
# so catching TimeoutError here would kill the receiver thread
# on the first (0.5s) poll timeout on Python 3.9.
# The 0.5 s poll timeout, check the stop event again.
continue
except OSError:
break
Expand Down
8 changes: 1 addition & 7 deletions pyhap/hap_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from http import HTTPStatus
import inspect
import logging
import sys
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import ParseResult, parse_qs, urlparse
import uuid
Expand All @@ -33,11 +32,6 @@
from .state import State
from .util import from_hap_json, to_hap_json

if sys.version_info >= (3, 11):
from asyncio import timeout as async_timeout
else:
from async_timeout import timeout as async_timeout

if TYPE_CHECKING:
from .accessory_driver import AccessoryDriver

Expand Down Expand Up @@ -105,7 +99,7 @@ class UnprivilegedRequestException(Exception):

async def _run_with_timeout(coro: Awaitable[bytes], timeout: float) -> bytes:
"""Run a coroutine with a timeout."""
async with async_timeout(timeout):
async with asyncio.timeout(timeout):
return await coro


Expand Down
2 changes: 1 addition & 1 deletion pyhap/hds_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ async def drain(self) -> None:
"""
try:
await asyncio.wait_for(self._can_write.wait(), _DRAIN_TIMEOUT)
except asyncio.TimeoutError:
except TimeoutError:
logger.info("Aborting HDS connection: peer stopped reading")
self.abort()
# connection_lost wakes us and cancels the recording streams, so a
Expand Down
10 changes: 2 additions & 8 deletions pyhap/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@
import inspect
import random
import socket
import sys
from typing import Any, cast
from uuid import UUID

import orjson

from .const import BASE_UUID

if sys.version_info >= (3, 11):
from asyncio import timeout as async_timeout
else:
from async_timeout import timeout as async_timeout

ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
HEX_DIGITS = "0123456789ABCDEF"
_BACKGROUND_TASKS: set[asyncio.Task[Any]] = set()
Expand Down Expand Up @@ -147,9 +141,9 @@ async def event_wait(event: asyncio.Event, timeout: float) -> bool:
:rtype: bool
"""
try:
async with async_timeout(timeout):
async with asyncio.timeout(timeout):
await event.wait()
except asyncio.TimeoutError:
except TimeoutError:
pass
return event.is_set()

Expand Down
8 changes: 3 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,20 @@ license = "Apache-2.0"
description = "HomeKit Accessory Protocol implementation in python"
readme = "README.md"
authors = [{ name = "Ivan Kalchev" }]
requires-python = ">=3.10"
requires-python = ">=3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Home Automation",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"async_timeout;python_version<'3.11'",
"cryptography",
"chacha20poly1305-reuseable",
"orjson>=3.7.2",
Expand Down Expand Up @@ -61,7 +59,7 @@ version = { attr = "pyhap.const.__version__" }

[tool.ruff]
line-length = 88
target-version = "py310"
target-version = "py311"
# Directories previously excluded from black.
exclude = ["generated"]

Expand Down Expand Up @@ -146,7 +144,7 @@ include = [
]

[tool.ty.environment]
python-version = "3.10"
python-version = "3.11"

[tool.pytest.ini_options]
testpaths = [
Expand Down
2 changes: 1 addition & 1 deletion tests/test_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ async def test_stop_stream_kill_after_process_exited(mock_driver: Any) -> None:
"""Verify the kill fallback tolerates a process that exited meanwhile."""
acc = camera.Camera(_OPTIONS, mock_driver, "Camera")
process = Mock()
process.communicate = AsyncMock(side_effect=asyncio.TimeoutError)
process.communicate = AsyncMock(side_effect=TimeoutError)
process.kill.side_effect = ProcessLookupError
process.wait = AsyncMock(return_value=0)
session_info = {
Expand Down
2 changes: 1 addition & 1 deletion tests/test_hap_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,7 +1199,7 @@ async def test_camera_snapshot_times_out(driver: AccessoryDriver) -> None:
connections = {}

def _get_snapshot(*_: Any) -> NoReturn:
raise asyncio.TimeoutError("timeout")
raise TimeoutError("timeout")

acc = Accessory(driver, "TestAcc")
# Camera-only snapshot method, attached dynamically so the
Expand Down
6 changes: 1 addition & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
[tox]
envlist = py310, py311, py312, py313, docs, lint, ty
envlist = py311, py312, py313, docs, lint, ty
skip_missing_interpreters = True

[gh-actions]
python =
3.10: py310
3.11: py311
3.12: py312
3.13: py313
Expand Down Expand Up @@ -64,8 +63,5 @@ extras =
bench
deps =
-r{toxinidir}/requirements_test.txt
# ty type checks against python-version 3.10, where hap_handler
# imports async_timeout instead of asyncio.timeout.
async_timeout
commands =
ty check
Loading