diff --git a/pyproject.toml b/pyproject.toml index d6a2369..a875668 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,10 +25,9 @@ classifiers = [ ] dependencies = [ "Jinja2>=3.1.1,<3.2.0", - "dakarabase>=1.4.2,<1.5.0", + "dakarabase>=2.0.0,<2.1.0", "filetype>=1.2.0,<1.3.0", "packaging>=21.3,<22.0", - "path>=16.4.0,<16.5.0", "python-mpv-jsonipc>=1.1.13,<1.2.0", "python-vlc>=3.0.18121,<3.1.0,!=3.0.12117", "setuptools>=68", diff --git a/src/dakara_player/__main__.py b/src/dakara_player/__main__.py index ef82307..b4fd4da 100755 --- a/src/dakara_player/__main__.py +++ b/src/dakara_player/__main__.py @@ -34,13 +34,13 @@ handle_config_incomplete = generate_exception_handler( DakaraError, "Config may be incomplete, please check '{}'".format( - directories.user_config_dir / CONFIG_FILE + directories.user_config_path / CONFIG_FILE ), ) handle_parameter_error = generate_exception_handler( ParameterError, "Config may be incomplete, please check '{}'".format( - directories.user_config_dir / CONFIG_FILE + directories.user_config_path / CONFIG_FILE ), ) @@ -123,7 +123,7 @@ def play(args): with handle_config_not_found(): create_logger() config = Config(CONFIG_PREFIX) - config.load_file(directories.user_config_dir / CONFIG_FILE) + config.load_file(directories.user_config_path / CONFIG_FILE) config.check_mandatory_keys(["player", "server"]) config.set_debug(args.debug) set_loglevel(config) diff --git a/src/dakara_player/audio.py b/src/dakara_player/audio.py index e86b142..367edb9 100644 --- a/src/dakara_player/audio.py +++ b/src/dakara_player/audio.py @@ -7,13 +7,13 @@ def get_audio_files(filepath): """Get audio files with the same name as provided file. Args: - filepath (path.Path): Path of the initial file. + filepath (pathlib.Path): Path of the initial file. Returns: - list of path.Path: List of paths of audio files. + list of pathlib.Path: List of paths of audio files. """ # list files with similar stem - items = filepath.dirname().glob("{}.*".format(filepath.stem)) + items = filepath.parent.glob(f"{filepath.stem}.*") return [item for item in items if item != filepath and is_audio_file(item)] @@ -21,7 +21,7 @@ def is_audio_file(file_path): """Detect if a file is audio file based on standard magic numbers. Args: - file_path (path.Path): Path of the file to investigate. + file_path (pathlib.Path): Path of the file to investigate. Returns: bool: `True` if the file is an audio file, `False` otherwise. diff --git a/src/dakara_player/background.py b/src/dakara_player/background.py index 47eb3bd..7a041b3 100644 --- a/src/dakara_player/background.py +++ b/src/dakara_player/background.py @@ -2,9 +2,10 @@ import logging from importlib.resources import path +from pathlib import Path +from shutil import copy from dakara_base.exceptions import DakaraError -from path import Path logger = logging.getLogger(__name__) @@ -27,6 +28,7 @@ class BackgroundLoader: - `something.png` and you use the following configuration: + >>> from pathlib import Path >>> loader = BackgroundLoader( ... destination=Path("/destination"), ... directory=Path("/directory/custom"), @@ -42,24 +44,24 @@ class BackgroundLoader: >>> loader.load() >>> loader.backgrounds { - "idle": "/destination/idle.png", - "transition": "/destination/transition.png", - "other": "/destination/something.png" + "idle": Path("/destination/idle.png"), + "transition": Path("/destination/transition.png"), + "other": Path("/destination/something.png") } Args: - destination (path.Path): Where to copy found background files. + destination (pathlib.Path): Where to copy found background files. package (str): Package checked for backgrounds by default. - directory (path.Path): Custom directory checked for backgrounds. + directory (pathlib.Path): Custom directory checked for backgrounds. filenames (dict): Dictionary of background filenames. The key is the background name, the value the background file name. Attributes: backgrounds (dict): Dictionary of background file paths. The key is the background name, the value the background file path. - destination (path.Path): Where to copy found background files. + destination (pathlib.Path): Where to copy found background files. package (str): Package checked for backgrounds by default. - directory (path.Path): Custom directory checked for backgrounds. + directory (pathlib.Path): Custom directory checked for backgrounds. filenames (dict): Dictionary of background filenames. The key is the background name, the value the background file name. """ @@ -73,7 +75,7 @@ def __init__( ): self.destination = destination self.package = package - self.directory = directory or Path() + self.directory = directory self.filenames = filenames or {} self.backgrounds = {} @@ -91,16 +93,16 @@ def get_background_path(self, background_name, file_name): file_name (str): Name of the background file. Returns: - path.Path: Absolute path to the background file. + pathlib.Path: Absolute path to the background file. """ # trying to load from custom directory - if self.directory: + if self.directory is not None: file_path = self.directory / file_name if file_path.exists(): logger.debug( "Loading custom %s background file '%s'", background_name, file_name ) - return file_path.copy(self.destination) + return Path(copy(file_path, self.destination)) # trying to load from package by default try: @@ -111,7 +113,7 @@ def get_background_path(self, background_name, file_name): file_name, ) file_path = Path(file) - return file_path.copy(self.destination) + return Path(copy(file_path, self.destination)) except FileNotFoundError as error: raise BackgroundNotFoundError( diff --git a/src/dakara_player/font.py b/src/dakara_player/font.py index 87a710b..83cf4d7 100644 --- a/src/dakara_player/font.py +++ b/src/dakara_player/font.py @@ -5,9 +5,10 @@ import platform from abc import ABC, abstractmethod from importlib.resources import contents, path +from pathlib import Path +from shutil import copy from dakara_base.exceptions import DakaraError -from path import Path logger = logging.getLogger(__name__) @@ -77,7 +78,7 @@ def get_font_name_list(self): font_file_name_list = [ file for file in contents(self.package) - if Path(file).ext.lower() in FONT_EXTENSIONS + if Path(file).suffix.lower() in FONT_EXTENSIONS ] logger.debug("Found %i font(s) to load", len(font_file_name_list)) @@ -87,7 +88,7 @@ def get_font_path_iterator(self): """Give font paths in font package. Yields: - path.Path: Absolute path to the font, from the package. + pathlib.Path: Absolute path to the font, from the package. """ for font_file_name in self.get_font_name_list(): with path(self.package, font_file_name) as font_file_path: @@ -116,9 +117,9 @@ class FontLoaderLinux(FontLoader): Attributes: package (str): Package checked for font files. - font_loaded (dict of path.Path): List of loaded fonts. The key is the - font file name and the value is the path of the installed font in - user directory. + font_loaded (dict of pathlib.Path): List of loaded fonts. The key is + the font file name and the value is the path of the installed font + in user directory. """ GREETINGS = "Font loader for Linux selected" @@ -132,71 +133,71 @@ def __init__(self, *args, **kwargs): # create list of fonts self.fonts_loaded = {} - def get_system_font_path_list(self): + def get_system_font_name_list(self): """Retrieve the list of system fonts. Returns: - list of path.Path: List of font paths. + list of pathlib.Path: List of font paths. """ - return list(self.FONT_DIR_SYSTEM.walkfiles()) + return [path.name for path in self.FONT_DIR_SYSTEM.rglob("*")] - def get_user_font_path_list(self): + def get_user_font_name_list(self): """Retrieve the list of user fonts. Returns: - list of path.Path: List of font paths. + list of pathlib.Path: List of font paths. """ - return list(self.FONT_DIR_USER.expanduser().walkfiles()) + return [path.name for path in self.FONT_DIR_USER.expanduser().rglob("*")] def load(self): """Load the fonts.""" # ensure that the user font directory exists - self.FONT_DIR_USER.expanduser().mkdir_p() + self.FONT_DIR_USER.expanduser().mkdir(parents=True, exist_ok=True) # get system and user font files - system_font_path_list = self.get_system_font_path_list() - user_font_path_list = self.get_user_font_path_list() + system_font_name_list = self.get_system_font_name_list() + user_font_name_list = self.get_user_font_name_list() # load fonts for font_file_path in self.get_font_path_iterator(): - self.load_font(font_file_path, system_font_path_list, user_font_path_list) + self.load_font(font_file_path, system_font_name_list, user_font_name_list) - def load_font(self, font_file_path, system_font_path_list, user_font_path_list): + def load_font(self, font_file_path, system_font_name_list, user_font_name_list): """Load the provided font. Args: - font_file_path (path.Path): Absolute path of the font to load. - system_font_path_list (list of path.Path): List of absolute paths - of system fonts. - user_font_path_list (list of path.Path): List of absolute paths of - user fonts. + font_file_path (pathlib.Path): Absolute path of the font to load. + system_font_name_list (list of pathlib.Path): List of system fonts + name. + user_font_name_list (list of pathlib.Path): List of user fonts + name. """ # get font file name - font_file_name = font_file_path.basename() + font_file_name = font_file_path.name # check if the font is installed at system level - if any(font_file_name in path for path in system_font_path_list): + if font_file_name in system_font_name_list: logger.debug("Font '%s' found in system directory", font_file_name) return # check if the font is installed at user level - if any(font_file_name in path for path in user_font_path_list): + if font_file_name in user_font_name_list: logger.debug("Font '%s' found in user directory", font_file_name) return # check if the font exists as broken link at user level # in this case remove it and continue execution font_file_user_path = self.FONT_DIR_USER.expanduser() / font_file_name - if font_file_user_path.islink(): + if font_file_user_path.is_symlink(): logger.debug( "Dead symbolic link found for font '%s' in user directory, " "removing it", font_file_name, ) - font_file_user_path.unlink_p() + font_file_user_path.unlink(missing_ok=True) # then, if the font is not installed, load by copying it - font_file_path.copy(font_file_user_path) + copy(font_file_path, font_file_user_path) # register the font self.fonts_loaded[font_file_name] = font_file_user_path @@ -250,8 +251,8 @@ class FontLoaderWindows(FontLoader): Attributes: package (str): Package checked for font files. - font_loaded (dict of path.Path): List of loaded fonts. The key is the - font file name and the value is the path of font used at + font_loaded (dict of pathlib.Path): List of loaded fonts. The key is + the font file name and the value is the path of font used at installation. """ @@ -279,7 +280,7 @@ def load_font(self, font_file_path): """Load the provided font. Args: - font_file_path (path.Path): Absolute path of the font to load. + font_file_path (pathlib.Path): Absolute path of the font to load. """ success = ctypes.windll.gdi32.AddFontResourceW(font_file_path) if success: diff --git a/src/dakara_player/media_player/base.py b/src/dakara_player/media_player/base.py index e0431ba..1469481 100644 --- a/src/dakara_player/media_player/base.py +++ b/src/dakara_player/media_player/base.py @@ -3,12 +3,12 @@ import logging from abc import ABC, abstractmethod from functools import wraps +from pathlib import Path from threading import Timer from dakara_base.directory import directories from dakara_base.exceptions import DakaraError from dakara_base.safe_workers import Worker -from path import Path from dakara_player.audio import get_audio_files from dakara_player.background import BackgroundLoader @@ -43,7 +43,7 @@ class MediaPlayer(Worker, ABC): errors (queue.Queue): Error queue to communicate the exception to the main thread. config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. Attributes: stop (threading.Event): Stop event that notify to stop the entire @@ -52,14 +52,14 @@ class MediaPlayer(Worker, ABC): main thread. player_name (str): Name of the media player. fullscreen (bool): If `True`, the media player will be fullscreen. - kara_folder_path (path.Path): Path to the karaoke folder. + kara_folder_path (pathlib.Path): Path to the karaoke folder. playlist_entry (dict): Playlist entyr object. callbacks (dict): High level callbacks associated with the media player. warn_long_exit (bool): If `True`, display a warning message if the media player takes too long to stop. durations (dict of int): Duration of the different screens in seconds. - text_paths (dict of path.Path): Path of the different text screens. + text_paths (dict of pathlib.Path): Path of the different text screens. text_generator (dakara_player.text.TextGenerator): Text generator instance. background_loader @@ -88,7 +88,7 @@ def init_worker(self, config, tempdir, warn_long_exit=True): Args: config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. warn_long_exit (bool): If `True`, the class will display a warning message if the media player takes too long to stop. """ @@ -125,7 +125,7 @@ def init_worker(self, config, tempdir, warn_long_exit=True): config_texts = config.get("templates") or {} self.text_generator = TextGenerator( package="dakara_player.resources.templates", - directory=directories.user_data_dir / "player" / "templates", + directory=directories.user_data_path / "player" / "templates", filenames={ "transition": config_texts.get( "transition_template_name", TRANSITION_TEXT_NAME @@ -139,7 +139,7 @@ def init_worker(self, config, tempdir, warn_long_exit=True): self.background_loader = BackgroundLoader( destination=tempdir, package="dakara_player.resources.backgrounds", - directory=directories.user_data_dir / "player" / "backgrounds", + directory=directories.user_data_path / "player" / "backgrounds", filenames={ "transition": config_backgrounds.get( "transition_background_name", TRANSITION_BG_NAME @@ -164,7 +164,7 @@ def init_player(self, config, tempdir): Args: config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. """ def load(self): @@ -351,7 +351,7 @@ def set_playlist_entry(self, playlist_entry, autoplay=True): """ file_path = self.kara_folder_path / playlist_entry["song"]["file_path"] - if not file_path.isfile(): + if not file_path.is_file(): logger.error("File not found '%s'", file_path) self.callbacks["error"](playlist_entry["id"], "File not found") self.callbacks["could_not_play"](playlist_entry["id"]) @@ -373,7 +373,7 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): Args: playlist_entry (dict): Playlist entry object. - file_path (path.Path): Absolute path to the song file. + file_path (pathlib.Path): Absolute path to the song file. autoplay (bool): If `True`, start to play transition screen as soon as possible (i.e. as soon as the transition screen media is ready). The song media is prepared when the transition screen @@ -410,10 +410,10 @@ def get_instrumental_file(filepath): Consider that this instrumental file should be the only one audio file found. Args: - filepath (path.Path): Path to the media file. + filepath (pathlib.Path): Path to the media file. Returns: - path.Path: Path to the instrumental file. None if not found. + pathlib.Path: Path to the instrumental file. None if not found. """ audio_files = get_audio_files(filepath) @@ -487,7 +487,7 @@ def generate_text(self, what, **kwargs): have no fade in effect. Returns: - path.Path: Path of the text screen. + pathlib.Path: Path of the text screen. Raises: ValueError: If the type of screen to generate is unknown. diff --git a/src/dakara_player/media_player/mpv.py b/src/dakara_player/media_player/mpv.py index 50ba756..ce47715 100644 --- a/src/dakara_player/media_player/mpv.py +++ b/src/dakara_player/media_player/mpv.py @@ -3,6 +3,7 @@ import logging import re from abc import ABC +from pathlib import Path from dakara_base.exceptions import DakaraError from dakara_base.safe_workers import safe @@ -182,7 +183,7 @@ class MediaPlayerMpvOld(MediaPlayerMpv): errors (queue.Queue): Error queue to communicate the exception to the main thread. config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. Attributes: stop (threading.Event): Stop event that notify to stop the entire @@ -191,14 +192,14 @@ class MediaPlayerMpvOld(MediaPlayerMpv): main thread. player_name (str): Name of mpv. fullscreen (bool): If `True`, mpv will be fullscreen. - kara_folder_path (path.Path): Path to the karaoke folder. + kara_folder_path (pathlib.Path): Path to the karaoke folder. playlist_entry (dict): Playlist entyr object. callbacks (dict): High level callbacks associated with the media player. warn_long_exit (bool): If `True`, display a warning message if the media player takes too long to stop. durations (dict of int): Duration of the different screens in seconds. - text_paths (dict of path.Path): Path of the different text screens. + text_paths (dict of pathlib.Path): Path of the different text screens. text_generator (dakara_player.text_generator.TextGenerator): Text generator instance. background_loader @@ -217,7 +218,7 @@ def init_player(self, config, tempdir): Args: config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. """ # set mpv player options and logging loglevel = config.get("loglevel", "info") @@ -343,15 +344,17 @@ def is_playing_this(self, what): assert len(playlist) == 1, "Too many entries in mpv internal playlist" - media = playlist[0] - media_path = media.get("filename") + media_raw = playlist[0].get("filename") + + if media_raw is None: + return False + + media_path = Path(media_raw) if what == "idle": return media_path == self.background_loader.backgrounds["idle"] - return ( - media_path == self.playlist_entry_data[what].path and media_path is not None - ) + return media_path == self.playlist_entry_data[what].path def play(self, what): """Request mpv to play something. @@ -378,21 +381,21 @@ def play(self, what): return self.generate_text("idle") - self.player.play(self.background_loader.backgrounds["idle"]) - self.player.sub_files = self.text_paths["idle"] + self.player.play(str(self.background_loader.backgrounds["idle"])) + self.player.sub_files = str(self.text_paths["idle"]) return if what == "transition": - self.player.play(self.playlist_entry_data["transition"].path) - self.player.sub_files = self.text_paths["transition"] + self.player.play(str(self.playlist_entry_data["transition"].path)) + self.player.sub_files = str(self.text_paths["transition"]) self.player.end = str(self.durations["transition"]) return if what == "song": # manage instrumental track/file - path_audio = self.playlist_entry_data["song"].path_audio + path_audio = str(self.playlist_entry_data["song"].path_audio) if path_audio: if path_audio == "self": # mpv use different index for each track, so we can safely request @@ -406,9 +409,11 @@ def play(self, what): # if the subtitle file cannot be discovered, do not request it if self.playlist_entry_data["song"].path_subtitle: - self.player.sub_files = [self.playlist_entry_data["song"].path_subtitle] + self.player.sub_files = [ + str(self.playlist_entry_data["song"].path_subtitle) + ] - self.player.play(self.playlist_entry_data["song"].path) + self.player.play(str(self.playlist_entry_data["song"].path)) return @@ -519,7 +524,7 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): Args: playlist_entry (dict): Playlist entry object. - file_path (path.Path): Absolute path to the song file. + file_path (pathlib.Path): Absolute path to the song file. autoplay (bool): If `True`, start to play transition screen as soon as possible (i.e. as soon as the transition screen media is ready). The song media is prepared when the transition screen @@ -543,9 +548,9 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): # manually set the subtitles as a workaround for the matching of # mpv being too permissive - path_without_ext = file_path.dirname() / file_path.stem + path_without_ext = file_path.parent / file_path.stem for subtitle_extension in SUBTITLE_EXTENSIONS: - path_subtitle = path_without_ext + subtitle_extension + path_subtitle = path_without_ext.with_suffix(subtitle_extension) if path_subtitle.exists(): break @@ -570,7 +575,7 @@ def manage_instrumental(self, playlist_entry, file_path): Args: playlist_entry (dict): Playlist entry data. Must contain the key `use_instrumental`. - file_path (path.Path): Path of the song file. + file_path (pathlib.Path): Path of the song file. """ # get instrumental file if possible audio_path = self.get_instrumental_file(file_path) @@ -777,7 +782,7 @@ class MediaPlayerMpvPost0330(MediaPlayerMpvOld): errors (queue.Queue): Error queue to communicate the exception to the main thread. config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. Attributes: stop (threading.Event): Stop event that notify to stop the entire @@ -786,14 +791,14 @@ class MediaPlayerMpvPost0330(MediaPlayerMpvOld): main thread. player_name (str): Name of mpv. fullscreen (bool): If `True`, mpv will be fullscreen. - kara_folder_path (path.Path): Path to the karaoke folder. + kara_folder_path (pathlib.Path): Path to the karaoke folder. playlist_entry (dict): Playlist entyr object. callbacks (dict): High level callbacks associated with the media player. warn_long_exit (bool): If `True`, display a warning message if the media player takes too long to stop. durations (dict of int): Duration of the different screens in seconds. - text_paths (dict of path.Path): Path of the different text screens. + text_paths (dict of pathlib.Path): Path of the different text screens. text_generator (dakara_player.text_generator.TextGenerator): Text generator instance. background_loader @@ -840,25 +845,28 @@ def was_playing_this(self, what, id): return self.is_playing_this(what, entries[0]["filename"]) - def is_playing_this(self, what, media_path=None): + def is_playing_this(self, what, current_media_path=None): """Query if mpv is playing the requested media type. Args: what (str): Tell if mpv current track is of the requested type, but not if it is actually playing it (it can be in pause). - media_path (path.Path): Optional media path. + current_media_path (pathlib.Path): Optional current media path. Returns: bool: `True` if mpv is playing the requested type. """ - media_path = media_path or self.player.path + media_raw = current_media_path or self.player.path + + if media_raw is None: + return False + + media_path = Path(media_raw) if what == "idle": return media_path == self.background_loader.backgrounds["idle"] - return ( - media_path == self.playlist_entry_data[what].path and media_path is not None - ) + return media_path == self.playlist_entry_data[what].path @safe def handle_end_file(self, event): @@ -921,7 +929,7 @@ class MediaPlayerMpvPost0340(MediaPlayerMpvPost0330): errors (queue.Queue): Error queue to communicate the exception to the main thread. config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. Attributes: stop (threading.Event): Stop event that notify to stop the entire @@ -930,14 +938,14 @@ class MediaPlayerMpvPost0340(MediaPlayerMpvPost0330): main thread. player_name (str): Name of mpv. fullscreen (bool): If `True`, mpv will be fullscreen. - kara_folder_path (path.Path): Path to the karaoke folder. + kara_folder_path (pathlib.Path): Path to the karaoke folder. playlist_entry (dict): Playlist entyr object. callbacks (dict): High level callbacks associated with the media player. warn_long_exit (bool): If `True`, display a warning message if the media player takes too long to stop. durations (dict of int): Duration of the different screens in seconds. - text_paths (dict of path.Path): Path of the different text screens. + text_paths (dict of pathlib.Path): Path of the different text screens. text_generator (dakara_player.text_generator.TextGenerator): Text generator instance. background_loader diff --git a/src/dakara_player/media_player/vlc.py b/src/dakara_player/media_player/vlc.py index 9e5ea6a..44e7064 100644 --- a/src/dakara_player/media_player/vlc.py +++ b/src/dakara_player/media_player/vlc.py @@ -51,7 +51,7 @@ class MediaPlayerVlc(MediaPlayer): errors (queue.Queue): Error queue to communicate the exception to the main thread. config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. Attributes: stop (threading.Event): Stop event that notify to stop the entire @@ -60,14 +60,14 @@ class MediaPlayerVlc(MediaPlayer): main thread. player_name (str): Name of VLC. fullscreen (bool): If `True`, VLC will be fullscreen. - kara_folder_path (path.Path): Path to the karaoke folder. + kara_folder_path (pathlib.Path): Path to the karaoke folder. playlist_entry (dict): Playlist entyr object. callbacks (dict): High level callbacks associated with the media player. warn_long_exit (bool): If `True`, display a warning message if the media player takes too long to stop. durations (dict of int): Duration of the different screens in seconds. - text_paths (dict of path.Path): Path of the different text screens. + text_paths (dict of pathlib.Path): Path of the different text screens. text_generator (dakara_player.text_generator.TextGenerator): Text generator instance. background_loader @@ -104,7 +104,7 @@ def init_player(self, config, tempdir): Args: config (dict): Dictionary of configuration. - tempdir (path.Path): Path of the temporary directory. + tempdir (pathlib.Path): Path of the temporary directory. """ # parameters config_vlc = config.get("vlc") or {} @@ -277,7 +277,7 @@ def play(self, what): if what == "idle": # create idle screen media media = self.instance.media_new_path( - self.background_loader.backgrounds["idle"] + str(self.background_loader.backgrounds["idle"]) ) media.add_options( @@ -415,7 +415,7 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): Args: playlist_entry (dict): Playlist entry object. - file_path (path.Path): Absolute path to the song file. + file_path (pathlib.Path): Absolute path to the song file. autoplay (bool): If `True`, start to play transition screen as soon as possible (i.e. as soon as the transition screen media is ready). The song media is prepared when the transition screen @@ -423,7 +423,7 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): """ # create transition screen media media_transition = self.instance.media_new_path( - self.background_loader.backgrounds["transition"] + str(self.background_loader.backgrounds["transition"]) ) media_transition.add_options( @@ -447,7 +447,7 @@ def set_playlist_entry_player(self, playlist_entry, file_path, autoplay): self.play("transition") # create song media - media_song = self.instance.media_new_path(file_path) + media_song = self.instance.media_new_path(str(file_path)) media_song.add_options(*self.media_parameters) media_song.parse() set_metadata( @@ -469,7 +469,7 @@ def manage_instrumental(self, playlist_entry, file_path): Args: playlist_entry (dict): Playlist entry data. Must contain the key `use_instrumental`. - file_path (path.Path): Path of the song file. + file_path (pathlib.Path): Path of the song file. """ # get instrumental file if possible audio_path = self.get_instrumental_file(file_path) diff --git a/src/dakara_player/mrl.py b/src/dakara_player/mrl.py index 79c9024..b096803 100644 --- a/src/dakara_player/mrl.py +++ b/src/dakara_player/mrl.py @@ -1,10 +1,8 @@ """Manage MRL.""" -import pathlib +from pathlib import Path from urllib.parse import unquote, urlparse -from path import Path - def mrl_to_path(file_mrl): """Convert a MRL to a filesystem path. @@ -16,24 +14,25 @@ def mrl_to_path(file_mrl): file_mrl (str): Path to the resource within MRL format. Returns: - path.Path: Path to the resource. + pathlib.Path: Path to the resource. """ + # TODO Replace by pathlib.Path.from_uri() available in Python 3.13 path_string = unquote(urlparse(file_mrl).path) # remove first '/' if a colon character is found like in '/C:/a/b' if path_string[0] == "/" and path_string[2] == ":": path_string = path_string[1:] - return Path(path_string).normpath() + return Path(path_string).resolve() def path_to_mrl(file_path): """Convert a filesystem path to MRL. Args: - file_path (path.Path or str): Path to the resource. + file_path (pathlib.Path or str): Path to the resource. Returns: str: Path to the resource within MRL format. """ - return pathlib.Path(file_path).as_uri() + return file_path.as_uri() diff --git a/src/dakara_player/player.py b/src/dakara_player/player.py index b97b682..2568dd7 100644 --- a/src/dakara_player/player.py +++ b/src/dakara_player/player.py @@ -2,10 +2,11 @@ import logging from contextlib import ExitStack +from pathlib import Path +from tempfile import TemporaryDirectory from dakara_base.exceptions import DakaraError from dakara_base.safe_workers import Runner, WorkerSafeThread -from path import TempDir from dakara_player.font import get_font_loader_class from dakara_player.manager import DakaraManager @@ -123,7 +124,7 @@ def run(self): # be executed. with ExitStack() as stack: # temporary directory - tempdir = stack.enter_context(TempDir(suffix=".dakara")) + tempdir = Path(stack.enter_context(TemporaryDirectory(suffix=".dakara"))) # font loader font_loader = stack.enter_context( diff --git a/src/dakara_player/text.py b/src/dakara_player/text.py index fca8189..d07bd13 100644 --- a/src/dakara_player/text.py +++ b/src/dakara_player/text.py @@ -3,10 +3,10 @@ import json import logging from importlib.resources import path +from pathlib import Path from dakara_base.exceptions import DakaraError from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PackageLoader -from path import Path ICON_MAP_FILE = "line-awesome.json" @@ -29,7 +29,7 @@ class TextGenerator: Example of use: - >>> from path import Path + >>> from pathlib import Path >>> generator = TextGenerator( ... package="package", ... directory=Path("directory"), @@ -51,13 +51,13 @@ class TextGenerator: Args: package (str): Package checked for text templates by default. - directory (path.Path): Custom directory checked for text templates. + directory (pathlib.Path): Custom directory checked for text templates. filenames (dict): Dictionary of text templates filenames. The key is the template name, the value the template file name. Attributes: package (str): Package checked for text templates by default. - directory (path.Path): Custom directory checked for text templates. + directory (pathlib.Path): Custom directory checked for text templates. filenames (dict): Dictionary of text templates filenames. The key is the template name, the value the template file name. environment (jinja2.Environment): Environment for Jinja2. diff --git a/src/dakara_player/user_resources.py b/src/dakara_player/user_resources.py index c7ee9be..3ab2f7d 100644 --- a/src/dakara_player/user_resources.py +++ b/src/dakara_player/user_resources.py @@ -3,9 +3,9 @@ import logging from distutils.util import strtobool from importlib.resources import contents, path +from shutil import copy from dakara_base.directory import directories -from path import Path logger = logging.getLogger(__name__) @@ -15,7 +15,7 @@ def copy_resource(resource, destination, force): Args: resource (str): Resource to copy. - destination (path.Path): Directory where to copy the resource. + destination (pathlib.Path): Directory where to copy the resource. force (bool): If the destination exists and this flag is set to `True`, overwrite the destination. """ @@ -34,7 +34,7 @@ def copy_resource(resource, destination, force): if not result: return - destination.makedirs_p() + destination.mkdir(parents=True, exist_ok=True) for file_name in contents(resource): # ignore Python files @@ -42,7 +42,7 @@ def copy_resource(resource, destination, force): continue with path(resource, file_name) as file: - Path(file).copy(destination) + copy(file, destination) def create_resource_files(force=False): @@ -52,8 +52,8 @@ def create_resource_files(force=False): force (bool): If the user directory already contains the resource directories and this flag is set, overwrite the directories. """ - user_directory = directories.user_data_dir - user_directory.makedirs_p() + user_directory = directories.user_data_path + user_directory.mkdir(parents=True, exist_ok=True) for directory in ["backgrounds", "templates"]: copy_resource( diff --git a/tests/integration/base.py b/tests/integration/base.py index 383d91a..3bfea05 100644 --- a/tests/integration/base.py +++ b/tests/integration/base.py @@ -1,3 +1,7 @@ +from pathlib import Path +from queue import Empty +from shutil import copy +from tempfile import TemporaryDirectory from time import sleep from unittest import TestCase @@ -7,8 +11,6 @@ except ImportError: from importlib_resources import path -from path import Path, TempDir - class TestCasePoller(TestCase): """Test class that can poll the state of tested player.""" @@ -29,10 +31,12 @@ def wait_is_playing(cls, player, what=None, wait_extra=DELAY): wait_extra (float): Time to wait at the end of the function. """ if what: - cls.wait(lambda: player.is_playing() and player.is_playing_this(what)) + cls.wait( + player, lambda: player.is_playing() and player.is_playing_this(what) + ) else: - cls.wait(lambda: player.is_playing()) + cls.wait(player, lambda: player.is_playing()) sleep(wait_extra) @@ -47,12 +51,12 @@ def wait_is_paused(cls, player, wait_extra=DELAY): player (dakara_player.media_player.base.MediaPlayer): Player. wait_extra (float): Time to wait at the end of the function. """ - cls.wait(lambda: player.is_paused()) + cls.wait(player, lambda: player.is_paused()) sleep(wait_extra) @classmethod - def wait(cls, condition_method): + def wait(cls, player, condition_method): """Wait for a condition to be true safely. Args: @@ -60,6 +64,16 @@ def wait(cls, condition_method): structure, so as to not break the loop in cause of error. """ while True: + # handle any player error + if player.stop.is_set(): + try: + _, error, traceback = player.errors.get(5) + error.with_traceback(traceback) + raise error + + except Empty as error_empty: + raise RuntimeError("Unexpected error happened") from error_empty + try: if condition_method(): return @@ -75,53 +89,67 @@ class TestCaseKara(TestCase): def setUp(self): # create kara folder - self.kara_folder = TempDir() + self.kara_folder = TemporaryDirectory() + # resolve to prevent DOS short paths on Windows CI + self.kara_folder_path = Path(self.kara_folder.name).resolve() # create subtitle with path("tests.resources", "song1.ass") as file: - self.subtitle1_path = Path(file).copy(self.kara_folder) + self.subtitle1_path = Path(copy(file, self.kara_folder_path)) with path("tests.resources", "song2.ass") as file: - self.subtitle2_path = Path(file).copy(self.kara_folder) + self.subtitle2_path = Path(copy(file, self.kara_folder_path)) # create song with path("tests.resources", "song1.mkv") as file: - self.song1_path = Path(file).copy(self.kara_folder) + self.song1_path = Path(copy(file, self.kara_folder_path)) with path("tests.resources", "song2.mkv") as file: - self.song2_path = Path(file).copy(self.kara_folder) + self.song2_path = Path(copy(file, self.kara_folder_path)) with path("tests.resources", "song3.avi") as file: - self.song3_path = Path(file).copy(self.kara_folder) + self.song3_path = Path(copy(file, self.kara_folder_path)) # create audio with path("tests.resources", "song2.mp3") as file: - self.audio2_path = Path(file).copy(self.kara_folder) + self.audio2_path = Path(copy(file, self.kara_folder_path)) # create playlist entry self.playlist_entry1 = { "id": 42, - "song": {"title": "Song 1", "file_path": self.song1_path, "duration": 60}, + "song": { + "title": "Song 1", + "file_path": str(self.song1_path), + "duration": 60, + }, "owner": "me", "use_instrumental": False, } self.playlist_entry2 = { "id": 43, - "song": {"title": "Song 2", "file_path": self.song2_path, "duration": 60}, + "song": { + "title": "Song 2", + "file_path": str(self.song2_path), + "duration": 60, + }, "owner": "me", "use_instrumental": False, } self.playlist_entry3 = { "id": 44, - "song": {"title": "Song 3", "file_path": self.song3_path, "duration": 60}, + "song": { + "title": "Song 3", + "file_path": str(self.song3_path), + "duration": 60, + }, "owner": "me", "use_instrumental": False, } def tearDown(self): - self.kara_folder.rmtree(ignore_errors=True) + self.kara_folder.cleanup() class TestCasePollerKara(TestCasePoller, TestCaseKara): diff --git a/tests/integration/test_media_player_mpv.py b/tests/integration/test_media_player_mpv.py index 4d95123..6204d80 100644 --- a/tests/integration/test_media_player_mpv.py +++ b/tests/integration/test_media_player_mpv.py @@ -1,5 +1,7 @@ from contextlib import ExitStack, contextmanager +from pathlib import Path from queue import Queue +from tempfile import TemporaryDirectory from threading import Event from time import sleep from unittest import skipUnless @@ -7,7 +9,6 @@ from dakara_base.config import Config from func_timeout import func_set_timeout -from path import TempDir from dakara_player.media_player.base import ( IDLE_BG_NAME, @@ -54,11 +55,11 @@ def get_instance(self, config=None, check_error=True): Yields: tuple: Containing the following elements: MediaPlayerMpv: Instance; - path.Path: Path of the temporary directory; + pathlib.Path: Path of the temporary directory; unittest.case._LoggingWatcher: Captured output. """ config_full = { - "kara_folder": self.kara_folder, + "kara_folder": str(self.kara_folder_path), "fullscreen": self.fullscreen, "mpv": {"vo": "null", "ao": "null"}, } @@ -66,7 +67,8 @@ def get_instance(self, config=None, check_error=True): if config: config_full.update(config) - with TempDir() as temp: + with TemporaryDirectory() as temp_str: + temp = Path(temp_str).resolve() try: with ExitStack() as stack: mpv_player = stack.enter_context( @@ -128,8 +130,10 @@ def test_play_idle(self): # post assertions self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, temp / IDLE_BG_NAME) - self.assertListEqual(mpv_player.player.sub_files, [temp / IDLE_TEXT_NAME]) + self.assertEqual(mpv_player.player.path, str(temp / IDLE_BG_NAME)) + self.assertListEqual( + mpv_player.player.sub_files, [str(temp / IDLE_TEXT_NAME)] + ) @func_set_timeout(TIMEOUT) def test_play_playlist_entry(self): @@ -173,14 +177,14 @@ def test_play_playlist_entry(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, temp / TRANSITION_BG_NAME) + self.assertEqual(mpv_player.player.path, str(temp / TRANSITION_BG_NAME)) # check there is no audio track self.assertFalse(mpv_player.player.audio) # check which subtitle file is read self.assertListEqual( - mpv_player.player.sub_files, [temp / TRANSITION_TEXT_NAME] + mpv_player.player.sub_files, [str(temp / TRANSITION_TEXT_NAME)] ) # assert the started transition callback has been called @@ -193,7 +197,7 @@ def test_play_playlist_entry(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # check audio track self.assertEqual(mpv_player.player.audio, 1) @@ -209,7 +213,7 @@ def test_play_playlist_entry(self): self.assertFalse(mpv_player.is_playing_this("idle")) # wait for the media to end - self.wait(lambda: not mpv_player.is_playing()) + self.wait(mpv_player, lambda: not mpv_player.is_playing()) # assert the player is not playing anything self.assertFalse(mpv_player.is_playing_this("transition")) @@ -242,7 +246,7 @@ def test_play_playlist_entry_instrumental_track(self): # check media exists self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # check audio track self.assertEqual(mpv_player.player.audio, 2) @@ -276,7 +280,7 @@ def test_play_playlist_entry_instrumental_file(self): # check media exists self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song2_path) + self.assertEqual(mpv_player.player.path, str(self.song2_path)) # check audio track self.assertEqual(mpv_player.player.audio, 3) @@ -422,7 +426,8 @@ def test_restart_song(self): # wait a bit for the player to play self.wait( - lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION + mpv_player, + lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION, ) # request playlist entry to restart @@ -464,7 +469,7 @@ def test_skip_song(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # request first playlist entry to stop mpv_player.skip() @@ -489,7 +494,7 @@ def test_skip_song(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song2_path) + self.assertEqual(mpv_player.player.path, str(self.song2_path)) # check skip flag self.assertFalse(mpv_player.player_data["skip"]) @@ -518,7 +523,7 @@ def test_skip_last_song(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # request first playlist entry to stop mpv_player.skip() @@ -567,7 +572,7 @@ def test_skip_song_pause(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # pause song mpv_player.pause() @@ -598,7 +603,7 @@ def test_skip_song_pause(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song2_path) + self.assertEqual(mpv_player.player.path, str(self.song2_path)) # check skip flag self.assertFalse(mpv_player.player_data["skip"]) @@ -632,7 +637,7 @@ def test_skip_last_song_pause(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # pause song mpv_player.pause() @@ -693,7 +698,7 @@ def test_skip_transition(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song2_path) + self.assertEqual(mpv_player.player.path, str(self.song2_path)) @func_set_timeout(TIMEOUT) def test_skip_idle(self): @@ -728,7 +733,7 @@ def test_skip_idle(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) @func_set_timeout(TIMEOUT) def test_rewind_song(self): @@ -760,7 +765,8 @@ def test_rewind_song(self): # wait a bit for the player to play self.wait( - lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION * 2 + mpv_player, + lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION * 2, ) timing1 = mpv_player.player.time_pos @@ -838,7 +844,8 @@ def test_fast_forward_song(self): # wait a bit for the player to play self.wait( - lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION * 2 + mpv_player, + lambda: mpv_player.player.time_pos >= REWIND_FAST_FORWARD_DURATION * 2, ) timing1 = mpv_player.player.time_pos @@ -915,7 +922,7 @@ def test_play_idle_after_playlist_paused(self): # check media self.assertIsNotNone(mpv_player.player.path) - self.assertEqual(mpv_player.player.path, self.song1_path) + self.assertEqual(mpv_player.player.path, str(self.song1_path)) # pause song mpv_player.pause() diff --git a/tests/integration/test_media_player_vlc.py b/tests/integration/test_media_player_vlc.py index 1997af4..5236171 100644 --- a/tests/integration/test_media_player_vlc.py +++ b/tests/integration/test_media_player_vlc.py @@ -1,5 +1,7 @@ from contextlib import ExitStack, contextmanager +from pathlib import Path from queue import Queue +from tempfile import TemporaryDirectory from threading import Event from unittest import skipIf, skipUnless from unittest.mock import MagicMock @@ -12,7 +14,6 @@ from dakara_base.config import Config from func_timeout import func_set_timeout -from path import TempDir from dakara_player.media_player.base import IDLE_BG_NAME, TRANSITION_BG_NAME from dakara_player.media_player.vlc import METADATA_KEYS_COUNT, MediaPlayerVlc @@ -67,12 +68,12 @@ def get_instance(self, config=None, check_error=True): Yields: tuple: Containing the following elements: MediaPlayerVlc: Instance; - path.Path: Path of the temporary directory; + pathlib.Path: Path of the temporary directory; unittest.case._LoggingWatcher: Captured output. """ config_full = { - "kara_folder": self.kara_folder, + "kara_folder": str(self.kara_folder_path), "fullscreen": self.fullscreen, "vlc": { "instance_parameters": self.instance_parameters, @@ -85,7 +86,7 @@ def get_instance(self, config=None, check_error=True): config_full.update(config) with ExitStack() as stack: - temp = stack.enter_context(TempDir()) + temp = Path(stack.enter_context(TemporaryDirectory())).resolve() vlc_player = stack.enter_context( MediaPlayerVlc( Event(), @@ -240,7 +241,7 @@ def test_play_playlist_entry(self): self.assertFalse(vlc_player.is_playing_this("idle")) # wait for the media to end - self.wait(lambda: not vlc_player.is_playing()) + self.wait(vlc_player, lambda: not vlc_player.is_playing()) # assert the player is not playing anything self.assertFalse(vlc_player.is_playing_this("transition")) @@ -342,7 +343,7 @@ def test_play_playlist_entry_instrumental_track_avi(self): def test_play_playlist_entry_instrumental_file(self): """Test to play a playlist entry using instrumental file.""" # request to use instrumental file - self.playlist_entry1["song"]["file_path"] = self.song2_path + self.playlist_entry1["song"]["file_path"] = str(self.song2_path) self.playlist_entry1["use_instrumental"] = True with self.get_instance() as (vlc_player, _, _): @@ -519,8 +520,9 @@ def test_restart_song(self): # wait a bit for the player to play self.wait( + vlc_player, lambda: vlc_player.player.get_time() - >= REWIND_FAST_FORWARD_DURATION * 1000 + >= REWIND_FAST_FORWARD_DURATION * 1000, ) # request to restart media @@ -674,8 +676,9 @@ def test_rewind_song(self): # wait a bit for the player to play self.wait( + vlc_player, lambda: vlc_player.player.get_time() - >= REWIND_FAST_FORWARD_DURATION * 2 * 1000 + >= REWIND_FAST_FORWARD_DURATION * 2 * 1000, ) timing1 = vlc_player.player.get_time() / 1000 @@ -752,8 +755,9 @@ def test_fast_forward_song(self): # wait a bit for the player to play self.wait( + vlc_player, lambda: vlc_player.player.get_time() - >= REWIND_FAST_FORWARD_DURATION * 2 * 1000 + >= REWIND_FAST_FORWARD_DURATION * 2 * 1000, ) timing1 = vlc_player.player.get_time() / 1000 diff --git a/tests/unit/test_audio.py b/tests/unit/test_audio.py new file mode 100644 index 0000000..a901622 --- /dev/null +++ b/tests/unit/test_audio.py @@ -0,0 +1,60 @@ +from importlib.resources import path +from pathlib import Path +from unittest import TestCase +from unittest.mock import patch + +from dakara_player.audio import get_audio_files, is_audio_file + + +class IsAudioFileTestCase(TestCase): + def test_mp3(self): + """Test to detect a MP3 file.""" + with path("tests.resources", "song2.mp3") as file: + self.assertTrue(is_audio_file(file)) + + def test_ass(self): + """Test to not detect an ASS file.""" + with path("tests.resources", "song2.ass") as file: + self.assertFalse(is_audio_file(file)) + + def test_mkv(self): + """Test to not detect a MKV file.""" + with path("tests.resources", "song2.mkv") as file: + self.assertFalse(is_audio_file(file)) + + +@patch("dakara_player.audio.is_audio_file", autospec=True) +@patch.object(Path, "glob", autospec=True) +class GetAudioFileTestCase(TestCase): + def test_no_audio(self, mocked_glob, mocked_is_audio_file): + """Test no audio file found.""" + file_video = Path("aa") / "file.mp4" + mocked_glob.return_value = [file_video] + + self.assertEqual(len(get_audio_files(file_video)), 0) + mocked_glob.assert_called_with(Path("aa"), "file.*") + + def test_one_audio(self, mocked_glob, mocked_is_audio_file): + """Test one audio file found.""" + file_video = Path("aa") / "file.mp4" + file_audio = Path("aa") / "file.mp3" + mocked_glob.return_value = [file_video, file_audio] + mocked_is_audio_file.return_value = True + + files_audio = get_audio_files(file_video) + self.assertEqual(len(files_audio), 1) + self.assertListEqual(files_audio, [file_audio]) + mocked_glob.assert_called_with(Path("aa"), "file.*") + + def test_two_audio(self, mocked_glob, mocked_is_audio_file): + """Test one audio file found.""" + file_video = Path("aa") / "file.mp4" + file_audio1 = Path("aa") / "file.mp3" + file_audio2 = Path("aa") / "file.ogg" + mocked_glob.return_value = [file_video, file_audio1, file_audio2] + mocked_is_audio_file.return_value = True + + files_audio = get_audio_files(file_video) + self.assertEqual(len(files_audio), 2) + self.assertListEqual(files_audio, [file_audio1, file_audio2]) + mocked_glob.assert_called_with(Path("aa"), "file.*") diff --git a/tests/unit/test_background.py b/tests/unit/test_background.py index aa1a3bc..e29332a 100644 --- a/tests/unit/test_background.py +++ b/tests/unit/test_background.py @@ -1,13 +1,12 @@ +from pathlib import Path from unittest import TestCase from unittest.mock import patch -from path import Path - from dakara_player.background import BackgroundLoader, BackgroundNotFoundError @patch("dakara_player.background.path", autospec=True) -@patch.object(Path, "copy", autospec=True) +@patch("dakara_player.background.copy", autospec=True) @patch.object(Path, "exists", autospec=True) class BackgroundLoaderTestCase(TestCase): """Test the loader for backgrounds.""" diff --git a/tests/unit/test_font.py b/tests/unit/test_font.py index 16d5f3b..238976a 100644 --- a/tests/unit/test_font.py +++ b/tests/unit/test_font.py @@ -1,9 +1,8 @@ import platform +from pathlib import Path from unittest import TestCase, skipUnless from unittest.mock import call, patch -from path import Path - from dakara_player.font import ( FontLoaderLinux, FontLoaderNotAvailableError, @@ -127,21 +126,21 @@ def get_font_loader(self): @patch.object(FontLoaderLinux, "load_font", autospec=True) @patch.object(FontLoaderLinux, "get_font_path_iterator", autospec=True) - @patch.object(Path, "walkfiles", autospec=True) - @patch.object(Path, "mkdir_p", autospec=True) + @patch.object(Path, "rglob", autospec=True) + @patch.object(Path, "mkdir", autospec=True) def test_load( self, - mocked_mkdir_p, - mocked_walkfiles, + mocked_mkdir, + mocked_rglob, mocked_get_font_path_iterator, mocked_load_font, ): """Test to load fonts.""" # prepare the mock mocked_get_font_path_iterator.return_value = (p for p in [self.font_path]) - mocked_walkfiles.side_effect = [ - (p for p in [Path("/") / "usr" / "share" / "fonts" / "font1"]), - (p for p in [self.user_directory / ".fonts" / "font2"]), + mocked_rglob.side_effect = [ + [Path("/") / "usr" / "share" / "fonts" / "font1"], + [self.user_directory / ".fonts" / "font2"], ] font_loader = self.get_font_loader() @@ -150,25 +149,27 @@ def test_load( font_loader.load() # assert the call - mocked_mkdir_p.assert_called_once_with(self.user_directory / ".fonts") - mocked_walkfiles.assert_has_calls( + mocked_mkdir.assert_called_once_with( + self.user_directory / ".fonts", parents=True, exist_ok=True + ) + mocked_rglob.assert_has_calls( [ - call(Path("/") / "usr" / "share" / "fonts"), - call(self.user_directory / ".fonts"), + call(Path("/") / "usr" / "share" / "fonts", "*"), + call(self.user_directory / ".fonts", "*"), ] ) mocked_get_font_path_iterator.assert_called_once_with(font_loader) mocked_load_font.assert_called_once_with( font_loader, self.font_path, - [Path("/") / "usr" / "share" / "fonts" / "font1"], - [self.user_directory / ".fonts" / "font2"], + ["font1"], + ["font2"], ) @patch.object(Path, "unlink", autospec=True) - @patch.object(Path, "copy", autospec=True) - @patch.object(Path, "islink", autospec=True) - def test_load_font_system(self, mocked_islink, mocked_copy, mocked_unlink): + @patch("dakara_player.font.copy", autospec=True) + @patch.object(Path, "is_symlink", autospec=True) + def test_load_font_system(self, mocked_is_symlink, mocked_copy, mocked_unlink): """Test to load one font which is in system directory.""" font_loader = self.get_font_loader() @@ -179,7 +180,7 @@ def test_load_font_system(self, mocked_islink, mocked_copy, mocked_unlink): with self.assertLogs("dakara_player.font", "DEBUG") as logger: font_loader.load_font( self.font_path, - [Path("/") / "usr" / "share" / "fonts" / "truetype" / "font_file.ttf"], + ["font_file.ttf"], [], ) @@ -196,14 +197,14 @@ def test_load_font_system(self, mocked_islink, mocked_copy, mocked_unlink): ) # assert the call - mocked_islink.assert_not_called() + mocked_is_symlink.assert_not_called() mocked_unlink.assert_not_called() mocked_copy.assert_not_called() @patch.object(Path, "unlink", autospec=True) - @patch.object(Path, "copy", autospec=True) - @patch.object(Path, "islink", autospec=True) - def test_load_font_user(self, mocked_islink, mocked_copy, mocked_unlink): + @patch("dakara_player.font.copy", autospec=True) + @patch.object(Path, "is_symlink", autospec=True) + def test_load_font_user(self, mocked_is_symlink, mocked_copy, mocked_unlink): """Test to load one font which is in user directory.""" font_loader = self.get_font_loader() @@ -215,7 +216,7 @@ def test_load_font_user(self, mocked_islink, mocked_copy, mocked_unlink): font_loader.load_font( self.font_path, [], - [self.user_directory / "fonts" / "truetype" / "font_file.ttf"], + ["font_file.ttf"], ) # post assertions @@ -231,22 +232,22 @@ def test_load_font_user(self, mocked_islink, mocked_copy, mocked_unlink): ) # assert the call - mocked_islink.assert_not_called() + mocked_is_symlink.assert_not_called() mocked_unlink.assert_not_called() mocked_copy.assert_not_called() @patch.object(Path, "unlink", autospec=True) - @patch.object(Path, "copy", autospec=True) - @patch.object(Path, "islink", autospec=True) + @patch("dakara_player.font.copy", autospec=True) + @patch.object(Path, "is_symlink", autospec=True) def test_load_font_user_link_dead_install( self, - mocked_islink, + mocked_is_symlink, mocked_copy, mocked_unlink, ): """Test to load one font which is in user directory as dead link.""" # prepare the mock - mocked_islink.return_value = True + mocked_is_symlink.return_value = True font_loader = self.get_font_loader() @@ -273,17 +274,19 @@ def test_load_font_user_link_dead_install( ) # assert the call - mocked_islink.assert_called_with(font_path) - mocked_unlink.assert_called_with(font_path) - mocked_copy.assert_called_once_with("directory/font_file.ttf", font_path) + mocked_is_symlink.assert_called_with(font_path) + mocked_unlink.assert_called_with(font_path, missing_ok=True) + mocked_copy.assert_called_once_with( + Path("directory") / "font_file.ttf", font_path + ) @patch.object(Path, "unlink", autospec=True) - @patch.object(Path, "copy", autospec=True) - @patch.object(Path, "islink", autospec=True) - def test_load_font_install(self, mocked_islink, mocked_copy, mocked_unlink): + @patch("dakara_player.font.copy", autospec=True) + @patch.object(Path, "is_symlink", autospec=True) + def test_load_font_install(self, mocked_is_symlink, mocked_copy, mocked_unlink): """Test to load one font which is not installed.""" # prepare the mock - mocked_islink.return_value = False + mocked_is_symlink.return_value = False font_loader = self.get_font_loader() @@ -312,10 +315,11 @@ def test_load_font_install(self, mocked_islink, mocked_copy, mocked_unlink): ) # assert the call - mocked_islink.assert_called_with(font_path) + mocked_is_symlink.assert_called_with(font_path) mocked_unlink.assert_not_called() mocked_copy.assert_called_once_with( - "directory/font_file.ttf", self.user_directory / ".fonts/font_file.ttf" + Path("directory") / "font_file.ttf", + self.user_directory / ".fonts/font_file.ttf", ) @patch.object(Path, "unlink", autospec=True) @@ -332,7 +336,7 @@ def test_unload(self, mocked_unlink): # assert the call # especially check that the unload function does not alter the list of # elements we are iterating on - mocked_unlink.assert_has_calls([call("font1"), call("font2")]) + mocked_unlink.assert_has_calls([call(Path("font1")), call(Path("font2"))]) @patch.object(Path, "unlink", autospec=True) def test_unload_font(self, mocked_unlink): diff --git a/tests/unit/test_media_player_mpv.py b/tests/unit/test_media_player_mpv.py index cd755ed..255cd4a 100644 --- a/tests/unit/test_media_player_mpv.py +++ b/tests/unit/test_media_player_mpv.py @@ -1,12 +1,11 @@ from contextlib import ExitStack +from pathlib import Path from queue import Queue -from tempfile import gettempdir from threading import Event from unittest import TestCase from unittest.mock import MagicMock, patch from packaging.version import Version -from path import Path from dakara_player.media_player.base import ( InvalidStateError, @@ -20,6 +19,7 @@ MediaPlayerMpvPost0340, MpvTooOldError, ) +from tests.utils import get_temp_dir class MediaPlayerMpvTestCase(TestCase): @@ -211,7 +211,7 @@ def get_instance( unittest.mock.MagicMock: BackgroundLoader class. unittest.mock.MagicMock: TextGenerator class. """ - config = config or {"kara_folder": gettempdir()} + config = config or {"kara_folder": get_temp_dir()} with ExitStack() as stack: mocked_instance_class = stack.enter_context( @@ -251,7 +251,7 @@ def set_playlist_entry(self, mpv_player, started=True): # create mocked transition mpv_player.playlist_entry_data["transition"].path = ( - Path(gettempdir()) / "transition.png" + get_temp_dir() / "transition.png" ) # create mocked song @@ -413,7 +413,7 @@ def test_handle_end_file_transition(self, mocked_play, mocked_clear_playlist_ent mpv_player, (mocked_player, _, _), _ = self.get_instance() mpv_player.set_callback("finished", MagicMock()) self.set_playlist_entry(mpv_player) - mocked_player.playlist[0]["filename"] = Path(gettempdir()) / "transition.png" + mocked_player.playlist[0]["filename"] = str(get_temp_dir() / "transition.png") # call the method with self.assertLogs("dakara_player.media_player.mpv", "DEBUG") as logger: @@ -425,7 +425,7 @@ def test_handle_end_file_transition(self, mocked_play, mocked_clear_playlist_ent [ "DEBUG:dakara_player.media_player.mpv:File end callback called", "DEBUG:dakara_player.media_player.mpv:Will play '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ), ], ) @@ -490,7 +490,7 @@ def test_handle_end_file_other(self, mocked_play, mocked_clear_playlist_entry): mpv_player, (mocked_player, _, _), _ = self.get_instance() mpv_player.set_callback("finished", MagicMock()) self.set_playlist_entry(mpv_player) - mocked_player.playlist[0]["filename"] = Path(gettempdir()) / "other" + mocked_player.playlist[0]["filename"] = str(get_temp_dir() / "other") self.assertFalse(mpv_player.stop.is_set()) @@ -530,7 +530,7 @@ def test_handle_log_message(self, mocked_skip): [ "DEBUG:dakara_player.media_player.mpv:Log message callback called", "ERROR:dakara_player.media_player.mpv:Unable to play '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ), ], ) @@ -598,7 +598,7 @@ def test_handle_start_file_song(self, mocked_is_playing_this): [ "DEBUG:dakara_player.media_player.mpv:Start file callback called", "INFO:dakara_player.media_player.mpv:Now playing 'Song title' " - "('{}')".format(Path(gettempdir()) / self.song_file_path), + "('{}')".format(get_temp_dir() / self.song_file_path), ], ) @@ -768,7 +768,7 @@ def test_handle_end_file_transition(self, mocked_play, mocked_clear_playlist_ent mpv_player, (mocked_player, _, _), _ = self.get_instance() mpv_player.set_callback("finished", MagicMock()) self.set_playlist_entry(mpv_player) - mocked_player.playlist[0]["filename"] = Path(gettempdir()) / "transition.png" + mocked_player.playlist[0]["filename"] = str(get_temp_dir() / "transition.png") # call the method with self.assertLogs("dakara_player.media_player.mpv", "DEBUG") as logger: @@ -782,7 +782,7 @@ def test_handle_end_file_transition(self, mocked_play, mocked_clear_playlist_ent [ "DEBUG:dakara_player.media_player.mpv:File end callback called", "DEBUG:dakara_player.media_player.mpv:Will play '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ), ], ) @@ -851,7 +851,7 @@ def test_handle_end_file_other(self, mocked_play, mocked_clear_playlist_entry): mpv_player, (mocked_player, _, _), _ = self.get_instance() mpv_player.set_callback("finished", MagicMock()) self.set_playlist_entry(mpv_player) - mocked_player.playlist[0]["filename"] = Path(gettempdir()) / "other" + mocked_player.playlist[0]["filename"] = str(get_temp_dir() / "other") self.assertFalse(mpv_player.stop.is_set()) diff --git a/tests/unit/test_media_player_vlc.py b/tests/unit/test_media_player_vlc.py index 58248d4..330bcee 100644 --- a/tests/unit/test_media_player_vlc.py +++ b/tests/unit/test_media_player_vlc.py @@ -1,7 +1,7 @@ import re from contextlib import ExitStack, contextmanager +from pathlib import Path from queue import Queue -from tempfile import gettempdir from threading import Event from time import sleep from unittest import TestCase, skipIf @@ -13,9 +13,8 @@ except (ImportError, OSError): vlc = None -from dakara_base.directory import AppDirsPath +from dakara_base.directory import PlatformDirs from packaging.version import parse -from path import Path from dakara_player.media_player.base import ( InvalidStateError, @@ -32,6 +31,7 @@ from dakara_player.mrl import path_to_mrl from dakara_player.text import TextGenerator from dakara_player.window import DummyWindowManager, WindowManager +from tests.utils import get_temp_dir class BaseTestCase(TestCase): @@ -62,7 +62,7 @@ def get_instance( Args: config (dict): Configuration passed to the constructor. - tempdir (path.Path): Path to temporary directory. + tempdir (pathlib.Path): Path to temporary directory. Yields: tuple: Contains the following elements: @@ -76,7 +76,7 @@ def get_instance( unittest.mock.MagicMock: BackgroundLoader class. unittest.mock.MagicMock: TextGenerator class. """ - config = config or {"kara_folder": gettempdir()} + config = config or {"kara_folder": str(get_temp_dir())} with ExitStack() as stack: if vlc is None: @@ -152,7 +152,7 @@ def test_init_window(self): """Test to use default or custom window.""" # default window with self.get_instance( - {"kara_folder": gettempdir(), "vlc": {"use_default_window": True}} + {"kara_folder": str(get_temp_dir()), "vlc": {"use_default_window": True}} ) as (vlc_player, _, _): self.assertIsInstance(vlc_player.window, DummyWindowManager) @@ -342,7 +342,9 @@ def test_check_kara_folder_path_does_not_exist(self, mocked_exists): # call the method with self.assertRaisesRegex( KaraFolderNotFound, - 'Karaoke folder "{}" does not exist'.format(re.escape(gettempdir())), + 'Karaoke folder "{}" does not exist'.format( + re.escape(str(get_temp_dir())) + ), ): vlc_player.check_kara_folder_path() @@ -388,12 +390,12 @@ def test_load( logger.output, ["INFO:dakara_player.media_player.vlc:VLC 3.0.0 NoName"] ) - @patch.object(Path, "isfile") - def test_set_playlist_entry_error_file(self, mocked_isfile): + @patch.object(Path, "is_file") + def test_set_playlist_entry_error_file(self, mocked_is_file): """Test to set a playlist entry that does not exist.""" with self.get_instance() as (vlc_player, _, _): # mock the system call - mocked_isfile.return_value = False + mocked_is_file.return_value = False # mock the callbacks vlc_player.set_callback("could_not_play", MagicMock()) @@ -407,7 +409,7 @@ def test_set_playlist_entry_error_file(self, mocked_isfile): vlc_player.set_playlist_entry(self.playlist_entry) # call assertions - mocked_isfile.assert_called_once_with() + mocked_is_file.assert_called_once_with() # post assertions self.assertIsNone(vlc_player.playlist_entry) @@ -421,7 +423,7 @@ def test_set_playlist_entry_error_file(self, mocked_isfile): logger.output, [ "ERROR:dakara_player.media_player.base:File not found '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ) ], ) @@ -431,10 +433,10 @@ def test_set_playlist_entry_error_file(self, mocked_isfile): @patch.object(MediaPlayerVlc, "manage_instrumental") @patch.object(MediaPlayerVlc, "play") @patch.object(MediaPlayerVlc, "generate_text") - @patch.object(Path, "isfile") + @patch.object(Path, "is_file") def test_set_playlist_entry( self, - mocked_isfile, + mocked_is_file, mocked_generate_text, mocked_play, mocked_manage_instrumental, @@ -444,9 +446,9 @@ def test_set_playlist_entry( """Test to set a playlist entry.""" with self.get_instance() as (vlc_player, (_, mocked_background_loader, _), _): # setup mocks - mocked_isfile.return_value = True + mocked_is_file.return_value = True mocked_background_loader.backgrounds = { - "transition": Path(gettempdir()) / "transition.png" + "transition": get_temp_dir() / "transition.png" } # mock the callbacks @@ -468,7 +470,7 @@ def test_set_playlist_entry( vlc_player.callbacks["error"].assert_not_called() # assert mocks - mocked_isfile.assert_called_with() + mocked_is_file.assert_called_with() mocked_generate_text.assert_called_with("transition") mocked_play.assert_called_with("transition") mocked_manage_instrumental.assert_not_called() @@ -484,8 +486,8 @@ def test_manage_instrumental_file( ): """Test to add instrumental file.""" with self.get_instance() as (vlc_player, (mocked_instance, _, _), _): - video_path = Path(gettempdir()) / "video" - audio_path = Path(gettempdir()) / "audio" + video_path = get_temp_dir() / "video" + audio_path = get_temp_dir() / "audio" # pre assertions self.assertIsNone(vlc_player.playlist_entry_data["song"].audio_track_id) @@ -528,8 +530,8 @@ def test_manage_instrumental_file_error_slaves_add( ): """Test to be unable to add instrumental file.""" with self.get_instance() as (vlc_player, (mocked_instance, _, _), _): - video_path = Path(gettempdir()) / "video" - audio_path = Path(gettempdir()) / "audio" + video_path = get_temp_dir() / "video" + audio_path = get_temp_dir() / "audio" # pre assertions self.assertIsNone(vlc_player.playlist_entry_data["song"].audio_track_id) @@ -584,7 +586,7 @@ def test_manage_instrumental_track( ), _, ): - video_path = Path(gettempdir()) / "video" + video_path = get_temp_dir() / "video" # pre assertions self.assertIsNone(vlc_player.playlist_entry_data["song"].audio_track_id) @@ -625,7 +627,7 @@ def test_manage_instrumental_no_instrumental_found( ): """Test to cannot find instrumental.""" with self.get_instance() as (vlc_player, (mocked_instance, _, _), _): - video_path = Path(gettempdir()) / "video" + video_path = get_temp_dir() / "video" # pre assertions self.assertIsNone(vlc_player.playlist_entry_data["song"].audio_track_id) @@ -755,7 +757,7 @@ def test_handle_end_reached_transition( [ "DEBUG:dakara_player.media_player.vlc:End reached callback called", "DEBUG:dakara_player.media_player.vlc:Will play '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ), ], ) @@ -889,7 +891,7 @@ def test_handle_encountered_error(self, mocked_is_playing_this, mocked_skip): [ "DEBUG:dakara_player.media_player.vlc:Error callback called", "ERROR:dakara_player.media_player.vlc:Unable to play '{}'".format( - Path(gettempdir()) / self.song_file_path + get_temp_dir() / self.song_file_path ), ], ) @@ -991,7 +993,7 @@ def test_handle_playing_song(self, mocked_is_playing_this): [ "DEBUG:dakara_player.media_player.vlc:Playing callback called", "INFO:dakara_player.media_player.vlc:Now playing 'Song title' " - "('{}')".format(Path(gettempdir()) / self.song_file_path), + "('{}')".format(get_temp_dir() / self.song_file_path), ], ) @@ -1027,7 +1029,7 @@ def test_handle_playing_media_starts_track_id(self, mocked_is_playing_this): "DEBUG:dakara_player.media_player.vlc:Requesting to play audio " "track 99", "INFO:dakara_player.media_player.vlc:Now playing 'Song title' " - "('{}')".format(Path(gettempdir()) / self.song_file_path), + "('{}')".format(get_temp_dir() / self.song_file_path), ], ) @@ -1100,10 +1102,10 @@ def test_handle_paused(self, mocked_get_timing): # assert the call vlc_player.callbacks["paused"].assert_called_with(42, 25) - @patch.object(AppDirsPath, "user_data_dir", new_callable=PropertyMock) - def test_custom_backgrounds(self, mocked_user_data_dir): + @patch.object(PlatformDirs, "user_data_path", new_callable=PropertyMock) + def test_custom_backgrounds(self, mocked_user_data_path): """Test to instanciate with custom backgrounds.""" - mocked_user_data_dir.return_value = Path("directory") + mocked_user_data_path.return_value = Path("directory") # create object tempdir = Path("temp") diff --git a/tests/unit/test_mrl.py b/tests/unit/test_mrl.py index b5c5c43..af506b3 100644 --- a/tests/unit/test_mrl.py +++ b/tests/unit/test_mrl.py @@ -1,8 +1,7 @@ import sys +from pathlib import Path from unittest import TestCase, skipIf -from path import Path - from dakara_player.mrl import mrl_to_path, path_to_mrl @@ -16,7 +15,7 @@ def test_mrl_to_path_posix(self): """Test to convert MRL to path for POSIX.""" path = mrl_to_path("file:///home/username/directory/file%20name.ext") self.assertEqual( - path, Path("/home").normpath() / "username" / "directory" / "file name.ext" + path, Path("/home").resolve() / "username" / "directory" / "file name.ext" ) @skipIf(not is_windows, "Tested on Windows") @@ -25,14 +24,14 @@ def test_mrl_to_path_windows(self): path = mrl_to_path("file:///C:/Users/username/directory/file%20name.ext") self.assertEqual( path, - Path("C:/Users").normpath() / "username" / "directory" / "file name.ext", + Path("C:/Users").resolve() / "username" / "directory" / "file name.ext", ) @skipIf(is_windows, "Tested on POSIX") def test_path_to_mrl_posix(self): """Test to convert path to MRL for POSIX.""" mrl = path_to_mrl( - Path("/home").normpath() / "username" / "directory" / "file name.ext" + Path("/home").resolve() / "username" / "directory" / "file name.ext" ) self.assertEqual(mrl, "file:///home/username/directory/file%20name.ext") @@ -40,6 +39,6 @@ def test_path_to_mrl_posix(self): def test_path_to_mrl_windows(self): """Test to convert path to MRL for Windows.""" mrl = path_to_mrl( - Path("C:/Users").normpath() / "username" / "directory" / "file name.ext" + Path("C:/Users").resolve() / "username" / "directory" / "file name.ext" ) self.assertEqual(mrl, "file:///C:/Users/username/directory/file%20name.ext") diff --git a/tests/unit/test_player.py b/tests/unit/test_player.py index 6c91d20..b0ed5c1 100644 --- a/tests/unit/test_player.py +++ b/tests/unit/test_player.py @@ -94,7 +94,7 @@ def test_get_media_player_class_unsupported(self): ): worker.get_media_player_class() - @patch("dakara_player.player.TempDir", autospec=True) + @patch("dakara_player.player.TemporaryDirectory", autospec=True) @patch("dakara_player.player.FontLoader", autospec=True) @patch("dakara_player.player.MediaPlayerVlc", autospec=True) @patch("dakara_player.player.HTTPClientDakara", autospec=True) diff --git a/tests/unit/test_text.py b/tests/unit/test_text.py index 5c198a2..c218af0 100644 --- a/tests/unit/test_text.py +++ b/tests/unit/test_text.py @@ -1,9 +1,10 @@ +from pathlib import Path from pathlib import Path as Path_pathlib +from shutil import copy +from tempfile import TemporaryDirectory from unittest import TestCase from unittest.mock import MagicMock, patch -from path import Path, TempDir - try: from importlib.resources import path @@ -260,7 +261,9 @@ def test_load_templates_default(self): Integration test. """ - with TempDir() as temp: + with TemporaryDirectory() as temp_str: + temp = Path(temp_str) + # create object text_generator = TextGenerator( package="dakara_player.resources.templates", @@ -280,13 +283,15 @@ def test_load_templates_default(self): def test_load_templates_custom(self): """Test to load custom templates using an existing directory.""" - with TempDir() as temp: + with TemporaryDirectory() as temp_str: + temp = Path(temp_str) + # prepare directory with path("dakara_player.resources.templates", "idle.ass") as file: - Path(file).copy(temp) + copy(file, temp) with path("dakara_player.resources.templates", "transition.ass") as file: - Path(file).copy(temp) + copy(file, temp) # create object text_generator = TextGenerator( diff --git a/tests/unit/test_user_resources.py b/tests/unit/test_user_resources.py index 11c814f..4161640 100644 --- a/tests/unit/test_user_resources.py +++ b/tests/unit/test_user_resources.py @@ -1,8 +1,8 @@ +from pathlib import Path from unittest import TestCase from unittest.mock import PropertyMock, call, patch -from dakara_base.directory import AppDirsPath -from path import Path +from dakara_base.directory import PlatformDirs from dakara_player import user_resources @@ -10,15 +10,15 @@ class CopyResourceTestCase(TestCase): """Test the copy_resource function.""" - @patch.object(Path, "copy", autospec=True) + @patch("dakara_player.user_resources.copy", autospec=True) @patch("dakara_player.user_resources.path", autospec=True) @patch("dakara_player.user_resources.contents", autospec=True) - @patch.object(Path, "makedirs_p", autospec=True) + @patch.object(Path, "mkdir", autospec=True) @patch.object(Path, "exists", autospec=True) def test_copy( self, mocked_exists, - mocked_makedirs_p, + mocked_mkdir, mocked_contents, mocked_path, mocked_copy, @@ -27,32 +27,34 @@ def test_copy( mocked_exists.return_value = False mocked_contents.return_value = ["file1.ext", "file2.ext", "__init__.py"] mocked_path.return_value.__enter__.side_effect = [ - "path/to/file1.ext", - "path/to/file2.ext", + Path("path/to/file1.ext"), + Path("path/to/file2.ext"), ] user_resources.copy_resource("package.resources", Path("destination"), False) mocked_exists.assert_called_with(Path("destination")) - mocked_makedirs_p.assert_called_with(Path("destination")) + mocked_mkdir.assert_called_with( + Path("destination"), parents=True, exist_ok=True + ) mocked_contents.assert_called_with("package.resources") mocked_copy.assert_has_calls( [ - call(Path("path/to/file1.ext"), "destination"), - call(Path("path/to/file2.ext"), "destination"), + call(Path("path/to/file1.ext"), Path("destination")), + call(Path("path/to/file2.ext"), Path("destination")), ] ) @patch("dakara_player.user_resources.input") - @patch.object(Path, "copy", autospec=True) + @patch("dakara_player.user_resources.copy", autospec=True) @patch("dakara_player.user_resources.path", autospec=True) @patch("dakara_player.user_resources.contents", autospec=True) - @patch.object(Path, "makedirs_p", autospec=True) + @patch.object(Path, "mkdir", autospec=True) @patch.object(Path, "exists", autospec=True) def test_copy_existing_abort( self, mocked_exists, - mocked_makedirs_p, + mocked_mkdir, mocked_contents, mocked_path, mocked_copy, @@ -62,28 +64,28 @@ def test_copy_existing_abort( mocked_exists.return_value = True mocked_contents.return_value = ["file1.ext", "file2.ext", "__init__.py"] mocked_path.return_value.__enter__.side_effect = [ - "path/to/file1.ext", - "path/to/file2.ext", + Path("path/to/file1.ext"), + Path("path/to/file2.ext"), ] mocked_input.return_value = "no" user_resources.copy_resource("package.resources", Path("destination"), False) mocked_exists.assert_called_with(Path("destination")) - mocked_makedirs_p.assert_not_called() + mocked_mkdir.assert_not_called() mocked_contents.assert_not_called() mocked_copy.assert_not_called() @patch("dakara_player.user_resources.input") - @patch.object(Path, "copy", autospec=True) + @patch("dakara_player.user_resources.copy", autospec=True) @patch("dakara_player.user_resources.path", autospec=True) @patch("dakara_player.user_resources.contents", autospec=True) - @patch.object(Path, "makedirs_p", autospec=True) + @patch.object(Path, "mkdir", autospec=True) @patch.object(Path, "exists", autospec=True) def test_copy_existing_abort_invalid( self, mocked_exists, - mocked_makedirs_p, + mocked_mkdir, mocked_contents, mocked_path, mocked_copy, @@ -93,28 +95,28 @@ def test_copy_existing_abort_invalid( mocked_exists.return_value = True mocked_contents.return_value = ["file1.ext", "file2.ext", "__init__.py"] mocked_path.return_value.__enter__.side_effect = [ - "path/to/file1.ext", - "path/to/file2.ext", + Path("path/to/file1.ext"), + Path("path/to/file2.ext"), ] mocked_input.return_value = "aaa" user_resources.copy_resource("package.resources", Path("destination"), False) mocked_exists.assert_called_with(Path("destination")) - mocked_makedirs_p.assert_not_called() + mocked_mkdir.assert_not_called() mocked_contents.assert_not_called() mocked_copy.assert_not_called() @patch("dakara_player.user_resources.input") - @patch.object(Path, "copy", autospec=True) + @patch("dakara_player.user_resources.copy", autospec=True) @patch("dakara_player.user_resources.path", autospec=True) @patch("dakara_player.user_resources.contents", autospec=True) - @patch.object(Path, "makedirs_p", autospec=True) + @patch.object(Path, "mkdir", autospec=True) @patch.object(Path, "exists", autospec=True) def test_copy_existing_overwrite( self, mocked_exists, - mocked_makedirs_p, + mocked_mkdir, mocked_contents, mocked_path, mocked_copy, @@ -124,33 +126,35 @@ def test_copy_existing_overwrite( mocked_exists.return_value = True mocked_contents.return_value = ["file1.ext", "file2.ext", "__init__.py"] mocked_path.return_value.__enter__.side_effect = [ - "path/to/file1.ext", - "path/to/file2.ext", + Path("path/to/file1.ext"), + Path("path/to/file2.ext"), ] mocked_input.return_value = "yes" user_resources.copy_resource("package.resources", Path("destination"), False) mocked_exists.assert_called_with(Path("destination")) - mocked_makedirs_p.assert_called_with(Path("destination")) + mocked_mkdir.assert_called_with( + Path("destination"), parents=True, exist_ok=True + ) mocked_contents.assert_called_with("package.resources") mocked_copy.assert_has_calls( [ - call(Path("path/to/file1.ext"), "destination"), - call(Path("path/to/file2.ext"), "destination"), + call(Path("path/to/file1.ext"), Path("destination")), + call(Path("path/to/file2.ext"), Path("destination")), ] ) @patch("dakara_player.user_resources.input") - @patch.object(Path, "copy", autospec=True) + @patch("dakara_player.user_resources.copy", autospec=True) @patch("dakara_player.user_resources.path", autospec=True) @patch("dakara_player.user_resources.contents", autospec=True) - @patch.object(Path, "makedirs_p", autospec=True) + @patch.object(Path, "mkdir", autospec=True) @patch.object(Path, "exists", autospec=True) def test_copy_existing_force( self, mocked_exists, - mocked_makedirs_p, + mocked_mkdir, mocked_contents, mocked_path, mocked_copy, @@ -160,39 +164,39 @@ def test_copy_existing_force( mocked_exists.return_value = True mocked_contents.return_value = ["file1.ext", "file2.ext", "__init__.py"] mocked_path.return_value.__enter__.side_effect = [ - "path/to/file1.ext", - "path/to/file2.ext", + Path("path/to/file1.ext"), + Path("path/to/file2.ext"), ] user_resources.copy_resource("package.resources", Path("destination"), True) mocked_input.assert_not_called() mocked_exists.assert_not_called() - mocked_makedirs_p.assert_called_with(Path("destination")) + mocked_mkdir.assert_called_with( + Path("destination"), parents=True, exist_ok=True + ) mocked_contents.assert_called_with("package.resources") mocked_copy.assert_has_calls( [ - call(Path("path/to/file1.ext"), "destination"), - call(Path("path/to/file2.ext"), "destination"), + call(Path("path/to/file1.ext"), Path("destination")), + call(Path("path/to/file2.ext"), Path("destination")), ] ) -@patch.object(AppDirsPath, "user_data_dir", new_callable=PropertyMock) +@patch.object(PlatformDirs, "user_data_path", new_callable=PropertyMock) @patch("dakara_player.user_resources.copy_resource", autospec=True) class CreateResourceFilesTestCase(TestCase): """Test the create_resource_files function.""" - @patch.object(Path, "makedirs_p", autospec=True) - def test_create( - self, mocked_makedirs_p, mocked_copy_resource, mocked_user_data_dir - ): + @patch.object(Path, "mkdir", autospec=True) + def test_create(self, mocked_mkdir, mocked_copy_resource, mocked_user_data_path): """Test to create resource files.""" - mocked_user_data_dir.return_value = Path("directory") + mocked_user_data_path.return_value = Path("directory") with self.assertLogs("dakara_player.user_resources", "DEBUG") as logger: user_resources.create_resource_files() - mocked_makedirs_p.assert_called_with(Path("directory")) + mocked_mkdir.assert_called_with(Path("directory"), parents=True, exist_ok=True) mocked_copy_resource.assert_has_calls( [ call( diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..bbb38ff --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,17 @@ +from pathlib import Path +from tempfile import gettempdir + + +def get_temp_dir() -> Path: + """Return the default temporary directory. + + This function fixes the problem on Windows CI where `tempfile.gettempdir` + would return a DOS short path, not a Windows long path. + + See: + https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#short-vs-long-names + + Returns: + pathlib.Path: Long path to the default temporary directory. + """ + return Path(gettempdir()).resolve()