logger levels working, linter and manual testing need to be tried

This commit is contained in:
jbannon 2022-06-06 22:24:35 +00:00
parent a08f31ae20
commit 1e68a785ba
5 changed files with 104 additions and 23 deletions

View file

@ -2,6 +2,8 @@ import argparse
################################################################################################### ###################################################################################################
# GLOBAL PARSER # GLOBAL PARSER
from ytdl_sub.utils.logger import LoggerLevels
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="ytdl-sub: Automate download and adding metadata with YoutubeDL" description="ytdl-sub: Automate download and adding metadata with YoutubeDL"
) )
@ -13,6 +15,14 @@ parser.add_argument(
help="path to the config yaml, uses config.yaml if not provided", help="path to the config yaml, uses config.yaml if not provided",
default="config.yaml", default="config.yaml",
) )
parser.add_argument(
"--log-level",
metavar="|".join(LoggerLevels.names()),
type=str,
help="level of logs to print to console, defaults to info",
default=LoggerLevels.name_of(LoggerLevels.INFO),
choices=LoggerLevels.names(),
)
################################################################################################### ###################################################################################################
# SUBSCRIPTION PARSER # SUBSCRIPTION PARSER
subparsers = parser.add_subparsers(dest="subparser") subparsers = parser.add_subparsers(dest="subparser")

View file

@ -18,6 +18,7 @@ from yt_dlp.utils import RejectedVideoReached
from ytdl_sub.config.preset_options import Overrides from ytdl_sub.config.preset_options import Overrides
from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.base_entry import BaseEntry
from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.logger import Logger
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
@ -115,8 +116,9 @@ class Downloader(Generic[DownloaderOptionsT, DownloaderEntryT], ABC):
if ytdl_options_overrides is not None: if ytdl_options_overrides is not None:
ytdl_options = dict(ytdl_options, **ytdl_options_overrides) ytdl_options = dict(ytdl_options, **ytdl_options_overrides)
with ytdl.YoutubeDL(ytdl_options) as ytdl_downloader: with Logger.handle_external_logs(name="yt-dlp"):
yield ytdl_downloader with ytdl.YoutubeDL(ytdl_options) as ytdl_downloader:
yield ytdl_downloader
def extract_info(self, ytdl_options_overrides: Optional[Dict] = None, **kwargs) -> Dict: def extract_info(self, ytdl_options_overrides: Optional[Dict] = None, **kwargs) -> Dict:
""" """

View file

@ -72,6 +72,8 @@ def _main():
args, extra_args = parser.parse_known_args() args, extra_args = parser.parse_known_args()
config: ConfigFile = ConfigFile.from_file_path(args.config).initialize() config: ConfigFile = ConfigFile.from_file_path(args.config).initialize()
Logger.set_log_level(log_level=args.log_level)
if args.subparser == "sub": if args.subparser == "sub":
_download_subscriptions_from_yaml_files(config=config, args=args) _download_subscriptions_from_yaml_files(config=config, args=args)
logger.info("Subscription download complete!") logger.info("Subscription download complete!")

View file

@ -4,6 +4,7 @@ import logging
import os import os
import sys import sys
import tempfile import tempfile
from typing import List
from typing import Optional from typing import Optional
@ -44,14 +45,60 @@ class LoggerLevels:
case _: case _:
return logging.INFO return logging.INFO
@classmethod
def name_of(cls, log_level: int):
"""
Parameters
----------
log_level
The log level
Returns
-------
Name of the log levels
"""
match log_level:
case cls.QUIET:
return "quiet"
case cls.INFO:
return "info"
case cls.VERBOSE:
return "verbose"
case cls.DEBUG:
return "debug"
case _:
raise ValueError("Invalid log level")
@classmethod
def all(cls) -> List[int]:
"""
Returns
-------
All log levels
"""
return [cls.QUIET, cls.INFO, cls.VERBOSE, cls.DEBUG]
@classmethod
def names(cls) -> List[str]:
"""
Returns
-------
All log level names
"""
return [cls.name_of(log_level=log_level) for log_level in cls.all()]
class Logger: class Logger:
# The level set via CLI arguments # The level set via CLI arguments
LEVEL = LoggerLevels.DEBUG _LEVEL = LoggerLevels.DEBUG
_DEBUG_LOGGER_FILE = None _DEBUG_LOGGER_FILE = None
@classmethod
def set_log_level(cls, log_level: int):
cls._LEVEL = log_level
@classmethod @classmethod
def _get_formatter(cls) -> logging.Formatter: def _get_formatter(cls) -> logging.Formatter:
""" """
@ -69,7 +116,7 @@ class Logger:
Logger handler Logger handler
""" """
handler = logging.StreamHandler(sys.stdout) handler = logging.StreamHandler(sys.stdout)
handler.setLevel(LoggerLevels.to_logging_level(cls.LEVEL)) handler.setLevel(LoggerLevels.to_logging_level(cls._LEVEL))
handler.setFormatter(cls._get_formatter()) handler.setFormatter(cls._get_formatter())
return handler return handler
@ -95,7 +142,7 @@ class Logger:
logger_name += f":{name}" logger_name += f":{name}"
logger = logging.Logger(name=logger_name, level=logging.DEBUG) logger = logging.Logger(name=logger_name, level=logging.DEBUG)
if stdout and cls.LEVEL >= LoggerLevels.INFO: if stdout and cls._LEVEL >= LoggerLevels.INFO:
logger.addHandler(cls._get_stdout_handler()) logger.addHandler(cls._get_stdout_handler())
if debug_file: if debug_file:
logger.addHandler(cls._get_debug_file_handler()) logger.addHandler(cls._get_debug_file_handler())
@ -130,22 +177,15 @@ class Logger:
Optional. Name of the logger which is included in the prefix like [ytdl-sub:<name>]. Optional. Name of the logger which is included in the prefix like [ytdl-sub:<name>].
If None, the prefix is just [ytdl-sub] If None, the prefix is just [ytdl-sub]
""" """
redirect_stream = io.StringIO() logger = cls._get(name=name, stdout=cls._LEVEL >= LoggerLevels.VERBOSE, debug_file=True)
redirect_handler = logging.StreamHandler(redirect_stream)
redirect_handler.setLevel(LoggerLevels.to_logging_level(cls.LEVEL))
redirect_handler.setFormatter(cls._get_formatter())
write_to_stdout = cls.LEVEL >= LoggerLevels.VERBOSE with io.StringIO() as redirect_stream:
write_to_debug_file = True with contextlib.redirect_stdout(new_target=redirect_stream):
with contextlib.redirect_stderr(new_target=redirect_stream):
yield
logger = cls._get(name=name, stdout=write_to_stdout, debug_file=write_to_debug_file) redirect_stream.flush()
logger.addHandler(redirect_handler) logger.info(redirect_stream.getvalue())
with contextlib.redirect_stdout(new_target=redirect_stream):
with contextlib.redirect_stderr(new_target=redirect_stream):
yield
redirect_stream.flush()
@classmethod @classmethod
def cleanup(cls, delete_debug_file: bool = True): def cleanup(cls, delete_debug_file: bool = True):

View file

@ -22,7 +22,7 @@ class TestLogger:
], ],
) )
def test_logger_info_stdout(self, capsys, log_level, outputs_to_stdout): def test_logger_info_stdout(self, capsys, log_level, outputs_to_stdout):
Logger.LEVEL = log_level Logger._LEVEL = log_level
logger = Logger.get(name="name_test") logger = Logger.get(name="name_test")
logger.info("test") logger.info("test")
@ -43,7 +43,7 @@ class TestLogger:
], ],
) )
def test_logger_debug_stdout(self, capsys, log_level, outputs_to_stdout): def test_logger_debug_stdout(self, capsys, log_level, outputs_to_stdout):
Logger.LEVEL = log_level Logger._LEVEL = log_level
logger = Logger.get(name="name_test") logger = Logger.get(name="name_test")
logger.debug("test") logger.debug("test")
@ -63,8 +63,8 @@ class TestLogger:
LoggerLevels.DEBUG, LoggerLevels.DEBUG,
], ],
) )
def test_logger_always_outputs_to_debug_file(self, capsys, log_level): def test_logger_always_outputs_to_debug_file(self, log_level):
Logger.LEVEL = log_level Logger._LEVEL = log_level
logger = Logger.get(name="name_test") logger = Logger.get(name="name_test")
logger.info("info test") logger.info("info test")
@ -74,3 +74,30 @@ class TestLogger:
lines = log_file.readlines() lines = log_file.readlines()
assert lines == ["[ytdl-sub:name_test] info test\n", "[ytdl-sub:name_test] debug test\n"] assert lines == ["[ytdl-sub:name_test] info test\n", "[ytdl-sub:name_test] debug test\n"]
@pytest.mark.parametrize(
"log_level, expected_stdout",
[
(LoggerLevels.QUIET, False),
(LoggerLevels.INFO, False),
(LoggerLevels.VERBOSE, True),
(LoggerLevels.DEBUG, True),
],
)
def test_handle_external_logs(self, capsys, log_level, expected_stdout):
Logger._LEVEL = log_level
with Logger.handle_external_logs(name="name_test"):
print("test line 1")
print("test line 2")
# Ensure it goes to stdout only if it is expected to
captured = capsys.readouterr()
if expected_stdout:
assert captured.out == "[ytdl-sub:name_test] test line 1\ntest line 2\n\n"
else:
assert not captured.out
# Ensure it always go to the debug file
with open(Logger._DEBUG_LOGGER_FILE.name, "r", encoding="utf-8") as log_file:
lines = log_file.readlines()
assert lines == ["[ytdl-sub:name_test] test line 1\n", "test line 2\n", "\n"]