Обработка сетевых ошибок

This commit is contained in:
Lev Plyusnin 2025-06-15 11:02:18 +07:00
parent 2ffd45d36e
commit e435328907
No known key found for this signature in database
GPG key ID: 21C6C2C9C0A4460D
4 changed files with 76 additions and 13 deletions

View file

@ -74,6 +74,8 @@ usage: yandex-music-downloader [-h] [--quality <Качество>] [--skip-exist
[--only-music] [--only-music]
[--compatibility-level <Уровень совместимости>] [--compatibility-level <Уровень совместимости>]
[--timeout <Время ожидания>] [--timeout <Время ожидания>]
[--tries <Количество попыток>]
[--retry-delay <Задержка>]
(--artist-id <ID исполнителя> | --album-id <ID альбома> | --track-id <ID трека> | --playlist-id <владелец плейлиста>/<тип плейлиста> | -u URL) (--artist-id <ID исполнителя> | --album-id <ID альбома> | --track-id <ID трека> | --playlist-id <владелец плейлиста>/<тип плейлиста> | -u URL)
[--unsafe-path] [--dir <Папка>] [--unsafe-path] [--dir <Папка>]
[--path-pattern <Паттерн>] --token <Токен> [--path-pattern <Паттерн>] --token <Токен>
@ -100,8 +102,14 @@ options:
--only-music Загружать только музыкальные альбомы (пропускать подкасты и аудиокниги) --only-music Загружать только музыкальные альбомы (пропускать подкасты и аудиокниги)
--compatibility-level <Уровень совместимости> --compatibility-level <Уровень совместимости>
Уровень совместимости, от 0 до 1. См. README для подробного описания (по умолчанию: 1) Уровень совместимости, от 0 до 1. См. README для подробного описания (по умолчанию: 1)
Сетевые параметры:
--timeout <Время ожидания> --timeout <Время ожидания>
Время ожидания ответа от сервера, в секундах. Увеличьте если возникают сетевые ошибки (по умолчанию: 20) Время ожидания ответа от сервера, в секундах. Увеличьте если возникают сетевые ошибки (по умолчанию: 20)
--tries <Количество попыток>
Количество попыток при возникновении сетевых ошибок. 0 - бесконечное количество попыток (по умолчанию: 20)
--retry-delay <Задержка>
Задержка между повторными запросами при сетевых ошибках (по умолчанию: 5)
ID: ID:
--artist-id <ID исполнителя> --artist-id <ID исполнителя>

View file

@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "yandex-music-downloader" name = "yandex-music-downloader"
version = "3.4.10" version = "3.5.0"
description = "Загрузчик музыки с сервиса Яндекс.Музыка" description = "Загрузчик музыки с сервиса Яндекс.Музыка"
requires-python = ">=3.9" requires-python = ">=3.9"
readme = "README.md" readme = "README.md"

View file

@ -6,7 +6,7 @@ import re
import time import time
import typing import typing
from argparse import ArgumentTypeError from argparse import ArgumentTypeError
from collections.abc import Generator, Iterable from collections.abc import Callable, Generator, Iterable
from pathlib import Path from pathlib import Path
from typing import Optional, Union from typing import Optional, Union
from urllib.parse import urlparse from urllib.parse import urlparse
@ -52,17 +52,25 @@ def compatibility_level_arg(astr: str) -> int:
) )
def natural_int_arg(astr: str) -> int: def checked_int_arg(
aint = int(astr) min_value: int, max_value: Optional[int] = None
if aint > 0: ) -> Callable[[str], int]:
return aint def func(astr: str) -> int:
raise ArgumentTypeError("Значение должен быть > 0") 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: def cover_resolution_arg(astr: str) -> int:
if astr == "original": if astr == "original":
return -1 return -1
return int(astr) return checked_int_arg(100)(astr)
def lyrics_format_arg(astr: str) -> core.LyricsFormat: def lyrics_format_arg(astr: str) -> core.LyricsFormat:
@ -115,7 +123,7 @@ def main():
"--delay", "--delay",
default=DEFAULT_DELAY, default=DEFAULT_DELAY,
metavar="<Задержка>", metavar="<Задержка>",
type=int, type=checked_int_arg(0),
help=show_default("Задержка между запросами, в секундах"), help=show_default("Задержка между запросами, в секундах"),
) )
common_group.add_argument( common_group.add_argument(
@ -137,15 +145,33 @@ def main():
f"Уровень совместимости, от {core.MIN_COMPATIBILITY_LEVEL} до {core.MAX_COMPATIBILITY_LEVEL}. См. README для подробного описания" 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", "--timeout",
metavar="<Время ожидания>", metavar="<Время ожидания>",
default=20, default=20,
type=natural_int_arg, type=checked_int_arg(1),
help=show_default( 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) common_group.add_argument("--debug", action="store_true", help=argparse.SUPPRESS)
id_group_meta = parser.add_argument_group("ID") id_group_meta = parser.add_argument_group("ID")
@ -221,7 +247,12 @@ def main():
print("Параметер url указан в неверном формате") print("Параметер url указан в неверном формате")
return 1 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] = [] result_tracks: Iterable[Track] = []
def album_tracks_gen(album_ids: Iterable[Union[int, str]]) -> Generator[Track]: def album_tracks_gen(album_ids: Iterable[Union[int, str]]) -> Generator[Track]:

View file

@ -1,6 +1,7 @@
import datetime as dt import datetime as dt
import hashlib import hashlib
import re import re
import time
import typing import typing
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
@ -33,6 +34,7 @@ from yandex_music import (
Track, Track,
YandexMusicModel, YandexMusicModel,
) )
from yandex_music.exceptions import NetworkError
from ymd import api from ymd import api
from ymd.api import ( from ymd.api import (
@ -91,9 +93,31 @@ class AlbumCover:
mime_type: MimeType 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 = Client(token)
client.request.set_timeout(timeout) 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() return client.init()