logger levels working, linter and manual testing need to be tried
This commit is contained in:
parent
a08f31ae20
commit
1e68a785ba
5 changed files with 104 additions and 23 deletions
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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,6 +116,7 @@ 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 Logger.handle_external_logs(name="yt-dlp"):
|
||||||
with ytdl.YoutubeDL(ytdl_options) as ytdl_downloader:
|
with ytdl.YoutubeDL(ytdl_options) as ytdl_downloader:
|
||||||
yield ytdl_downloader
|
yield ytdl_downloader
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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!")
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
write_to_debug_file = True
|
|
||||||
|
|
||||||
logger = cls._get(name=name, stdout=write_to_stdout, debug_file=write_to_debug_file)
|
|
||||||
logger.addHandler(redirect_handler)
|
|
||||||
|
|
||||||
|
with io.StringIO() as redirect_stream:
|
||||||
with contextlib.redirect_stdout(new_target=redirect_stream):
|
with contextlib.redirect_stdout(new_target=redirect_stream):
|
||||||
with contextlib.redirect_stderr(new_target=redirect_stream):
|
with contextlib.redirect_stderr(new_target=redirect_stream):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
redirect_stream.flush()
|
redirect_stream.flush()
|
||||||
|
logger.info(redirect_stream.getvalue())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cleanup(cls, delete_debug_file: bool = True):
|
def cleanup(cls, delete_debug_file: bool = True):
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue