From 97e77ab2293ecf239f395ac2e2ceae465acde349 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Mon, 14 Jun 2021 23:59:25 +0800 Subject: [PATCH] Improve type hints in stream (#51837) * Improve type hints in stream * Fix import locations * Add stream to .strict-typing Co-authored-by: Ruslan Sayfutdinov --- .strict-typing | 1 + homeassistant/components/stream/__init__.py | 68 ++++++++++++--------- homeassistant/components/stream/core.py | 36 +++++++---- homeassistant/components/stream/hls.py | 36 ++++++++--- homeassistant/components/stream/recorder.py | 8 +-- homeassistant/components/stream/worker.py | 16 ++--- mypy.ini | 11 ++++ tests/components/stream/test_hls.py | 18 +++--- tests/components/stream/test_recorder.py | 12 ++-- tests/components/stream/test_worker.py | 14 ++--- 10 files changed, 135 insertions(+), 85 deletions(-) diff --git a/.strict-typing b/.strict-typing index 7218823424e..2427bcdd754 100644 --- a/.strict-typing +++ b/.strict-typing @@ -65,6 +65,7 @@ homeassistant.components.sensor.* homeassistant.components.slack.* homeassistant.components.sonos.media_player homeassistant.components.ssdp.* +homeassistant.components.stream.* homeassistant.components.sun.* homeassistant.components.switch.* homeassistant.components.synology_dsm.* diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index f71e725fcb4..d8e4cb2cdb2 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -16,16 +16,19 @@ to always keep workers active. """ from __future__ import annotations +from collections.abc import Mapping import logging import re import secrets import threading import time from types import MappingProxyType +from typing import cast from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.typing import ConfigType from .const import ( ATTR_ENDPOINTS, @@ -40,18 +43,21 @@ from .const import ( ) from .core import PROVIDERS, IdleTimer, StreamOutput from .hls import async_setup_hls +from .recorder import RecorderOutput _LOGGER = logging.getLogger(__name__) STREAM_SOURCE_RE = re.compile("//.*:.*@") -def redact_credentials(data): +def redact_credentials(data: str) -> str: """Redact credentials from string data.""" return STREAM_SOURCE_RE.sub("//****:****@", data) -def create_stream(hass, stream_source, options=None): +def create_stream( + hass: HomeAssistant, stream_source: str, options: dict[str, str] +) -> Stream: """Create a stream with the specified identfier based on the source url. The stream_source is typically an rtsp url and options are passed into @@ -60,9 +66,6 @@ def create_stream(hass, stream_source, options=None): if DOMAIN not in hass.config.components: raise HomeAssistantError("Stream integration is not set up.") - if options is None: - options = {} - # For RTSP streams, prefer TCP if isinstance(stream_source, str) and stream_source[:7] == "rtsp://": options = { @@ -76,7 +79,7 @@ def create_stream(hass, stream_source, options=None): return stream -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up stream.""" # Set log level to error for libav logging.getLogger("libav").setLevel(logging.ERROR) @@ -98,7 +101,7 @@ async def async_setup(hass, config): async_setup_recorder(hass) @callback - def shutdown(event): + def shutdown(event: Event) -> None: """Stop all stream workers.""" for stream in hass.data[DOMAIN][ATTR_STREAMS]: stream.keepalive = False @@ -113,41 +116,43 @@ async def async_setup(hass, config): class Stream: """Represents a single stream.""" - def __init__(self, hass, source, options=None): + def __init__( + self, hass: HomeAssistant, source: str, options: dict[str, str] + ) -> None: """Initialize a stream.""" self.hass = hass self.source = source self.options = options self.keepalive = False - self.access_token = None - self._thread = None + self.access_token: str | None = None + self._thread: threading.Thread | None = None self._thread_quit = threading.Event() self._outputs: dict[str, StreamOutput] = {} self._fast_restart_once = False - if self.options is None: - self.options = {} - def endpoint_url(self, fmt: str) -> str: """Start the stream and returns a url for the output format.""" if fmt not in self._outputs: raise ValueError(f"Stream is not configured for format '{fmt}'") if not self.access_token: self.access_token = secrets.token_hex() - return self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(self.access_token) + endpoint_fmt: str = self.hass.data[DOMAIN][ATTR_ENDPOINTS][fmt] + return endpoint_fmt.format(self.access_token) - def outputs(self): + def outputs(self) -> Mapping[str, StreamOutput]: """Return a copy of the stream outputs.""" # A copy is returned so the caller can iterate through the outputs # without concern about self._outputs being modified from another thread. return MappingProxyType(self._outputs.copy()) - def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): + def add_provider( + self, fmt: str, timeout: int = OUTPUT_IDLE_TIMEOUT + ) -> StreamOutput: """Add provider output stream.""" if not self._outputs.get(fmt): @callback - def idle_callback(): + def idle_callback() -> None: if ( not self.keepalive or fmt == RECORDER_PROVIDER ) and fmt in self._outputs: @@ -160,7 +165,7 @@ class Stream: self._outputs[fmt] = provider return self._outputs[fmt] - def remove_provider(self, provider): + def remove_provider(self, provider: StreamOutput) -> None: """Remove provider output stream.""" if provider.name in self._outputs: self._outputs[provider.name].cleanup() @@ -169,12 +174,12 @@ class Stream: if not self._outputs: self.stop() - def check_idle(self): + def check_idle(self) -> None: """Reset access token if all providers are idle.""" if all(p.idle for p in self._outputs.values()): self.access_token = None - def start(self): + def start(self) -> None: """Start a stream.""" if self._thread is None or not self._thread.is_alive(): if self._thread is not None: @@ -189,14 +194,14 @@ class Stream: self._thread.start() _LOGGER.info("Started stream: %s", redact_credentials(str(self.source))) - def update_source(self, new_source): + def update_source(self, new_source: str) -> None: """Restart the stream with a new stream source.""" _LOGGER.debug("Updating stream source %s", new_source) self.source = new_source self._fast_restart_once = True self._thread_quit.set() - def _run_worker(self): + def _run_worker(self) -> None: """Handle consuming streams and restart keepalive streams.""" # Keep import here so that we can import stream integration without installing reqs # pylint: disable=import-outside-toplevel @@ -229,17 +234,17 @@ class Stream: ) self._worker_finished() - def _worker_finished(self): + def _worker_finished(self) -> None: """Schedule cleanup of all outputs.""" @callback - def remove_outputs(): + def remove_outputs() -> None: for provider in self.outputs().values(): self.remove_provider(provider) self.hass.loop.call_soon_threadsafe(remove_outputs) - def stop(self): + def stop(self) -> None: """Remove outputs and access token.""" self._outputs = {} self.access_token = None @@ -247,7 +252,7 @@ class Stream: if not self.keepalive: self._stop() - def _stop(self): + def _stop(self) -> None: """Stop worker thread.""" if self._thread is not None: self._thread_quit.set() @@ -255,7 +260,9 @@ class Stream: self._thread = None _LOGGER.info("Stopped stream: %s", redact_credentials(str(self.source))) - async def async_record(self, video_path, duration=30, lookback=5): + async def async_record( + self, video_path: str, duration: int = 30, lookback: int = 5 + ) -> None: """Make a .mp4 recording from a provided stream.""" # Check for file access @@ -265,10 +272,13 @@ class Stream: # Add recorder recorder = self.outputs().get(RECORDER_PROVIDER) if recorder: + assert isinstance(recorder, RecorderOutput) raise HomeAssistantError( f"Stream already recording to {recorder.video_path}!" ) - recorder = self.add_provider(RECORDER_PROVIDER, timeout=duration) + recorder = cast( + RecorderOutput, self.add_provider(RECORDER_PROVIDER, timeout=duration) + ) recorder.video_path = video_path self.start() diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 136c3c1dbfa..5f8bb736761 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -4,18 +4,21 @@ from __future__ import annotations import asyncio from collections import deque import datetime -from typing import Callable +from typing import TYPE_CHECKING from aiohttp import web import attr -from homeassistant.components.http import HomeAssistantView -from homeassistant.core import HomeAssistant, callback +from homeassistant.components.http.view import HomeAssistantView +from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers.event import async_call_later from homeassistant.util.decorator import Registry from .const import ATTR_STREAMS, DOMAIN +if TYPE_CHECKING: + from . import Stream + PROVIDERS = Registry() @@ -59,34 +62,34 @@ class IdleTimer: """ def __init__( - self, hass: HomeAssistant, timeout: int, idle_callback: Callable[[], None] + self, hass: HomeAssistant, timeout: int, idle_callback: CALLBACK_TYPE ) -> None: """Initialize IdleTimer.""" self._hass = hass self._timeout = timeout self._callback = idle_callback - self._unsub = None + self._unsub: CALLBACK_TYPE | None = None self.idle = False - def start(self): + def start(self) -> None: """Start the idle timer if not already started.""" self.idle = False if self._unsub is None: self._unsub = async_call_later(self._hass, self._timeout, self.fire) - def awake(self): + def awake(self) -> None: """Keep the idle time alive by resetting the timeout.""" self.idle = False # Reset idle timeout self.clear() self._unsub = async_call_later(self._hass, self._timeout, self.fire) - def clear(self): + def clear(self) -> None: """Clear and disable the timer if it has not already fired.""" if self._unsub is not None: self._unsub() - def fire(self, _now=None): + def fire(self, _now: datetime.datetime) -> None: """Invoke the idle timeout callback, called when the alarm fires.""" self.idle = True self._unsub = None @@ -97,7 +100,10 @@ class StreamOutput: """Represents a stream output.""" def __init__( - self, hass: HomeAssistant, idle_timer: IdleTimer, deque_maxlen: int = None + self, + hass: HomeAssistant, + idle_timer: IdleTimer, + deque_maxlen: int | None = None, ) -> None: """Initialize a stream output.""" self._hass = hass @@ -172,7 +178,7 @@ class StreamOutput: self._event.set() self._event.clear() - def cleanup(self): + def cleanup(self) -> None: """Handle cleanup.""" self._event.set() self.idle_timer.clear() @@ -190,7 +196,9 @@ class StreamView(HomeAssistantView): requires_auth = False platform = None - async def get(self, request, token, sequence=None): + async def get( + self, request: web.Request, token: str, sequence: str = "" + ) -> web.StreamResponse: """Start a GET request.""" hass = request.app["hass"] @@ -207,6 +215,8 @@ class StreamView(HomeAssistantView): return await self.handle(request, stream, sequence) - async def handle(self, request, stream, sequence): + async def handle( + self, request: web.Request, stream: Stream, sequence: str + ) -> web.StreamResponse: """Handle the stream request.""" raise NotImplementedError() diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 0b0cd4ac3b2..d7167e0b7de 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,7 +1,11 @@ """Provide functionality to stream HLS.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + from aiohttp import web -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from .const import ( EXT_X_START, @@ -10,12 +14,15 @@ from .const import ( MAX_SEGMENTS, NUM_PLAYLIST_SEGMENTS, ) -from .core import PROVIDERS, HomeAssistant, IdleTimer, StreamOutput, StreamView +from .core import PROVIDERS, IdleTimer, StreamOutput, StreamView from .fmp4utils import get_codec_string +if TYPE_CHECKING: + from . import Stream + @callback -def async_setup_hls(hass): +def async_setup_hls(hass: HomeAssistant) -> str: """Set up api endpoints.""" hass.http.register_view(HlsPlaylistView()) hass.http.register_view(HlsSegmentView()) @@ -32,12 +39,13 @@ class HlsMasterPlaylistView(StreamView): cors_allowed = True @staticmethod - def render(track): + def render(track: StreamOutput) -> str: """Render M3U8 file.""" # Need to calculate max bandwidth as input_container.bit_rate doesn't seem to work # Calculate file size / duration and use a small multiplier to account for variation # hls spec already allows for 25% variation - segment = track.get_segment(track.sequences[-2]) + if not (segment := track.get_segment(track.sequences[-2])): + return "" bandwidth = round( (len(segment.init) + sum(len(part.data) for part in segment.parts)) * 8 @@ -52,7 +60,9 @@ class HlsMasterPlaylistView(StreamView): ] return "\n".join(lines) + "\n" - async def handle(self, request, stream, sequence): + async def handle( + self, request: web.Request, stream: Stream, sequence: str + ) -> web.Response: """Return m3u8 playlist.""" track = stream.add_provider(HLS_PROVIDER) stream.start() @@ -73,7 +83,7 @@ class HlsPlaylistView(StreamView): cors_allowed = True @staticmethod - def render(track): + def render(track: StreamOutput) -> str: """Render playlist.""" # NUM_PLAYLIST_SEGMENTS+1 because most recent is probably not yet complete segments = list(track.get_segments())[-(NUM_PLAYLIST_SEGMENTS + 1) :] @@ -130,7 +140,9 @@ class HlsPlaylistView(StreamView): return "\n".join(playlist) + "\n" - async def handle(self, request, stream, sequence): + async def handle( + self, request: web.Request, stream: Stream, sequence: str + ) -> web.Response: """Return m3u8 playlist.""" track = stream.add_provider(HLS_PROVIDER) stream.start() @@ -154,7 +166,9 @@ class HlsInitView(StreamView): name = "api:stream:hls:init" cors_allowed = True - async def handle(self, request, stream, sequence): + async def handle( + self, request: web.Request, stream: Stream, sequence: str + ) -> web.Response: """Return init.mp4.""" track = stream.add_provider(HLS_PROVIDER) if not (segments := track.get_segments()): @@ -170,7 +184,9 @@ class HlsSegmentView(StreamView): name = "api:stream:hls:segment" cors_allowed = True - async def handle(self, request, stream, sequence): + async def handle( + self, request: web.Request, stream: Stream, sequence: str + ) -> web.Response: """Return fmp4 segment.""" track = stream.add_provider(HLS_PROVIDER) track.idle_timer.awake() diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 8e21777fa0b..99276d9763c 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -23,11 +23,11 @@ _LOGGER = logging.getLogger(__name__) @callback -def async_setup_recorder(hass): +def async_setup_recorder(hass: HomeAssistant) -> None: """Only here so Provider Registry works.""" -def recorder_save_worker(file_out: str, segments: deque[Segment]): +def recorder_save_worker(file_out: str, segments: deque[Segment]) -> None: """Handle saving stream.""" if not segments: @@ -121,7 +121,7 @@ class RecorderOutput(StreamOutput): def __init__(self, hass: HomeAssistant, idle_timer: IdleTimer) -> None: """Initialize recorder output.""" super().__init__(hass, idle_timer) - self.video_path = None + self.video_path: str @property def name(self) -> str: @@ -132,7 +132,7 @@ class RecorderOutput(StreamOutput): """Prepend segments to existing list.""" self._segments.extendleft(reversed(segments)) - def cleanup(self): + def cleanup(self) -> None: """Write recording and clean up.""" _LOGGER.debug("Starting recorder worker thread") thread = threading.Thread( diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index cca981e5db3..3023b8cd85c 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -7,7 +7,7 @@ from fractions import Fraction from io import BytesIO import logging from threading import Event -from typing import Callable, cast +from typing import Any, Callable, cast import av @@ -45,9 +45,9 @@ class SegmentBuffer: self._memory_file: BytesIO = cast(BytesIO, None) self._av_output: av.container.OutputContainer = None self._input_video_stream: av.video.VideoStream = None - self._input_audio_stream = None # av.audio.AudioStream | None + self._input_audio_stream: Any | None = None # av.audio.AudioStream | None self._output_video_stream: av.video.VideoStream = None - self._output_audio_stream = None # av.audio.AudioStream | None + self._output_audio_stream: Any | None = None # av.audio.AudioStream | None self._segment: Segment | None = None self._segment_last_write_pos: int = cast(int, None) self._part_start_dts: int = cast(int, None) @@ -82,7 +82,7 @@ class SegmentBuffer: def set_streams( self, video_stream: av.video.VideoStream, - audio_stream, + audio_stream: Any, # no type hint for audio_stream until https://github.com/PyAV-Org/PyAV/pull/775 is merged ) -> None: """Initialize output buffer with streams from container.""" @@ -206,7 +206,10 @@ class SegmentBuffer: def stream_worker( # noqa: C901 - source: str, options: dict, segment_buffer: SegmentBuffer, quit_event: Event + source: str, + options: dict[str, str], + segment_buffer: SegmentBuffer, + quit_event: Event, ) -> None: """Handle consuming streams.""" @@ -259,7 +262,7 @@ def stream_worker( # noqa: C901 found_audio = False try: container_packets = container.demux((video_stream, audio_stream)) - first_packet = None + first_packet: av.Packet | None = None # Get to first video keyframe while first_packet is None: packet = next(container_packets) @@ -315,7 +318,6 @@ def stream_worker( # noqa: C901 _LOGGER.warning( "Audio stream not found" ) # Some streams declare an audio stream and never send any packets - audio_stream = None except (av.AVError, StopIteration) as ex: _LOGGER.error( diff --git a/mypy.ini b/mypy.ini index 775b0653fe4..b21c71d172c 100644 --- a/mypy.ini +++ b/mypy.ini @@ -726,6 +726,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.stream.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.sun.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 37c499b6bd0..89c07083b17 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -107,7 +107,7 @@ async def test_hls_stream(hass, hls_stream, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # Request stream stream.add_provider(HLS_PROVIDER) @@ -148,7 +148,7 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # Request stream stream.add_provider(HLS_PROVIDER) @@ -190,7 +190,7 @@ async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): # Setup demo HLS track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # Request stream stream.add_provider(HLS_PROVIDER) @@ -212,7 +212,7 @@ async def test_stream_keepalive(hass): # Setup demo HLS track source = "test_stream_keepalive_source" - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) track = stream.add_provider(HLS_PROVIDER) track.num_segments = 2 @@ -247,7 +247,7 @@ async def test_hls_playlist_view_no_output(hass, hass_client, hls_stream): """Test rendering the hls playlist with no output segments.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, STREAM_SOURCE) + stream = create_stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) hls_client = await hls_stream(stream) @@ -261,7 +261,7 @@ async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync): """Test rendering the hls playlist with 1 and 2 output segments.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, STREAM_SOURCE) + stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) @@ -295,7 +295,7 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync): """Test rendering the hls playlist with more segments than the segment deque can hold.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, STREAM_SOURCE) + stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) @@ -347,7 +347,7 @@ async def test_hls_playlist_view_discontinuity(hass, hls_stream, stream_worker_s """Test a discontinuity across segments in the stream with 3 segments.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, STREAM_SOURCE) + stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) @@ -389,7 +389,7 @@ async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sy """Test a discontinuity with more segments than the segment deque can hold.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, STREAM_SOURCE) + stream = create_stream(hass, STREAM_SOURCE, {}) stream_worker_sync.pause() hls = stream.add_provider(HLS_PROVIDER) diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 07e1464f31a..72c6dfa197f 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -34,7 +34,7 @@ async def test_record_stream(hass, hass_client, record_worker_sync): # Setup demo track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") @@ -56,7 +56,7 @@ async def test_record_lookback( await async_setup_component(hass, "stream", {"stream": {}}) source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # Start an HLS feed to enable lookback stream.add_provider(HLS_PROVIDER) @@ -85,7 +85,7 @@ async def test_recorder_timeout(hass, hass_client, stream_worker_sync): # Setup demo track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") recorder = stream.add_provider(RECORDER_PROVIDER) @@ -111,7 +111,7 @@ async def test_record_path_not_allowed(hass, hass_client): # Setup demo track source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) with patch.object( hass.config, "is_allowed_path", return_value=False ), pytest.raises(HomeAssistantError): @@ -203,7 +203,7 @@ async def test_record_stream_audio( source = generate_h264_video( container_format="mov", audio_codec=a_codec ) # mov can store PCM - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") recorder = stream.add_provider(RECORDER_PROVIDER) @@ -234,7 +234,7 @@ async def test_record_stream_audio( async def test_recorder_log(hass, caplog): """Test starting a stream to record logs the url without username and password.""" await async_setup_component(hass, "stream", {"stream": {}}) - stream = create_stream(hass, "https://abcd:efgh@foo.bar") + stream = create_stream(hass, "https://abcd:efgh@foo.bar", {}) with patch.object(hass.config, "is_allowed_path", return_value=True): await stream.async_record("/example/path") assert "https://abcd:efgh@foo.bar" not in caplog.text diff --git a/tests/components/stream/test_worker.py b/tests/components/stream/test_worker.py index 74a4fa0e553..793038c6770 100644 --- a/tests/components/stream/test_worker.py +++ b/tests/components/stream/test_worker.py @@ -220,7 +220,7 @@ class MockFlushPart: async def async_decode_stream(hass, packets, py_av=None): """Start a stream worker that decodes incoming stream packets into output segments.""" - stream = Stream(hass, STREAM_SOURCE) + stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) if not py_av: @@ -244,7 +244,7 @@ async def async_decode_stream(hass, packets, py_av=None): async def test_stream_open_fails(hass): """Test failure on stream open.""" - stream = Stream(hass, STREAM_SOURCE) + stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") @@ -565,7 +565,7 @@ async def test_stream_stopped_while_decoding(hass): worker_open = threading.Event() worker_wake = threading.Event() - stream = Stream(hass, STREAM_SOURCE) + stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) py_av = MockPyAv() @@ -592,7 +592,7 @@ async def test_update_stream_source(hass): worker_open = threading.Event() worker_wake = threading.Event() - stream = Stream(hass, STREAM_SOURCE) + stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) # Note that keepalive is not set here. The stream is "restarted" even though # it is not stopping due to failure. @@ -636,7 +636,7 @@ async def test_update_stream_source(hass): async def test_worker_log(hass, caplog): """Test that the worker logs the url without username and password.""" - stream = Stream(hass, "https://abcd:efgh@foo.bar") + stream = Stream(hass, "https://abcd:efgh@foo.bar", {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") @@ -654,7 +654,7 @@ async def test_durations(hass, record_worker_sync): await async_setup_component(hass, "stream", {"stream": {}}) source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # use record_worker_sync to grab output segments with patch.object(hass.config, "is_allowed_path", return_value=True): @@ -693,7 +693,7 @@ async def test_has_keyframe(hass, record_worker_sync): await async_setup_component(hass, "stream", {"stream": {}}) source = generate_h264_video() - stream = create_stream(hass, source) + stream = create_stream(hass, source, {}) # use record_worker_sync to grab output segments with patch.object(hass.config, "is_allowed_path", return_value=True):