From 61977d352a10f89e51c950ca012052661a4a95bd Mon Sep 17 00:00:00 2001 From: bastimeyer Date: Thu, 13 Apr 2023 18:22:11 +0200 Subject: [PATCH] cli.output: split up module into subpackage - Move abstract `Output` base class into `streamlink_cli.output.abc` and properly declare abstract methods - Move `FileOutput` into `streamlink_cli.output.file` - Move `PlayerOutput` into `streamlink_cli.output.player` - Update patched module paths in tests --- src/streamlink_cli/output/__init__.py | 3 + src/streamlink_cli/output/abc.py | 34 +++++++++ src/streamlink_cli/output/file.py | 46 ++++++++++++ .../{output.py => output/player.py} | 75 +------------------ tests/cli/output/test_output.py | 16 ++-- tests/cli/output/test_playeroutput.py | 4 +- tests/cli/test_cmdline.py | 6 +- tests/cli/test_streamrunner.py | 2 +- 8 files changed, 98 insertions(+), 88 deletions(-) create mode 100644 src/streamlink_cli/output/__init__.py create mode 100644 src/streamlink_cli/output/abc.py create mode 100644 src/streamlink_cli/output/file.py rename src/streamlink_cli/{output.py => output/player.py} (82%) diff --git a/src/streamlink_cli/output/__init__.py b/src/streamlink_cli/output/__init__.py new file mode 100644 index 00000000..c91cdb4d --- /dev/null +++ b/src/streamlink_cli/output/__init__.py @@ -0,0 +1,3 @@ +from streamlink_cli.output.abc import Output +from streamlink_cli.output.file import FileOutput +from streamlink_cli.output.player import PlayerOutput diff --git a/src/streamlink_cli/output/abc.py b/src/streamlink_cli/output/abc.py new file mode 100644 index 00000000..8c203f46 --- /dev/null +++ b/src/streamlink_cli/output/abc.py @@ -0,0 +1,34 @@ +from abc import ABCMeta, abstractmethod + + +class Output(metaclass=ABCMeta): + def __init__(self): + self.opened = False + + def open(self): + self._open() + self.opened = True + + def close(self): + if self.opened: + self._close() + + self.opened = False + + def write(self, data): + if not self.opened: + raise OSError("Output is not opened") + + return self._write(data) + + @abstractmethod + def _open(self): + raise NotImplementedError + + @abstractmethod + def _close(self): + raise NotImplementedError + + @abstractmethod + def _write(self, data): + raise NotImplementedError diff --git a/src/streamlink_cli/output/file.py b/src/streamlink_cli/output/file.py new file mode 100644 index 00000000..1cd5772a --- /dev/null +++ b/src/streamlink_cli/output/file.py @@ -0,0 +1,46 @@ +from pathlib import Path +from typing import BinaryIO, Optional + +from streamlink.compat import is_win32 +from streamlink_cli.compat import stdout +from streamlink_cli.output.abc import Output + + +if is_win32: + import msvcrt + from os import O_BINARY + + +class FileOutput(Output): + def __init__( + self, + filename: Optional[Path] = None, + fd: Optional[BinaryIO] = None, + record: Optional["FileOutput"] = None, + ): + super().__init__() + self.filename = filename + self.fd = fd + self.record = record + + def _open(self): + if self.filename: + self.filename.parent.mkdir(parents=True, exist_ok=True) + self.fd = open(self.filename, "wb") + + if self.record: + self.record.open() + + if is_win32: + msvcrt.setmode(self.fd.fileno(), O_BINARY) + + def _close(self): + if self.fd is not stdout: + self.fd.close() + if self.record: + self.record.close() + + def _write(self, data): + self.fd.write(data) + if self.record: + self.record.write(data) diff --git a/src/streamlink_cli/output.py b/src/streamlink_cli/output/player.py similarity index 82% rename from src/streamlink_cli/output.py rename to src/streamlink_cli/output/player.py index 19904b25..48edb49d 100644 --- a/src/streamlink_cli/output.py +++ b/src/streamlink_cli/output/player.py @@ -5,87 +5,17 @@ import shlex import subprocess import sys from contextlib import suppress -from pathlib import Path from time import sleep -from typing import BinaryIO, Optional from streamlink.compat import is_win32 -from streamlink_cli.compat import stdout from streamlink_cli.constants import PLAYER_ARGS_INPUT_DEFAULT, PLAYER_ARGS_INPUT_FALLBACK, SUPPORTED_PLAYERS +from streamlink_cli.output.abc import Output from streamlink_cli.utils import Formatter -if is_win32: - import msvcrt - log = logging.getLogger("streamlink.cli.output") -class Output: - def __init__(self): - self.opened = False - - def open(self): - self._open() - self.opened = True - - def close(self): - if self.opened: - self._close() - - self.opened = False - - def write(self, data): - if not self.opened: - raise OSError("Output is not opened") - - return self._write(data) - - def _open(self): - pass - - def _close(self): - pass - - def _write(self, data): - pass - - -class FileOutput(Output): - def __init__( - self, - filename: Optional[Path] = None, - fd: Optional[BinaryIO] = None, - record: Optional["FileOutput"] = None, - ): - super().__init__() - self.filename = filename - self.fd = fd - self.record = record - - def _open(self): - if self.filename: - self.filename.parent.mkdir(parents=True, exist_ok=True) - self.fd = open(self.filename, "wb") - - if self.record: - self.record.open() - - if is_win32: - msvcrt.setmode(self.fd.fileno(), os.O_BINARY) - - def _close(self): - if self.fd is not stdout: - self.fd.close() - if self.record: - self.record.close() - - def _write(self, data): - self.fd.write(data) - if self.record: - self.record.write(data) - - class PlayerOutput(Output): PLAYER_TERMINATE_TIMEOUT = 10.0 @@ -281,6 +211,3 @@ class PlayerOutput(Output): self.http.write(data) else: self.player.stdin.write(data) - - -__all__ = ["PlayerOutput", "FileOutput"] diff --git a/tests/cli/output/test_output.py b/tests/cli/output/test_output.py index d4d0b877..27a34163 100644 --- a/tests/cli/output/test_output.py +++ b/tests/cli/output/test_output.py @@ -10,7 +10,7 @@ import pytest from streamlink_cli.output import FileOutput, PlayerOutput -@patch("streamlink_cli.output.stdout") +@patch("streamlink_cli.output.file.stdout") class TestFileOutput(unittest.TestCase): @staticmethod def subject(filename, fd): @@ -75,7 +75,7 @@ class TestFileOutput(unittest.TestCase): self._test_open(mock_open, mock_stdout) @pytest.mark.windows_only() - @patch("streamlink_cli.output.msvcrt") + @patch("streamlink_cli.output.file.msvcrt") @patch("builtins.open") def test_open_windows(self, mock_open: Mock, mock_msvcrt: Mock, mock_stdout: Mock): mock_path = self._test_open(mock_open, mock_stdout) @@ -91,34 +91,34 @@ class TestPlayerOutput(unittest.TestCase): assert PlayerOutput.supported_player("mpv") == "mpv" assert PlayerOutput.supported_player("potplayermini.exe") == "potplayer" - @patch("streamlink_cli.output.os.path.basename", new=ntpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename) def test_supported_player_win32(self): assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe") == "mpv" assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe") == "vlc" assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe") == "potplayer" - @patch("streamlink_cli.output.os.path.basename", new=posixpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename) def test_supported_player_posix(self): assert PlayerOutput.supported_player("/usr/bin/mpv") == "mpv" assert PlayerOutput.supported_player("/usr/bin/vlc") == "vlc" - @patch("streamlink_cli.output.os.path.basename", new=ntpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename) def test_supported_player_args_win32(self): assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe --argh") == "mpv" assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe --argh") == "vlc" assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe --argh") == "potplayer" - @patch("streamlink_cli.output.os.path.basename", new=posixpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename) def test_supported_player_args_posix(self): assert PlayerOutput.supported_player("/usr/bin/mpv --argh") == "mpv" assert PlayerOutput.supported_player("/usr/bin/vlc --argh") == "vlc" - @patch("streamlink_cli.output.os.path.basename", new=posixpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename) def test_supported_player_negative_posix(self): assert PlayerOutput.supported_player("/usr/bin/xmpvideo") is None assert PlayerOutput.supported_player("/usr/bin/echo") is None - @patch("streamlink_cli.output.os.path.basename", new=ntpath.basename) + @patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename) def test_supported_player_negative_win32(self): assert PlayerOutput.supported_player("C:\\mpc\\mpc-hd.exe") is None assert PlayerOutput.supported_player("C:\\mplayer\\not-vlc.exe") is None diff --git a/tests/cli/output/test_playeroutput.py b/tests/cli/output/test_playeroutput.py index 8e125cb1..578213cc 100644 --- a/tests/cli/output/test_playeroutput.py +++ b/tests/cli/output/test_playeroutput.py @@ -7,7 +7,7 @@ from streamlink_cli.output import PlayerOutput @pytest.fixture() def playeroutput(request: pytest.FixtureRequest): - with patch("streamlink_cli.output.sleep"): + with patch("streamlink_cli.output.player.sleep"): playeroutput = PlayerOutput(**getattr(request, "param", {})) yield playeroutput playeroutput.close() @@ -16,7 +16,7 @@ def playeroutput(request: pytest.FixtureRequest): @pytest.fixture() def mock_popen(playeroutput: PlayerOutput): mock_popen = Mock(return_value=Mock(poll=Mock(side_effect=Mock(return_value=None)))) - with patch("streamlink_cli.output.sleep"), \ + with patch("streamlink_cli.output.player.sleep"), \ patch("subprocess.Popen", mock_popen): yield mock_popen diff --git a/tests/cli/test_cmdline.py b/tests/cli/test_cmdline.py index 954bfd7d..26f4165e 100644 --- a/tests/cli/test_cmdline.py +++ b/tests/cli/test_cmdline.py @@ -34,9 +34,9 @@ class CommandLineTestCase(unittest.TestCase): patch("streamlink_cli.main.setup_plugins"), \ patch("streamlink_cli.main.setup_streamlink") as mock_setup_streamlink, \ patch("streamlink_cli.main.streamlink", session), \ - patch("streamlink_cli.output.subprocess.Popen") as mock_popen, \ - patch("streamlink_cli.output.subprocess.call") as mock_call, \ - patch("streamlink_cli.output.sleep"): + patch("streamlink_cli.output.player.subprocess.Popen") as mock_popen, \ + patch("streamlink_cli.output.player.subprocess.call") as mock_call, \ + patch("streamlink_cli.output.player.sleep"): mock_argv.__getitem__.side_effect = lambda x: args[x] mock_popen.return_value = Mock(poll=Mock(side_effect=poll_factory([None, 0]))) try: diff --git a/tests/cli/test_streamrunner.py b/tests/cli/test_streamrunner.py index ef3c18f2..402ccf7c 100644 --- a/tests/cli/test_streamrunner.py +++ b/tests/cli/test_streamrunner.py @@ -164,7 +164,7 @@ class TestPlayerOutput: @pytest.fixture() def output(self, player_process: Mock): with patch("subprocess.Popen") as mock_popen, \ - patch("streamlink_cli.output.sleep"): + patch("streamlink_cli.output.player.sleep"): mock_popen.return_value = player_process output = FakePlayerOutput("mocked") output.open()