From e435328907ff416bb65b3f248654283a26e7d921 Mon Sep 17 00:00:00 2001 From: Lev Plyusnin Date: Sun, 15 Jun 2025 11:02:18 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=D0=B5=D1=82=D0=B5=D0=B2=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ pyproject.toml | 2 +- ymd/cli.py | 53 +++++++++++++++++++++++++++++++++++++++----------- ymd/core.py | 26 ++++++++++++++++++++++++- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2efccc2..c167fdc 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ usage: yandex-music-downloader [-h] [--quality <Качество>] [--skip-exist [--only-music] [--compatibility-level <Уровень совместимости>] [--timeout <Время ожидания>] + [--tries <Количество попыток>] + [--retry-delay <Задержка>] (--artist-id | --album-id | --track-id | --playlist-id <владелец плейлиста>/<тип плейлиста> | -u URL) [--unsafe-path] [--dir <Папка>] [--path-pattern <Паттерн>] --token <Токен> @@ -100,8 +102,14 @@ options: --only-music Загружать только музыкальные альбомы (пропускать подкасты и аудиокниги) --compatibility-level <Уровень совместимости> Уровень совместимости, от 0 до 1. См. README для подробного описания (по умолчанию: 1) + +Сетевые параметры: --timeout <Время ожидания> Время ожидания ответа от сервера, в секундах. Увеличьте если возникают сетевые ошибки (по умолчанию: 20) + --tries <Количество попыток> + Количество попыток при возникновении сетевых ошибок. 0 - бесконечное количество попыток (по умолчанию: 20) + --retry-delay <Задержка> + Задержка между повторными запросами при сетевых ошибках (по умолчанию: 5) ID: --artist-id diff --git a/pyproject.toml b/pyproject.toml index 36d2f2b..8c769e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "yandex-music-downloader" -version = "3.4.10" +version = "3.5.0" description = "Загрузчик музыки с сервиса Яндекс.Музыка" requires-python = ">=3.9" readme = "README.md" diff --git a/ymd/cli.py b/ymd/cli.py index 31277d9..99adf4b 100644 --- a/ymd/cli.py +++ b/ymd/cli.py @@ -6,7 +6,7 @@ import re import time import typing from argparse import ArgumentTypeError -from collections.abc import Generator, Iterable +from collections.abc import Callable, Generator, Iterable from pathlib import Path from typing import Optional, Union from urllib.parse import urlparse @@ -52,17 +52,25 @@ def compatibility_level_arg(astr: str) -> int: ) -def natural_int_arg(astr: str) -> int: - aint = int(astr) - if aint > 0: - return aint - raise ArgumentTypeError("Значение должен быть > 0") +def checked_int_arg( + min_value: int, max_value: Optional[int] = None +) -> Callable[[str], int]: + def func(astr: str) -> int: + aint = int(astr) + if aint >= min_value and (max_value is None or aint <= max_value): + return aint + error_text = f"Значение должен быть >= {min_value}" + if max_value is not None: + error_text += f" и <= {max_value}" + raise ArgumentTypeError(error_text) + + return func def cover_resolution_arg(astr: str) -> int: if astr == "original": return -1 - return int(astr) + return checked_int_arg(100)(astr) def lyrics_format_arg(astr: str) -> core.LyricsFormat: @@ -115,7 +123,7 @@ def main(): "--delay", default=DEFAULT_DELAY, metavar="<Задержка>", - type=int, + type=checked_int_arg(0), help=show_default("Задержка между запросами, в секундах"), ) common_group.add_argument( @@ -137,15 +145,33 @@ def main(): f"Уровень совместимости, от {core.MIN_COMPATIBILITY_LEVEL} до {core.MAX_COMPATIBILITY_LEVEL}. См. README для подробного описания" ), ) - common_group.add_argument( + + network_group = parser.add_argument_group("Сетевые параметры") + network_group.add_argument( "--timeout", metavar="<Время ожидания>", default=20, - type=natural_int_arg, + type=checked_int_arg(1), help=show_default( "Время ожидания ответа от сервера, в секундах. Увеличьте если возникают сетевые ошибки" ), ) + network_group.add_argument( + "--tries", + metavar="<Количество попыток>", + default=20, + type=checked_int_arg(0), + help=show_default( + "Количество попыток при возникновении сетевых ошибок. 0 - бесконечное количество попыток" + ), + ) + network_group.add_argument( + "--retry-delay", + metavar="<Задержка>", + default=5, + type=checked_int_arg(0), + help=show_default("Задержка между повторными запросами при сетевых ошибках"), + ) common_group.add_argument("--debug", action="store_true", help=argparse.SUPPRESS) id_group_meta = parser.add_argument_group("ID") @@ -221,7 +247,12 @@ def main(): print("Параметер url указан в неверном формате") return 1 - client = core.init_client(args.token, args.timeout) + client = core.init_client( + token=args.token, + timeout=args.timeout, + max_try_count=args.tries, + retry_delay=args.retry_delay, + ) result_tracks: Iterable[Track] = [] def album_tracks_gen(album_ids: Iterable[Union[int, str]]) -> Generator[Track]: diff --git a/ymd/core.py b/ymd/core.py index 8c32a4f..41bb5e2 100644 --- a/ymd/core.py +++ b/ymd/core.py @@ -1,6 +1,7 @@ import datetime as dt import hashlib import re +import time import typing from collections.abc import Callable from dataclasses import dataclass @@ -33,6 +34,7 @@ from yandex_music import ( Track, YandexMusicModel, ) +from yandex_music.exceptions import NetworkError from ymd import api from ymd.api import ( @@ -91,9 +93,31 @@ class AlbumCover: mime_type: MimeType -def init_client(token: str, timeout: int) -> Client: +def init_client( + token: str, timeout: int, max_try_count: int, retry_delay: int +) -> Client: + assert timeout > 0 + assert max_try_count >= 0 + assert retry_delay >= 0 + client = Client(token) client.request.set_timeout(timeout) + + original_wrapper = client.request._request_wrapper + + def retry_wrapper(*args, **kwargs): + try_count = 0 + while True: + try: + return original_wrapper(*args, **kwargs) + except NetworkError as error: + if max_try_count == 0 or try_count < max_try_count: + try_count += 1 + time.sleep(retry_delay) + continue + raise error + + client.request._request_wrapper = retry_wrapper return client.init()