[FEATURE] Persisted subscription logs (#512)
This commit is contained in:
parent
022c24cc2c
commit
648027204f
12 changed files with 447 additions and 146 deletions
|
|
@ -29,7 +29,25 @@ and subscriptions.
|
||||||
.. autoclass:: ytdl_sub.config.config_validator.ConfigOptions()
|
.. autoclass:: ytdl_sub.config.config_validator.ConfigOptions()
|
||||||
:members:
|
:members:
|
||||||
:member-order: bysource
|
:member-order: bysource
|
||||||
|
:exclude-members: persist_logs
|
||||||
|
|
||||||
|
persist_logs
|
||||||
|
""""""""""""
|
||||||
|
Within ``configuration``, define whether logs from subscription downloads
|
||||||
|
should be persisted.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
configuration:
|
||||||
|
persist_logs:
|
||||||
|
logs_directory: "/path/to/log/directory"
|
||||||
|
|
||||||
|
Log files are stored as
|
||||||
|
``YYYY-mm-dd-HHMMSS.subscription_name.(success|error).log``.
|
||||||
|
|
||||||
|
.. autoclass:: ytdl_sub.config.config_validator.PersistLogsValidator()
|
||||||
|
:members:
|
||||||
|
:member-order: bysource
|
||||||
|
|
||||||
presets
|
presets
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
import argparse
|
|
||||||
import gc
|
import gc
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
|
from yt_dlp.utils import sanitize_filename
|
||||||
|
|
||||||
from ytdl_sub.cli.download_args_parser import DownloadArgsParser
|
from ytdl_sub.cli.download_args_parser import DownloadArgsParser
|
||||||
from ytdl_sub.cli.main_args_parser import parser
|
from ytdl_sub.cli.main_args_parser import parser
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
from ytdl_sub.subscriptions.subscription import Subscription
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||||
from ytdl_sub.utils.file_lock import working_directory_lock
|
from ytdl_sub.utils.file_lock import working_directory_lock
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
|
@ -19,8 +24,41 @@ logger = Logger.get()
|
||||||
_VIEW_EXTRA_ARGS_FORMATTER = "--preset _view --overrides.url {}"
|
_VIEW_EXTRA_ARGS_FORMATTER = "--preset _view --overrides.url {}"
|
||||||
|
|
||||||
|
|
||||||
|
def _maybe_write_subscription_log_file(
|
||||||
|
config: ConfigFile,
|
||||||
|
subscription: Subscription,
|
||||||
|
dry_run: bool,
|
||||||
|
exception: Optional[Exception] = None,
|
||||||
|
) -> None:
|
||||||
|
success: bool = exception is None
|
||||||
|
|
||||||
|
# If dry-run, do nothing
|
||||||
|
if dry_run:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If persisting logs is disabled, do nothing
|
||||||
|
if not config.config_options.persist_logs:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If persisting successful logs is disabled, do nothing
|
||||||
|
if success and not config.config_options.persist_logs.keep_successful_logs:
|
||||||
|
return
|
||||||
|
|
||||||
|
log_time = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||||
|
log_subscription_name = sanitize_filename(subscription.name).lower().replace(" ", "_")
|
||||||
|
log_success = "success" if success else "error"
|
||||||
|
|
||||||
|
log_filename = f"{log_time}.{log_subscription_name}.{log_success}.log"
|
||||||
|
persist_log_path = Path(config.config_options.persist_logs.logs_directory) / log_filename
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
Logger.log_exit_exception(exception=exception, log_filepath=persist_log_path)
|
||||||
|
|
||||||
|
FileHandler.copy(Logger.debug_log_filename(), persist_log_path)
|
||||||
|
|
||||||
|
|
||||||
def _download_subscriptions_from_yaml_files(
|
def _download_subscriptions_from_yaml_files(
|
||||||
config: ConfigFile, args: argparse.Namespace
|
config: ConfigFile, subscription_paths: List[str], dry_run: bool
|
||||||
) -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
|
) -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
|
||||||
"""
|
"""
|
||||||
Downloads all subscriptions from one or many subscription yaml files.
|
Downloads all subscriptions from one or many subscription yaml files.
|
||||||
|
|
@ -29,8 +67,10 @@ def _download_subscriptions_from_yaml_files(
|
||||||
----------
|
----------
|
||||||
config
|
config
|
||||||
Configuration file
|
Configuration file
|
||||||
args
|
subscription_paths
|
||||||
Arguments from argparse
|
Path to subscription files to download
|
||||||
|
dry_run
|
||||||
|
Whether to dry run or not
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -38,9 +78,9 @@ def _download_subscriptions_from_yaml_files(
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
Validation exception if main arg is specified as a subscription path
|
Exception
|
||||||
|
Any exception during download
|
||||||
"""
|
"""
|
||||||
subscription_paths: List[str] = args.subscription_paths
|
|
||||||
subscriptions: List[Subscription] = []
|
subscriptions: List[Subscription] = []
|
||||||
output: List[Tuple[Subscription, FileHandlerTransactionLog]] = []
|
output: List[Tuple[Subscription, FileHandlerTransactionLog]] = []
|
||||||
|
|
||||||
|
|
@ -51,15 +91,26 @@ def _download_subscriptions_from_yaml_files(
|
||||||
for subscription in subscriptions:
|
for subscription in subscriptions:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Beginning subscription %s for %s",
|
"Beginning subscription %s for %s",
|
||||||
("dry run" if args.dry_run else "download"),
|
("dry run" if dry_run else "download"),
|
||||||
subscription.name,
|
subscription.name,
|
||||||
)
|
)
|
||||||
logger.debug("Subscription full yaml:\n%s", subscription.as_yaml())
|
logger.debug("Subscription full yaml:\n%s", subscription.as_yaml())
|
||||||
transaction_log = subscription.download(dry_run=args.dry_run)
|
|
||||||
|
|
||||||
output.append((subscription, transaction_log))
|
try:
|
||||||
gc.collect() # Garbage collect after each subscription download
|
transaction_log = subscription.download(dry_run=dry_run)
|
||||||
Logger.cleanup() # Cleanup logger after each successful subscription download
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
|
_maybe_write_subscription_log_file(
|
||||||
|
config=config, subscription=subscription, dry_run=dry_run, exception=exc
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
output.append((subscription, transaction_log))
|
||||||
|
_maybe_write_subscription_log_file(
|
||||||
|
config=config, subscription=subscription, dry_run=dry_run
|
||||||
|
)
|
||||||
|
Logger.cleanup() # Cleanup logger after each successful subscription download
|
||||||
|
finally:
|
||||||
|
gc.collect() # Garbage collect after each subscription download
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -136,7 +187,11 @@ def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
|
||||||
|
|
||||||
with working_directory_lock(config=config):
|
with working_directory_lock(config=config):
|
||||||
if args.subparser == "sub":
|
if args.subparser == "sub":
|
||||||
transaction_logs = _download_subscriptions_from_yaml_files(config=config, args=args)
|
transaction_logs = _download_subscriptions_from_yaml_files(
|
||||||
|
config=config,
|
||||||
|
subscription_paths=args.subscription_paths,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
)
|
||||||
|
|
||||||
# One-off download
|
# One-off download
|
||||||
elif args.subparser == "dl":
|
elif args.subparser == "dl":
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from ytdl_sub.config.config_validator import ConfigValidator
|
from ytdl_sub.config.config_validator import ConfigValidator
|
||||||
from ytdl_sub.config.preset import Preset
|
from ytdl_sub.config.preset import Preset
|
||||||
|
|
@ -65,3 +66,11 @@ class ConfigFile(ConfigValidator):
|
||||||
"""
|
"""
|
||||||
config_dict = load_yaml(file_path=config_path)
|
config_dict = load_yaml(file_path=config_path)
|
||||||
return ConfigFile.from_dict(config_dict)
|
return ConfigFile.from_dict(config_dict)
|
||||||
|
|
||||||
|
def as_dict(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
The config in its dict form
|
||||||
|
"""
|
||||||
|
return self._value
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ from typing import Dict
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mergedeep import mergedeep
|
from mergedeep import mergedeep
|
||||||
|
from yt_dlp.utils import datetime_from_str
|
||||||
|
|
||||||
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
from ytdl_sub.prebuilt_presets import PREBUILT_PRESETS
|
||||||
from ytdl_sub.utils.system import IS_WINDOWS
|
from ytdl_sub.utils.system import IS_WINDOWS
|
||||||
from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
|
from ytdl_sub.validators.file_path_validators import FFmpegFileValidator
|
||||||
from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
|
from ytdl_sub.validators.file_path_validators import FFprobeFileValidator
|
||||||
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
||||||
|
from ytdl_sub.validators.validators import BoolValidator
|
||||||
from ytdl_sub.validators.validators import LiteralDictValidator
|
from ytdl_sub.validators.validators import LiteralDictValidator
|
||||||
from ytdl_sub.validators.validators import StringValidator
|
from ytdl_sub.validators.validators import StringValidator
|
||||||
|
|
||||||
|
|
@ -22,9 +24,71 @@ else:
|
||||||
_DEFAULT_FFPROBE_PATH = "/usr/bin/ffprobe"
|
_DEFAULT_FFPROBE_PATH = "/usr/bin/ffprobe"
|
||||||
|
|
||||||
|
|
||||||
|
class PersistLogsValidator(StrictDictValidator):
|
||||||
|
_required_keys = {"logs_directory"}
|
||||||
|
_optional_keys = {"keep_logs_after", "keep_successful_logs"}
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any):
|
||||||
|
super().__init__(name, value)
|
||||||
|
|
||||||
|
self._logs_directory = self._validate_key(key="logs_directory", validator=StringValidator)
|
||||||
|
|
||||||
|
self._keep_logs_after: Optional[str] = None
|
||||||
|
if keep_logs_validator := self._validate_key_if_present(
|
||||||
|
key="keep_logs_after", validator=StringValidator
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
self._keep_logs_after = datetime_from_str(keep_logs_validator.value)
|
||||||
|
except Exception as exc:
|
||||||
|
raise self._validation_exception(f"Invalid datetime string: {str(exc)}")
|
||||||
|
|
||||||
|
self._keep_successful_logs = self._validate_key(
|
||||||
|
key="keep_successful_logs", validator=BoolValidator, default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logs_directory(self) -> str:
|
||||||
|
"""
|
||||||
|
Required. The directory to store the logs in.
|
||||||
|
"""
|
||||||
|
return self._logs_directory.value
|
||||||
|
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
# @property
|
||||||
|
# def keep_logs_after(self) -> Optional[str]:
|
||||||
|
# """
|
||||||
|
# Optional. Keep logs after this date, in yt-dlp datetime format.
|
||||||
|
#
|
||||||
|
# .. code-block:: Markdown
|
||||||
|
#
|
||||||
|
# A string in the format YYYYMMDD or
|
||||||
|
# (now|today|yesterday|date)[+-][0-9](microsecond|second|minute|hour|day|week|month|year)(s)
|
||||||
|
#
|
||||||
|
# For example, ``today-1week`` means keep 1 week's worth of logs. By default, ytdl-sub will
|
||||||
|
# keep all log files.
|
||||||
|
# """
|
||||||
|
# return self._keep_logs_after
|
||||||
|
|
||||||
|
# pylint: enable=line-too-long
|
||||||
|
|
||||||
|
@property
|
||||||
|
def keep_successful_logs(self) -> bool:
|
||||||
|
"""
|
||||||
|
Optional. Whether to store logs when downloading is successful. Defaults to True.
|
||||||
|
"""
|
||||||
|
return self._keep_successful_logs.value
|
||||||
|
|
||||||
|
|
||||||
class ConfigOptions(StrictDictValidator):
|
class ConfigOptions(StrictDictValidator):
|
||||||
_required_keys = {"working_directory"}
|
_required_keys = {"working_directory"}
|
||||||
_optional_keys = {"umask", "dl_aliases", "lock_directory", "ffmpeg_path", "ffprobe_path"}
|
_optional_keys = {
|
||||||
|
"umask",
|
||||||
|
"dl_aliases",
|
||||||
|
"persist_logs",
|
||||||
|
"lock_directory",
|
||||||
|
"ffmpeg_path",
|
||||||
|
"ffprobe_path",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, name: str, value: Any):
|
def __init__(self, name: str, value: Any):
|
||||||
super().__init__(name, value)
|
super().__init__(name, value)
|
||||||
|
|
@ -38,6 +102,9 @@ class ConfigOptions(StrictDictValidator):
|
||||||
self._dl_aliases = self._validate_key_if_present(
|
self._dl_aliases = self._validate_key_if_present(
|
||||||
key="dl_aliases", validator=LiteralDictValidator
|
key="dl_aliases", validator=LiteralDictValidator
|
||||||
)
|
)
|
||||||
|
self._persist_logs = self._validate_key_if_present(
|
||||||
|
key="persist_logs", validator=PersistLogsValidator
|
||||||
|
)
|
||||||
self._lock_directory = self._validate_key(
|
self._lock_directory = self._validate_key(
|
||||||
key="lock_directory", validator=StringValidator, default=_DEFAULT_LOCK_DIRECTORY
|
key="lock_directory", validator=StringValidator, default=_DEFAULT_LOCK_DIRECTORY
|
||||||
)
|
)
|
||||||
|
|
@ -93,6 +160,13 @@ class ConfigOptions(StrictDictValidator):
|
||||||
return self._dl_aliases.dict
|
return self._dl_aliases.dict
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def persist_logs(self) -> Optional[PersistLogsValidator]:
|
||||||
|
"""
|
||||||
|
Persist logs validator. readthedocs in the validator itself!
|
||||||
|
"""
|
||||||
|
return self._persist_logs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lock_directory(self) -> str:
|
def lock_directory(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ytdl_sub import __local_version__
|
|
||||||
from ytdl_sub.cli.main_args_parser import parser
|
from ytdl_sub.cli.main_args_parser import parser
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,22 +22,11 @@ def main():
|
||||||
"""
|
"""
|
||||||
Entrypoint for ytdl-sub
|
Entrypoint for ytdl-sub
|
||||||
"""
|
"""
|
||||||
logger = Logger.get()
|
|
||||||
try:
|
try:
|
||||||
_main()
|
_main()
|
||||||
Logger.cleanup() # Ran successfully, so we can delete the debug file
|
Logger.cleanup() # Ran successfully, so we can delete the debug file
|
||||||
except ValidationException as validation_exception:
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
logger.error(validation_exception)
|
Logger.log_exit_exception(exception=exc)
|
||||||
sys.exit(1)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
logger.exception("An uncaught error occurred:")
|
|
||||||
logger.error(
|
|
||||||
"Version %s\nPlease upload the error log file '%s' and make a Github "
|
|
||||||
"issue at https://github.com/jmbannon/ytdl-sub/issues with your config and "
|
|
||||||
"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!",
|
|
||||||
__local_version__,
|
|
||||||
Logger.debug_log_filename(),
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from ytdl_sub import __local_version__
|
||||||
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,6 +93,9 @@ class Logger:
|
||||||
_DEBUG_LOGGER_FILE = tempfile.NamedTemporaryFile(prefix="ytdl-sub.", delete=False)
|
_DEBUG_LOGGER_FILE = tempfile.NamedTemporaryFile(prefix="ytdl-sub.", delete=False)
|
||||||
# pylint: enable=R1732
|
# pylint: enable=R1732
|
||||||
|
|
||||||
|
# Whether the final exception lines were added to the debug log
|
||||||
|
_LOGGED_EXIT_EXCEPTION: bool = False
|
||||||
|
|
||||||
# Keep track of all Loggers created
|
# Keep track of all Loggers created
|
||||||
_LOGGERS: List[logging.Logger] = []
|
_LOGGERS: List[logging.Logger] = []
|
||||||
|
|
||||||
|
|
@ -190,19 +196,48 @@ class Logger:
|
||||||
)
|
)
|
||||||
|
|
||||||
with StreamToLogger(logger=logger) as redirect_stream:
|
with StreamToLogger(logger=logger) as redirect_stream:
|
||||||
with contextlib.redirect_stdout(new_target=redirect_stream):
|
try:
|
||||||
with contextlib.redirect_stderr(new_target=redirect_stream):
|
with contextlib.redirect_stdout(new_target=redirect_stream):
|
||||||
yield
|
with contextlib.redirect_stderr(new_target=redirect_stream):
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
redirect_stream.flush()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cleanup(cls, delete_debug_file: bool = True):
|
def log_exit_exception(cls, exception: Exception, log_filepath: Optional[Path] = None):
|
||||||
"""
|
"""
|
||||||
Cleans up any log files left behind
|
Performs the final log before exiting from an error
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
delete_debug_file
|
exception
|
||||||
Whether to delete the debug log file. Defaults to True.
|
The exception to log
|
||||||
|
log_filepath
|
||||||
|
Optional. The filepath to the debug logs
|
||||||
|
"""
|
||||||
|
if not cls._LOGGED_EXIT_EXCEPTION:
|
||||||
|
logger = cls.get()
|
||||||
|
|
||||||
|
# Log validation exceptions as-is
|
||||||
|
if isinstance(exception, ValidationException):
|
||||||
|
logger.error(exception)
|
||||||
|
# For other uncaught errors, log as bug:
|
||||||
|
else:
|
||||||
|
logger.exception("An uncaught error occurred:")
|
||||||
|
logger.error(
|
||||||
|
"Version %s\nPlease upload the error log file '%s' and make a Github "
|
||||||
|
"issue at https://github.com/jmbannon/ytdl-sub/issues with your config and "
|
||||||
|
"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!",
|
||||||
|
__local_version__,
|
||||||
|
log_filepath if log_filepath else Logger.debug_log_filename(),
|
||||||
|
)
|
||||||
|
|
||||||
|
cls._LOGGED_EXIT_EXCEPTION = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cleanup(cls):
|
||||||
|
"""
|
||||||
|
Cleans up debug log file left behind
|
||||||
"""
|
"""
|
||||||
for logger in cls._LOGGERS:
|
for logger in cls._LOGGERS:
|
||||||
for handler in logger.handlers:
|
for handler in logger.handlers:
|
||||||
|
|
@ -210,5 +245,5 @@ class Logger:
|
||||||
|
|
||||||
cls._DEBUG_LOGGER_FILE.close()
|
cls._DEBUG_LOGGER_FILE.close()
|
||||||
|
|
||||||
if delete_debug_file:
|
FileHandler.delete(cls.debug_log_filename())
|
||||||
FileHandler.delete(cls.debug_log_filename())
|
cls._LOGGED_EXIT_EXCEPTION = False
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import contextlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
@ -11,15 +12,52 @@ from typing import List
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from expected_download import _get_files_in_directory
|
||||||
|
|
||||||
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
|
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
from ytdl_sub.utils.logger import LoggerLevels
|
||||||
|
from ytdl_sub.utils.yaml import load_yaml
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def cleanup_debug_file():
|
||||||
|
"""
|
||||||
|
Clean logs after every test
|
||||||
|
"""
|
||||||
|
Logger.set_log_level(log_level_name=LoggerLevels.DEBUG.name)
|
||||||
|
yield
|
||||||
|
Logger.cleanup()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def working_directory() -> Path:
|
def working_directory() -> str:
|
||||||
|
"""
|
||||||
|
Any time the working directory is used, ensure no files remain on cleaning it up
|
||||||
|
"""
|
||||||
|
logger = Logger.get("test")
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
yield temp_dir
|
|
||||||
|
def _assert_working_directory_empty(self, is_error: bool = False):
|
||||||
|
files = [str(file_path) for file_path in _get_files_in_directory(temp_dir)]
|
||||||
|
num_files = len(files)
|
||||||
|
if os.path.isdir(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
if not is_error:
|
||||||
|
if num_files > 0:
|
||||||
|
logger.error("left-over files in working dir:\n%s", "\n".join(files))
|
||||||
|
assert num_files == 0
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
SubscriptionDownload,
|
||||||
|
"_delete_working_directory",
|
||||||
|
new=_assert_working_directory_empty,
|
||||||
|
):
|
||||||
|
yield temp_dir
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -95,3 +133,43 @@ def preset_dict_to_subscription_yaml_generator() -> Callable:
|
||||||
FileHandler.delete(tmp_file.name)
|
FileHandler.delete(tmp_file.name)
|
||||||
|
|
||||||
return _preset_dict_to_subscription_yaml_generator
|
return _preset_dict_to_subscription_yaml_generator
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################################
|
||||||
|
# Example config fixtures
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config(config_path: Path, working_directory: str) -> ConfigFile:
|
||||||
|
config_dict = load_yaml(file_path=config_path)
|
||||||
|
config_dict["configuration"]["working_directory"] = working_directory
|
||||||
|
|
||||||
|
return ConfigFile.from_dict(config_dict)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def music_video_config_path() -> Path:
|
||||||
|
return Path("examples/music_videos_config.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def music_video_config(music_video_config_path, working_directory) -> ConfigFile:
|
||||||
|
return _load_config(music_video_config_path, working_directory)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def music_video_subscription_path() -> Path:
|
||||||
|
return Path("examples/music_videos_subscriptions.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def channel_as_tv_show_config(working_directory) -> ConfigFile:
|
||||||
|
return _load_config(
|
||||||
|
config_path=Path("examples/tv_show_config.yaml"), working_directory=working_directory
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def music_audio_config(working_directory) -> ConfigFile:
|
||||||
|
return _load_config(
|
||||||
|
config_path=Path("examples/music_audio_config.yaml"), working_directory=working_directory
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,16 @@
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from expected_download import _get_files_in_directory
|
|
||||||
|
|
||||||
from ytdl_sub.cli.main import main
|
from ytdl_sub.cli.main import main
|
||||||
from ytdl_sub.config.config_file import ConfigFile
|
|
||||||
from ytdl_sub.subscriptions.subscription import Subscription
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
from ytdl_sub.subscriptions.subscription_download import SubscriptionDownload
|
|
||||||
from ytdl_sub.utils.file_handler import FileHandler
|
from ytdl_sub.utils.file_handler import FileHandler
|
||||||
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||||
from ytdl_sub.utils.logger import Logger
|
|
||||||
from ytdl_sub.utils.yaml import load_yaml
|
|
||||||
|
|
||||||
logger = Logger.get("test")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def working_directory() -> str:
|
|
||||||
"""
|
|
||||||
Any time the working directory is used, ensure no files remain on cleaning it up
|
|
||||||
"""
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
|
||||||
|
|
||||||
def _assert_working_directory_empty(self, is_error: bool = False):
|
|
||||||
files = [str(file_path) for file_path in _get_files_in_directory(temp_dir)]
|
|
||||||
num_files = len(files)
|
|
||||||
if os.path.isdir(temp_dir):
|
|
||||||
shutil.rmtree(temp_dir)
|
|
||||||
|
|
||||||
if not is_error:
|
|
||||||
if num_files > 0:
|
|
||||||
logger.error("left-over files in working dir:\n%s", "\n".join(files))
|
|
||||||
assert num_files == 0
|
|
||||||
|
|
||||||
with patch.object(
|
|
||||||
SubscriptionDownload,
|
|
||||||
"_delete_working_directory",
|
|
||||||
new=_assert_working_directory_empty,
|
|
||||||
):
|
|
||||||
yield temp_dir
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def music_video_config_path() -> Path:
|
|
||||||
return Path("examples/music_videos_config.yaml")
|
|
||||||
|
|
||||||
|
|
||||||
def _load_config(config_path: Path, working_directory: Path) -> ConfigFile:
|
|
||||||
config_dict = load_yaml(file_path=config_path)
|
|
||||||
config_dict["configuration"]["working_directory"] = working_directory
|
|
||||||
|
|
||||||
return ConfigFile.from_dict(config_dict)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def music_video_config(music_video_config_path, working_directory) -> ConfigFile:
|
|
||||||
return _load_config(music_video_config_path, working_directory)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|
@ -77,20 +24,6 @@ def music_video_config_for_cli(music_video_config) -> str:
|
||||||
FileHandler.delete(tmp_file.name)
|
FileHandler.delete(tmp_file.name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def channel_as_tv_show_config(working_directory) -> ConfigFile:
|
|
||||||
return _load_config(
|
|
||||||
config_path=Path("examples/tv_show_config.yaml"), working_directory=working_directory
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def music_audio_config(working_directory) -> ConfigFile:
|
|
||||||
return _load_config(
|
|
||||||
config_path=Path("examples/music_audio_config.yaml"), working_directory=working_directory
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def timestamps_file_path():
|
def timestamps_file_path():
|
||||||
timestamps = [
|
timestamps = [
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,125 @@
|
||||||
import sys
|
import re
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import mergedeep
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ytdl_sub.cli.main import main
|
from ytdl_sub.cli.main import _download_subscriptions_from_yaml_files
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.config.config_file import ConfigFile
|
||||||
|
from ytdl_sub.subscriptions.subscription import Subscription
|
||||||
|
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
|
||||||
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
def test_args_after_sub_work():
|
@pytest.fixture
|
||||||
with patch.object(
|
def persist_logs_directory():
|
||||||
sys,
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
"argv",
|
yield temp_dir
|
||||||
["ytdl-sub", "-c", "examples/tv_show_config.yaml", "sub", "--log-level", "debug"],
|
|
||||||
), patch("ytdl_sub.cli.main._download_subscriptions_from_yaml_files") as mock_sub:
|
|
||||||
main()
|
|
||||||
|
|
||||||
assert mock_sub.call_count == 1
|
|
||||||
assert mock_sub.call_args.kwargs["args"].config == "examples/tv_show_config.yaml"
|
@pytest.fixture
|
||||||
assert mock_sub.call_args.kwargs["args"].subscription_paths == ["subscriptions.yaml"]
|
def persist_logs_config_factory(
|
||||||
assert mock_sub.call_args.kwargs["args"].ytdl_sub_log_level == "debug"
|
music_video_config: ConfigFile, persist_logs_directory: str
|
||||||
|
) -> Callable:
|
||||||
|
def _persist_logs_config_factory(keep_successful_logs: bool) -> ConfigFile:
|
||||||
|
return ConfigFile.from_dict(
|
||||||
|
dict(
|
||||||
|
mergedeep.merge(
|
||||||
|
music_video_config.as_dict(),
|
||||||
|
{
|
||||||
|
"configuration": {
|
||||||
|
"persist_logs": {
|
||||||
|
"logs_directory": persist_logs_directory,
|
||||||
|
"keep_successful_logs": keep_successful_logs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return _persist_logs_config_factory
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_subscription_download_factory():
|
||||||
|
def _mock_subscription_download_factory(mock_success_output: bool) -> Callable:
|
||||||
|
def _mock_download(self: Subscription, dry_run: bool) -> FileHandlerTransactionLog:
|
||||||
|
Logger.get().info(
|
||||||
|
"name=%s success=%s dry_run=%s", self.name, mock_success_output, dry_run
|
||||||
|
)
|
||||||
|
time.sleep(1)
|
||||||
|
if not mock_success_output:
|
||||||
|
raise ValueError("error")
|
||||||
|
return FileHandlerTransactionLog()
|
||||||
|
|
||||||
|
return _mock_download
|
||||||
|
|
||||||
|
return _mock_subscription_download_factory
|
||||||
|
|
||||||
|
|
||||||
|
class TestPersistLogs:
|
||||||
|
@pytest.mark.parametrize("dry_run", [True, False])
|
||||||
|
@pytest.mark.parametrize("mock_success_output", [True, False])
|
||||||
|
@pytest.mark.parametrize("keep_successful_logs", [True, False])
|
||||||
|
def test_subscription_logs_write_to_file(
|
||||||
|
self,
|
||||||
|
persist_logs_directory: str,
|
||||||
|
persist_logs_config_factory: Callable,
|
||||||
|
mock_subscription_download_factory: Callable,
|
||||||
|
music_video_subscription_path: Path,
|
||||||
|
dry_run: bool,
|
||||||
|
mock_success_output: bool,
|
||||||
|
keep_successful_logs: bool,
|
||||||
|
):
|
||||||
|
num_subscriptions = 2
|
||||||
|
config = persist_logs_config_factory(keep_successful_logs=keep_successful_logs)
|
||||||
|
subscription_paths = [str(music_video_subscription_path)] * num_subscriptions
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
Subscription,
|
||||||
|
"download",
|
||||||
|
new=mock_subscription_download_factory(mock_success_output=mock_success_output),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
_download_subscriptions_from_yaml_files(
|
||||||
|
config=config, subscription_paths=subscription_paths, dry_run=dry_run
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
assert not mock_success_output
|
||||||
|
|
||||||
|
log_directory_files = list(Path(persist_logs_directory).rglob("*"))
|
||||||
|
|
||||||
|
# If dry run or success but success logging disabled, expect 0 log files
|
||||||
|
if dry_run or (mock_success_output and not keep_successful_logs):
|
||||||
|
assert len(log_directory_files) == 0
|
||||||
|
return
|
||||||
|
# If not success, expect 1 log file
|
||||||
|
elif not mock_success_output:
|
||||||
|
assert len(log_directory_files) == 1
|
||||||
|
log_path = log_directory_files[0]
|
||||||
|
assert bool(re.match(r"\d{4}-\d{2}-\d{2}-\d{6}\.john_smith\.error\.log", log_path.name))
|
||||||
|
with open(log_path, "r", encoding="utf-8") as log_file:
|
||||||
|
assert log_file.readlines()[-1] == (
|
||||||
|
f"Please upload the error log file '{str(log_path)}' and make a Github issue "
|
||||||
|
f"at https://github.com/jmbannon/ytdl-sub/issues with your config and "
|
||||||
|
f"command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!\n"
|
||||||
|
)
|
||||||
|
# If success and success logging, expect 3 log files
|
||||||
|
else:
|
||||||
|
assert len(log_directory_files) == num_subscriptions
|
||||||
|
for log_file_path in log_directory_files:
|
||||||
|
assert bool(
|
||||||
|
re.match(
|
||||||
|
r"\d{4}-\d{2}-\d{2}-\d{6}\.john_smith\.success\.log", log_file_path.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
with open(log_file_path, "r", encoding="utf-8") as log_file:
|
||||||
|
assert (
|
||||||
|
log_file.readlines()[-1]
|
||||||
|
== "[ytdl-sub] name=john_smith success=True dry_run=False\n"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from src.ytdl_sub import __local_version__
|
||||||
from src.ytdl_sub.main import main
|
from src.ytdl_sub.main import main
|
||||||
from ytdl_sub.utils.exceptions import ValidationException
|
from ytdl_sub.utils.exceptions import ValidationException
|
||||||
from ytdl_sub.utils.logger import Logger
|
from ytdl_sub.utils.logger import Logger
|
||||||
|
from ytdl_sub.utils.logger import LoggerLevels
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -66,3 +67,16 @@ def test_main_uncaught_error(capsys, mock_sys_exit, expected_uncaught_error_mess
|
||||||
assert mock_error.call_args.args[0] == expected_uncaught_error_message
|
assert mock_error.call_args.args[0] == expected_uncaught_error_message
|
||||||
assert mock_error.call_args.args[1] == __local_version__
|
assert mock_error.call_args.args[1] == __local_version__
|
||||||
assert mock_error.call_args.args[2] == Logger.debug_log_filename()
|
assert mock_error.call_args.args[2] == Logger.debug_log_filename()
|
||||||
|
|
||||||
|
|
||||||
|
def test_args_after_sub_work(mock_sys_exit):
|
||||||
|
with mock_sys_exit(expected_exit_code=0), patch.object(
|
||||||
|
sys,
|
||||||
|
"argv",
|
||||||
|
["ytdl-sub", "-c", "examples/tv_show_config.yaml", "sub", "--log-level", "verbose"],
|
||||||
|
), patch("ytdl_sub.cli.main._download_subscriptions_from_yaml_files") as mock_sub:
|
||||||
|
main()
|
||||||
|
|
||||||
|
assert mock_sub.call_count == 1
|
||||||
|
assert mock_sub.call_args.kwargs["subscription_paths"] == ["subscriptions.yaml"]
|
||||||
|
assert Logger._LOGGER_LEVEL == LoggerLevels.VERBOSE
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable:
|
||||||
playlist_index: int = 1,
|
playlist_index: int = 1,
|
||||||
playlist_count: int = 1,
|
playlist_count: int = 1,
|
||||||
is_youtube_channel: bool = False,
|
is_youtube_channel: bool = False,
|
||||||
|
mock_download_to_working_dir: bool = True,
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
entry_dict = {
|
entry_dict = {
|
||||||
UID: uid,
|
UID: uid,
|
||||||
|
|
@ -84,12 +85,14 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable:
|
||||||
]
|
]
|
||||||
|
|
||||||
# Create mock video file
|
# Create mock video file
|
||||||
copy_file_fixture(
|
if mock_download_to_working_dir:
|
||||||
fixture_name="sample_vid.mp4", output_file_path=mock_downloaded_file_path(f"{uid}.mp4")
|
copy_file_fixture(
|
||||||
)
|
fixture_name="sample_vid.mp4",
|
||||||
copy_file_fixture(
|
output_file_path=mock_downloaded_file_path(f"{uid}.mp4"),
|
||||||
fixture_name="thumb.jpg", output_file_path=mock_downloaded_file_path(f"{uid}.jpg")
|
)
|
||||||
)
|
copy_file_fixture(
|
||||||
|
fixture_name="thumb.jpg", output_file_path=mock_downloaded_file_path(f"{uid}.jpg")
|
||||||
|
)
|
||||||
return entry_dict
|
return entry_dict
|
||||||
|
|
||||||
return _mock_entry_dict_factory
|
return _mock_entry_dict_factory
|
||||||
|
|
@ -124,36 +127,33 @@ def mock_download_collection_entries(
|
||||||
):
|
):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _mock_download_collection_entries_factory(is_youtube_channel: bool):
|
def _mock_download_collection_entries_factory(is_youtube_channel: bool):
|
||||||
def _(**kwargs):
|
|
||||||
return mock_entry_dict_factory(**kwargs)
|
|
||||||
|
|
||||||
def _write_entries_to_working_dir(*args, **kwargs) -> List[Dict]:
|
def _write_entries_to_working_dir(*args, **kwargs) -> List[Dict]:
|
||||||
if (len(args[0].collection.urls.list) == 1) or (
|
if (len(args[0].collection.urls.list) == 1) or (
|
||||||
"season.2" in kwargs["url"] and len(args[0].download_options.urls.list) > 1
|
"season.2" in kwargs["url"] and len(args[0].download_options.urls.list) > 1
|
||||||
):
|
):
|
||||||
return [
|
return [
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="21-1",
|
uid="21-1",
|
||||||
upload_date="20210808",
|
upload_date="20210808",
|
||||||
playlist_index=1,
|
playlist_index=1,
|
||||||
playlist_count=4,
|
playlist_count=4,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
), # 1
|
), # 1
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-1",
|
uid="20-1",
|
||||||
upload_date="20200808",
|
upload_date="20200808",
|
||||||
playlist_index=2,
|
playlist_index=2,
|
||||||
playlist_count=4,
|
playlist_count=4,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
), # 2 98
|
), # 2 98
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-2",
|
uid="20-2",
|
||||||
upload_date="20200808",
|
upload_date="20200808",
|
||||||
playlist_index=3,
|
playlist_index=3,
|
||||||
playlist_count=4,
|
playlist_count=4,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
), # 1 99
|
), # 1 99
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-3",
|
uid="20-3",
|
||||||
upload_date="20200807",
|
upload_date="20200807",
|
||||||
playlist_index=4,
|
playlist_index=4,
|
||||||
|
|
@ -163,35 +163,36 @@ def mock_download_collection_entries(
|
||||||
]
|
]
|
||||||
return [
|
return [
|
||||||
# 20-3 should resolve to collection 1 (which is season 2)
|
# 20-3 should resolve to collection 1 (which is season 2)
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-3",
|
uid="20-3",
|
||||||
upload_date="20200807",
|
upload_date="20200807",
|
||||||
playlist_index=1,
|
playlist_index=1,
|
||||||
playlist_count=5,
|
playlist_count=5,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
|
mock_download_to_working_dir=False,
|
||||||
),
|
),
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-4",
|
uid="20-4",
|
||||||
upload_date="20200806",
|
upload_date="20200806",
|
||||||
playlist_index=2,
|
playlist_index=2,
|
||||||
playlist_count=5,
|
playlist_count=5,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
),
|
),
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-5",
|
uid="20-5",
|
||||||
upload_date="20200706",
|
upload_date="20200706",
|
||||||
playlist_index=3,
|
playlist_index=3,
|
||||||
playlist_count=5,
|
playlist_count=5,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
),
|
),
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-6",
|
uid="20-6",
|
||||||
upload_date="20200706",
|
upload_date="20200706",
|
||||||
playlist_index=4,
|
playlist_index=4,
|
||||||
playlist_count=5,
|
playlist_count=5,
|
||||||
is_youtube_channel=is_youtube_channel,
|
is_youtube_channel=is_youtube_channel,
|
||||||
),
|
),
|
||||||
_(
|
mock_entry_dict_factory(
|
||||||
uid="20-7",
|
uid="20-7",
|
||||||
upload_date="20200606",
|
upload_date="20200606",
|
||||||
playlist_index=5,
|
playlist_index=5,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,6 @@ from ytdl_sub.utils.logger import Logger
|
||||||
from ytdl_sub.utils.logger import LoggerLevels
|
from ytdl_sub.utils.logger import LoggerLevels
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def cleanup_debug_file():
|
|
||||||
Logger.set_log_level(log_level_name=LoggerLevels.DEBUG.name)
|
|
||||||
yield
|
|
||||||
Logger.cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
class TestLogger:
|
class TestLogger:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"log_level",
|
"log_level",
|
||||||
|
|
@ -114,7 +107,7 @@ class TestLogger:
|
||||||
"[ytdl-sub:name_test] debug test\n",
|
"[ytdl-sub:name_test] debug test\n",
|
||||||
]
|
]
|
||||||
|
|
||||||
Logger.cleanup(delete_debug_file=True)
|
Logger.cleanup()
|
||||||
assert not os.path.isfile(Logger._DEBUG_LOGGER_FILE.name)
|
assert not os.path.isfile(Logger._DEBUG_LOGGER_FILE.name)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue