From 9cc0006b926efd0ae28ac9101b1ae210cbc69c11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 01:23:53 -1000 Subject: [PATCH] Ensure TurboJPEG is imported in the executor (#113504) The import was too late and it eneded up being imported in the event loop --- homeassistant/components/camera/img_util.py | 23 ++++++++++---------- tests/components/camera/test_img_util.py | 24 +++++++++++++++------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/camera/img_util.py b/homeassistant/components/camera/img_util.py index bf07d6a2f1f..b9b607d5edf 100644 --- a/homeassistant/components/camera/img_util.py +++ b/homeassistant/components/camera/img_util.py @@ -2,20 +2,27 @@ from __future__ import annotations +from contextlib import suppress import logging from typing import TYPE_CHECKING, Literal, cast +with suppress(Exception): # pylint: disable=broad-except + # TurboJPEG imports numpy which may or may not work so + # we have to guard the import here. We still want + # to import it at top level so it gets loaded + # in the import executor and not in the event loop. + from turbojpeg import TurboJPEG + + +if TYPE_CHECKING: + from . import Image + SUPPORTED_SCALING_FACTORS = [(7, 8), (3, 4), (5, 8), (1, 2), (3, 8), (1, 4), (1, 8)] _LOGGER = logging.getLogger(__name__) JPEG_QUALITY = 75 -if TYPE_CHECKING: - from turbojpeg import TurboJPEG - - from . import Image - def find_supported_scaling_factor( current_width: int, current_height: int, target_width: int, target_height: int @@ -90,12 +97,6 @@ class TurboJPEGSingleton: def __init__(self) -> None: """Try to create TurboJPEG only once.""" try: - # TurboJPEG checks for libturbojpeg - # when its created, but it imports - # numpy which may or may not work so - # we have to guard the import here. - from turbojpeg import TurboJPEG # pylint: disable=import-outside-toplevel - TurboJPEGSingleton.__instance = TurboJPEG() except Exception: # pylint: disable=broad-except _LOGGER.exception( diff --git a/tests/components/camera/test_img_util.py b/tests/components/camera/test_img_util.py index 4fb863d414d..5c3d3712f38 100644 --- a/tests/components/camera/test_img_util.py +++ b/tests/components/camera/test_img_util.py @@ -38,25 +38,33 @@ def test_scale_jpeg_camera_image() -> None: camera_image = Image("image/jpeg", EMPTY_16_12_JPEG) turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch("turbojpeg.TurboJPEG", return_value=False): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=False + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) turbo_jpeg.decode_header.side_effect = OSError - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == EMPTY_16_12_JPEG turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=8, second_height=6 ) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() jpeg_bytes = scale_jpeg_camera_image(camera_image, 8, 6) @@ -65,7 +73,9 @@ def test_scale_jpeg_camera_image() -> None: turbo_jpeg = mock_turbo_jpeg( first_width=640, first_height=480, second_width=640, second_height=480 ) - with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", return_value=turbo_jpeg + ): TurboJPEGSingleton() jpeg_bytes = scale_jpeg_camera_image(camera_image, 320, 480) @@ -75,7 +85,9 @@ def test_scale_jpeg_camera_image() -> None: def test_turbojpeg_load_failure() -> None: """Handle libjpegturbo not being installed.""" _clear_turbojpeg_singleton() - with patch("turbojpeg.TurboJPEG", side_effect=Exception): + with patch( + "homeassistant.components.camera.img_util.TurboJPEG", side_effect=Exception + ): TurboJPEGSingleton() assert TurboJPEGSingleton.instance() is False