From 1c03889d95be8606d264281c5028c0c319d7e27e Mon Sep 17 00:00:00 2001 From: Marshal Date: Mon, 18 Nov 2019 23:54:46 +0300 Subject: [PATCH 01/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D1=8F=D1=82=D1=8C=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=8B?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=BF=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D1=81=D0=B8-=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20(#139).=20?= =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 +- Pipfile.lock | 48 +++++++++++++++++++++++++---------- README.rst | 19 ++++++++++++++ yandex_music/client.py | 6 ++++- yandex_music/utils/request.py | 34 ++++++++++++++----------- 5 files changed, 79 insertions(+), 30 deletions(-) diff --git a/Pipfile b/Pipfile index a82d7185..889c4e40 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ pytest-cov = "*" codecov = "*" [packages] -requests = "*" +requests = { extras = ["socks"] } [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 7f560722..faee4012 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c889ddb9cbefe66436c2a82bf83cac14e9a8f1a85ad8b2e9d0d8aa891f33a5fc" + "sha256": "4af012e674da3f8f42b23516f2f3bdd4571bc89b117efec071530308cf97e478" }, "pipfile-spec": 6, "requires": { @@ -37,7 +37,18 @@ ], "version": "==2.8" }, + "pysocks": { + "hashes": [ + "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", + "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", + "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" + ], + "version": "==1.7.1" + }, "requests": { + "extras": [ + "socks" + ], "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" @@ -47,10 +58,10 @@ }, "urllib3": { "hashes": [ - "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", - "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==1.25.6" + "version": "==1.25.7" } }, "develop": { @@ -104,6 +115,14 @@ "index": "pypi", "version": "==2.0.15" }, + "colorama": { + "hashes": [ + "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", + "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.1" + }, "coverage": { "hashes": [ "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", @@ -249,18 +268,18 @@ }, "pyparsing": { "hashes": [ - "sha256:4acadc9a2b96c19fe00932a38ca63e601180c39a189a696abce1eaab641447e1", - "sha256:61b5ed888beab19ddccab3478910e2076a6b5a0295dffc43021890e136edf764" + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" ], - "version": "==2.4.4" + "version": "==2.4.5" }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:8e256fe71eb74e14a4d20a5987bb5e1488f0511ee800680aaedc62b9358714e8", + "sha256:ff0090819f669aaa0284d0f4aad1a6d9d67a6efdc6dd4eb4ac56b704f890a0d6" ], "index": "pypi", - "version": "==5.2.2" + "version": "==5.2.4" }, "pytest-cov": { "hashes": [ @@ -278,6 +297,9 @@ "version": "==2019.3" }, "requests": { + "extras": [ + "socks" + ], "hashes": [ "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" @@ -359,10 +381,10 @@ }, "urllib3": { "hashes": [ - "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", - "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==1.25.6" + "version": "==1.25.7" }, "wcwidth": { "hashes": [ diff --git a/README.rst b/README.rst index 633f5efd..67de2dc0 100644 --- a/README.rst +++ b/README.rst @@ -176,6 +176,25 @@ Microsoft Store. Так как API является закрытым и испо Первым треком из примера является следующий трек: music.yandex.ru/album/**1193829**/track/**10994777** +Выполнение запросов с использование прокси: + +.. code:: python + + from yandex_music.utils.request import Request + from yandex_music.client import Client + + request = Request(proxy_url='socks5://user:password@host:port') + client = Client(request=request) + +Примеры proxy url: + +- socks5://user:password@host:port +- http://host:port +- https://host:port +- http://user:password@host + +Больше примеров тут: `proxies - advanced usage - requests `_ + -------------------- Изучение по примерам -------------------- diff --git a/yandex_music/client.py b/yandex_music/client.py index 550289d6..8d0f9b40 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -77,7 +77,11 @@ def __init__(self, token=None, base_url=None, oauth_url=None, request=None): self.base_url = base_url self.oauth_url = oauth_url - self._request = request or Request(self) + if request: + self._request = request + self._request.set_and_return_client(self) + else: + self._request = Request(self) self.account = self.account_status().account diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index 3f20d8f3..e51d8f3e 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -24,26 +24,30 @@ class Request: client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex Music. headers (:obj:`dict`, optional): Заголовки передаваемые с каждым запросом. - proxies (:obj:`dict`, optional): Прокси. + proxy_url (:obj:`str`, optional): Прокси. """ def __init__(self, - client, + client=None, headers=None, - proxies=None): - - self.client = client - + proxy_url=None): self.headers = headers or HEADERS.copy() - if self.client.token: - self.set_authorization(self.client.token) + self.client = self.set_and_return_client(client) - self.proxies = proxies # TODO + self.proxies = {'http': proxy_url, 'https': proxy_url} if proxy_url else None def set_authorization(self, token): self.headers.update({'Authorization': f'OAuth {token}'}) + def set_and_return_client(self, client): + self.client = client + + if self.client and self.client.token: + self.set_authorization(self.client.token) + + return self.client + @staticmethod def _convert_camel_to_snake(text): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', text) @@ -107,21 +111,21 @@ def _request_wrapper(self, *args, **kwargs): raise NetworkError(f'{message} ({resp.status_code})') def get(self, url, params=None, timeout=5, *args, **kwargs): - result = self._request_wrapper('GET', url, params=params, headers=self.headers, timeout=timeout, - *args, **kwargs) + result = self._request_wrapper('GET', url, params=params, headers=self.headers, proxies=self.proxies, + timeout=timeout, *args, **kwargs) return self._parse(result.content).result def post(self, url, data=None, timeout=5, *args, **kwargs): - result = self._request_wrapper('POST', url, headers=self.headers, data=data, timeout=timeout, - *args, **kwargs) + result = self._request_wrapper('POST', url, headers=self.headers, proxies=self.proxies, data=data, + timeout=timeout, *args, **kwargs) return self._parse(result.content).result def retrieve(self, url, timeout=5, *args, **kwargs): - return self._request_wrapper('GET', url, timeout=timeout, *args, **kwargs) + return self._request_wrapper('GET', url, proxies=self.proxies, timeout=timeout, *args, **kwargs) def download(self, url, filename, timeout=5, *args, **kwargs): - result = self.retrieve(url, timeout=timeout, *args, *kwargs) + result = self.retrieve(url, proxies=self.proxies, timeout=timeout, *args, *kwargs) with open(filename, 'wb') as f: f.write(result.content) From b517e57e6ca030c3ddef9f61d629f6437a409a1b Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 19 Nov 2019 16:53:21 +0300 Subject: [PATCH 02/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81-=D0=BE=D0=B1=D1=91=D1=80=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=BC=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20=D1=81=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BF=D1=87=D0=B5=D0=B9=20(CaptchaResponse).=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B5=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20-=20CaptchaRequired.=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D1=81=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE=D0=B4=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=D1=8E=20=D0=BF=D1=80=D0=BE=D0=B9=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83.=20#1?= =?UTF-8?q?40?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/exceptions.py | 7 +++++++ yandex_music/utils/captcha_response.py | 26 ++++++++++++++++++++++++++ yandex_music/utils/request.py | 14 ++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 yandex_music/utils/captcha_response.py diff --git a/yandex_music/exceptions.py b/yandex_music/exceptions.py index 39f82c9a..c280223f 100644 --- a/yandex_music/exceptions.py +++ b/yandex_music/exceptions.py @@ -20,6 +20,13 @@ class Unauthorized(YandexMusicError): pass +class CaptchaRequired(YandexMusicError): + """Класс исключения, вызываемый в случае необходимости ввода проверочного кода. + """ + + pass + + class NetworkError(YandexMusicError): """Базовый класс исключений, вызываемых для ошибок, связанных с запросами к серверу. diff --git a/yandex_music/utils/captcha_response.py b/yandex_music/utils/captcha_response.py new file mode 100644 index 00000000..5e9d3d9b --- /dev/null +++ b/yandex_music/utils/captcha_response.py @@ -0,0 +1,26 @@ +from yandex_music import YandexMusicObject + + +class CaptchaResponse(YandexMusicObject): + def __init__(self, + x_captcha_url, + x_captcha_key, + error_description, + error, + client=None, + **kwargs): + self.x_captcha_url = x_captcha_url + self.x_captcha_key = x_captcha_key + self.error_description = error_description + self.error = error + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(CaptchaResponse, cls).de_json(data, client) + + return cls(client=client, **data) diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index 3f20d8f3..871f785a 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -3,9 +3,9 @@ import logging import requests +from yandex_music.utils.captcha_response import CaptchaResponse from yandex_music.utils.response import Response -from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, YandexMusicError - +from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, YandexMusicError, CaptchaRequired USER_AGENT = 'Yandex-Music-API' HEADERS = { @@ -74,6 +74,9 @@ def _parse(self, json_data) -> Response: except (AttributeError, ValueError): raise YandexMusicError('Invalid server response') + if not data.get('result'): + data = {'result': data, 'error': data.get('error'), 'error_description': data.get('error_description')} + return Response.de_json(data, self.client) def _request_wrapper(self, *args, **kwargs): @@ -92,9 +95,12 @@ def _request_wrapper(self, *args, **kwargs): if 200 <= resp.status_code <= 299: return resp - message = self._parse(resp.content).error or 'Unknown HTTPError' + parse = self._parse(resp.content) + message = parse.error or 'Unknown HTTPError' - if resp.status_code in (401, 403): + if message == '403 CAPTCHA required': + raise CaptchaRequired(message, CaptchaResponse.de_json(parse.result, self.client)) + elif resp.status_code in (401, 403): raise Unauthorized(message) elif resp.status_code == 400: raise BadRequest(message) From d5578d7be120eb466ca4f8d94eb0b0341eaa86cc Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 19 Nov 2019 17:31:30 +0300 Subject: [PATCH 03/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F.=20=D0=94=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=BF=D1=87=D0=B8.=20#140?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yandex_music.utils.captcha_response.rst | 7 +++ docs/source/yandex_music.utils.rst | 1 + yandex_music/utils/captcha_response.py | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 docs/source/yandex_music.utils.captcha_response.rst diff --git a/docs/source/yandex_music.utils.captcha_response.rst b/docs/source/yandex_music.utils.captcha_response.rst new file mode 100644 index 00000000..183d0b1a --- /dev/null +++ b/docs/source/yandex_music.utils.captcha_response.rst @@ -0,0 +1,7 @@ +yandex_music.utils.captcha_response.CaptchaResponse +=================================================== + +.. autoclass:: yandex_music.utils.captcha_response.CaptchaResponse + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/yandex_music.utils.rst b/docs/source/yandex_music.utils.rst index 6f89caa1..d196cc89 100644 --- a/docs/source/yandex_music.utils.rst +++ b/docs/source/yandex_music.utils.rst @@ -5,4 +5,5 @@ yandex_music.utils.request yandex_music.utils.response + yandex_music.utils.captcha_response yandex_music.utils.difference \ No newline at end of file diff --git a/yandex_music/utils/captcha_response.py b/yandex_music/utils/captcha_response.py index 5e9d3d9b..b41feb51 100644 --- a/yandex_music/utils/captcha_response.py +++ b/yandex_music/utils/captcha_response.py @@ -2,6 +2,26 @@ class CaptchaResponse(YandexMusicObject): + """Класс представляющий ответ сервера с запросом на ввод капчи. + + Attributes: + x_captcha_url (:obj:`str`): Ссылка на изображение с капчей. + x_captcha_key (:obj:`str`): Уникальный ключ капчи. + error_description (:obj:`str`): Описание ошибки. + error (:obj:`str`): Код ошибки. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Args: + x_captcha_url (:obj:`str`): Ссылка на изображение с капчей. + x_captcha_key (:obj:`str`): Уникальный ключ капчи. + error_description (:obj:`str`): Описание ошибки. + error (:obj:`str`): Код ошибки. + client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент + Yandex Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + def __init__(self, x_captcha_url, x_captcha_key, @@ -15,9 +35,34 @@ def __init__(self, self.error = error self.client = client + self._id_attrs = (self.x_captcha_key, self.x_captcha_url) + + def download(self, filename=None): + """Загрузка изображения с капчей. + + Args: + filename (:obj:`str`, optional): Путь и(или) название файла вместе с расширением. По умолчанию ключ + капчи и расширение `.gif`. + """ + + if not filename: + filename = f'{self.x_captcha_key}.gif' + + self.client.request.download(self.x_captcha_url, filename) @classmethod def de_json(cls, data, client): + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Returns: + :obj:`yandex_music.utils.captcha_response.CaptchaResponse`: Объект класса + :class:`yandex_music.utils.captcha_response.CaptchaResponse`. + """ if not data: return None From 844e2904cfea7d98b33b15ab3a042232769ad461 Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 19 Nov 2019 18:35:09 +0300 Subject: [PATCH 04/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=B0=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BF=D1=87=D1=83.=20=D0=98=D1=81=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D0=BE=D0=B5=20=D1=81=20=D0=BA=D0=B0=D0=BF?= =?UTF-8?q?=D1=87=D0=B5=D0=B9=20=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D1=82=D0=BE?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D0=B4=D0=B2=D0=B0:=20CaptchaRequired,=20Capt?= =?UTF-8?q?chaWrong.=20#140?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 17 ++++++++++++----- yandex_music/exceptions.py | 15 ++++++++++++++- yandex_music/utils/request.py | 8 +++++--- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index 550289d6..85e20a72 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -25,7 +25,6 @@ 'playlist': PlaylistsLikes.de_list, } - logging.getLogger(__name__).addHandler(logging.NullHandler()) @@ -82,7 +81,7 @@ def __init__(self, token=None, base_url=None, oauth_url=None, request=None): self.account = self.account_status().account @classmethod - def from_credentials(cls, username, password, *args, **kwargs): + def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, *args, **kwargs): """Инициализция клиента по логину и паролю. Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить и @@ -91,13 +90,16 @@ def from_credentials(cls, username, password, *args, **kwargs): Args: username (:obj:`str`): Логин клиента (идентификатор). password (:obj:`str`): Пароль клиента (аутентификатор). + x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). + x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. Returns: :obj:`yandex_music.Client`. """ - return cls(cls().generate_token_by_username_and_password(username, password), *args, **kwargs) + return cls(cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, + x_captcha_key=x_captcha_key), *args, **kwargs) @classmethod def from_token(cls, token, *args, **kwargs): @@ -116,14 +118,16 @@ def from_token(cls, token, *args, **kwargs): return cls(token=token, *args, **kwargs) @log - def generate_token_by_username_and_password(self, username, password, grant_type='password', - timeout=None, *args, **kwargs): + def generate_token_by_username_and_password(self, username, password, grant_type='password', x_captcha_answer=None, + x_captcha_key=None, timeout=None, *args, **kwargs): """Метод получения OAuth токена по логину и паролю. Args: username (:obj:`str`): Логин клиента (идентификатор). password (:obj:`str`): Пароль клиента (аутентификатор). grant_type (:obj:`str`, optional): Тип разрешения OAuth. + x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). + x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания ответа от сервера вместо указанного при создании пула. **kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). @@ -145,6 +149,9 @@ def generate_token_by_username_and_password(self, username, password, grant_type 'password': password } + if x_captcha_answer and x_captcha_key: + data.update({'x_captcha_answer': x_captcha_answer, 'x_captcha_key': x_captcha_key}) + result = self._request.post(url, data, timeout=timeout, *args, **kwargs) return result.get('access_token') diff --git a/yandex_music/exceptions.py b/yandex_music/exceptions.py index c280223f..88243e31 100644 --- a/yandex_music/exceptions.py +++ b/yandex_music/exceptions.py @@ -20,13 +20,26 @@ class Unauthorized(YandexMusicError): pass -class CaptchaRequired(YandexMusicError): +class Captcha(YandexMusicError): + """Базовый класс, представляющий исключение связанное с капчей. + """ + + pass + + +class CaptchaRequired(Captcha): """Класс исключения, вызываемый в случае необходимости ввода проверочного кода. """ pass +class CaptchaWrong(Captcha): + """Класс исключения, вызываемый в случае неправильного ввода капчи. + """ + + pass + class NetworkError(YandexMusicError): """Базовый класс исключений, вызываемых для ошибок, связанных с запросами к серверу. diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index 871f785a..0a29e1ca 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -5,7 +5,8 @@ from yandex_music.utils.captcha_response import CaptchaResponse from yandex_music.utils.response import Response -from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, YandexMusicError, CaptchaRequired +from yandex_music.exceptions import Unauthorized, BadRequest, NetworkError, YandexMusicError, CaptchaRequired, \ + CaptchaWrong USER_AGENT = 'Yandex-Music-API' HEADERS = { @@ -98,8 +99,9 @@ def _request_wrapper(self, *args, **kwargs): parse = self._parse(resp.content) message = parse.error or 'Unknown HTTPError' - if message == '403 CAPTCHA required': - raise CaptchaRequired(message, CaptchaResponse.de_json(parse.result, self.client)) + if 'CAPTCHA' in message: + exception = CaptchaWrong if 'Wrong' in message else CaptchaRequired + raise exception(message, CaptchaResponse.de_json(parse.result, self.client)) elif resp.status_code in (401, 403): raise Unauthorized(message) elif resp.status_code == 400: From 301fa81bb99f8577d7d5160d0a7a1966f12533c4 Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 19 Nov 2019 18:38:00 +0300 Subject: [PATCH 05/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BF=D1=87=D0=B8.=20#140?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.rst b/README.rst index 633f5efd..91d92e6e 100644 --- a/README.rst +++ b/README.rst @@ -176,6 +176,24 @@ Microsoft Store. Так как API является закрытым и испо Первым треком из примера является следующий трек: music.yandex.ru/album/**1193829**/track/**10994777** +Пример инициализации клиента с обработкой капчи: + +.. code:: python + + def init_client(): + client = captcha_key = captcha_answer = None + while not client: + try: + client = Client.from_credentials('login', 'pass', captcha_answer, captcha_key) + except Captcha as e: + captcha = e.args[1] + captcha.download('captcha.png') + + captcha_key = captcha.x_captcha_key + captcha_answer = input('Число с картинки: ') + + return client + -------------------- Изучение по примерам -------------------- From 2b437350beea513fa13c0f47a3df6e8ccaa68263 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 20:30:11 +0400 Subject: [PATCH 06/30] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20Sea?= =?UTF-8?q?rch=20(#143)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Задокументирован класс Search * Задокументировано свойство Search.nocorrect * Исправлена документация класса Search Указана опциональность полей Добавлена пустая строчка * Правки в документации класса Search * Указана опциональность полей * Испрвлена докуменация свойства Search.best * Испрвлена докуменация Search --- yandex_music/search/search.py | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/yandex_music/search/search.py b/yandex_music/search/search.py index ad9dce1e..09e1c892 100644 --- a/yandex_music/search/search.py +++ b/yandex_music/search/search.py @@ -2,6 +2,38 @@ class Search(YandexMusicObject): + """Класс представляющий результаты поиска. + + Attributes: + search_request_id (:obj:`str`): ID запроса. + text (:obj:`str`): Текст запроса. + best (:obj:`yandex_music.Best`): Объект класса :class:`yandex_music.Best` представляющий лучший результат. + albums (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные альбомы. + artists (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденных исполнителей. + playlists (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные плейлисты. + tracks (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные треки. + videos (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные видео. + misspell_corrected (:obj:`bool`): Был ли исправлен запрос. + nocorrect (:obj:`bool`): Было ли отключено исправление результата. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Args: + search_request_id (:obj:`str`): ID запроса. + text (:obj:`str`): Текст запроса. + best (:obj:`yandex_music.Best`): Объект класса :class:`yandex_music.Best` представляющий лучший результат. + albums (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные альбомы. + artists (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденных исполнителей. + playlists (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные плейлисты. + tracks (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные треки. + videos (:obj:`yandex_music.SearchResult`): Объект класса :class:`yandex_music.SearchResult` представляющий найденные видео. + misspell_corrected (:obj:`bool`, optional): Был ли исправлен запрос. + nocorrect (:obj:`bool`, optional): Было ли отключено исправление результата. + client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + def __init__(self, search_request_id, text, @@ -33,6 +65,16 @@ def __init__(self, @classmethod def de_json(cls, data, client): + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Returns: + :obj:`yandex_music.Search`: Объект класса :class:`yandex_music.Search`. + """ if not data: return None From bbf7ad23702d42e6de2bdd8f6153533e91eb635d Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 21:00:58 +0400 Subject: [PATCH 07/30] =?UTF-8?q?=D0=9A=D0=BB=D0=B0=D1=81=D1=81=20Pager=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D0=B5=D0=BD=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D1=8C=20=D0=B1=D0=B8=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/__init__.py | 2 +- yandex_music/{artist => }/pager.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename yandex_music/{artist => }/pager.py (100%) diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index f216bcb1..db46aab7 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -23,7 +23,6 @@ from .artist.counts import Counts from .artist.description import Description from .artist.link import Link -from .artist.pager import Pager from .artist.ratings import Ratings from .artist.vinyl import Vinyl @@ -94,6 +93,7 @@ from .supplement.lyrics import Lyrics from .supplement.video_supplement import VideoSupplement +from .pager import Pager from .cover import Cover from .experiments import Experiments from .invocation_info import InvocationInfo diff --git a/yandex_music/artist/pager.py b/yandex_music/pager.py similarity index 100% rename from yandex_music/artist/pager.py rename to yandex_music/pager.py From 5bd1ad4c56a992ff542f21625032db38111bd525 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 21:30:07 +0400 Subject: [PATCH 08/30] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=B1=D0=BE=D0=BC=D0=BE=D0=B2=20=D0=B8=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B8=D1=82=D0=B5=D0=BB=D1=8F=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=20ArtistAlbums=20=D0=92=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20Cli?= =?UTF-8?q?ent=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=20artists=5Falbums?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/__init__.py | 1 + yandex_music/artist/artist.py | 5 +++++ yandex_music/artist/artist_albums.py | 26 ++++++++++++++++++++++++++ yandex_music/client.py | 19 ++++++++++++++++++- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 yandex_music/artist/artist_albums.py diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index db46aab7..bd45357a 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -19,6 +19,7 @@ from .album.track_position import TrackPosition from .artist.artist import Artist from .artist.artist_tracks import ArtistTracks +from .artist.artist_albums import ArtistAlbums from .artist.brief_info import BriefInfo from .artist.counts import Counts from .artist.description import Description diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index 53ffe1dc..3ac8ac69 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -95,6 +95,9 @@ def get_tracks(self, page=0, page_size=20, *args, **kwargs): """ return self.client.artists_tracks(self.id, page, page_size, *args, **kwargs) + def get_albums(self, page=0, page_size=20, sort_by='year', *args, **kwargs): + return self.client.artists_albums(self.id, page, page_size, sort_by, *args, **kwargs) + @classmethod def de_json(cls, data, client): if not data: @@ -129,3 +132,5 @@ def de_list(cls, data, client): downloadOpImage = download_op_image #: Псевдоним для :attr:`get_tracks` getTracks = get_tracks + #: Псевдоним для :attr:`get_albums` + getAlbums = get_albums diff --git a/yandex_music/artist/artist_albums.py b/yandex_music/artist/artist_albums.py new file mode 100644 index 00000000..dcffdfed --- /dev/null +++ b/yandex_music/artist/artist_albums.py @@ -0,0 +1,26 @@ +from yandex_music import YandexMusicObject + + +class ArtistAlbums(YandexMusicObject): + def __init__(self, + albums, + pager, + client=None, + **kwargs): + self.albums = albums + self.pager = pager + + self.client = client + self._id_attrs = (self.pager, self.albums) + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(ArtistAlbums, cls).de_json(data, client) + from yandex_music import Album, Pager + data['albums'] = Album.de_list(data.get('albums'), client) + data['pager'] = Pager.de_json(data.get('pager'), client) + + return cls(client=client, **data) diff --git a/yandex_music/client.py b/yandex_music/client.py index 8d0f9b40..e54e405b 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -4,7 +4,8 @@ from yandex_music import YandexMusicObject, Status, Settings, PermissionAlerts, Experiments, Artist, Album, Playlist, \ TracksList, Track, AlbumsLikes, ArtistsLikes, PlaylistsLikes, Feed, PromoCodeStatus, DownloadInfo, Search, \ - Suggestions, Landing, Genre, Dashboard, StationResult, StationTracksResult, BriefInfo, Supplement, ArtistTracks + Suggestions, Landing, Genre, Dashboard, StationResult, StationTracksResult, BriefInfo, Supplement, ArtistTracks, \ + ArtistAlbums from yandex_music.utils.request import Request from yandex_music.utils.difference import Difference from yandex_music.exceptions import InvalidToken @@ -920,6 +921,20 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No return ArtistTracks.de_json(result, self) + @log + def artists_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): + url = f'{self.base_url}/artists/{artist_id}/direct-albums' + + params = { + 'sort-by': sort_by, + 'page': page, + 'page-size': page_size + } + + result = self._request.get(url, params, timeout=timeout, *args, **kwargs) + + return ArtistAlbums.de_json(result, self) + def _like_action(self, object_type: str, ids: str or int or list, remove: bool = False, user_id: str or int = None, timeout=None, *args, **kwargs): if user_id is None: @@ -1140,6 +1155,8 @@ def users_dislikes_tracks_remove(self, track_ids: str or list, user_id: str or i artistsBriefInfo = artists_brief_info #: Псевдоним для :attr:`artists_tracks` artistsTracks = artists_tracks + #: Псевдоним для :attr:`artists_albums` + artistsAlbums = artists_albums #: Псевдоним для :attr:`users_likes_tracks_add` usersLikesTracksAdd = users_likes_tracks_add #: Псевдоним для :attr:`users_likes_tracks_remove` From be3152a7eed5157eb449ec817506b0335a3ddffb Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 21:52:17 +0400 Subject: [PATCH 09/30] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=97=D0=B0=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20ArtistAlbums=20?= =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=20get=5Falbums=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20?= =?UTF-8?q?Artist=20=D0=97=D0=B0=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=20artists=5Falbums=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D0=B0=20Client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/artist/artist.py | 4 ++++ yandex_music/artist/artist_albums.py | 26 ++++++++++++++++++++++++++ yandex_music/client.py | 16 ++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index 3ac8ac69..5e3b838c 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -96,6 +96,10 @@ def get_tracks(self, page=0, page_size=20, *args, **kwargs): return self.client.artists_tracks(self.id, page, page_size, *args, **kwargs) def get_albums(self, page=0, page_size=20, sort_by='year', *args, **kwargs): + """Сокращение для:: + + client.artists_albums(artist.id, page, page_size, sort_by, *args, **kwargs) + """ return self.client.artists_albums(self.id, page, page_size, sort_by, *args, **kwargs) @classmethod diff --git a/yandex_music/artist/artist_albums.py b/yandex_music/artist/artist_albums.py index dcffdfed..91e2ecb0 100644 --- a/yandex_music/artist/artist_albums.py +++ b/yandex_music/artist/artist_albums.py @@ -2,6 +2,22 @@ class ArtistAlbums(YandexMusicObject): + """Класс представляющий страницу списка альбомов артиста. + + Attributes: + albums (:obj:`list` из :obj:`yandex_music.Album`): Список альбомов артиста. + pager (:obj:`yandex_music.Pager`): Объект класса :class:`yandex_music.Pager` представляющий пагинатор. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Args: + albums (:obj:`list` из :obj:`yandex_music.Album`): Список альбомов артиста. + pager (:obj:`yandex_music.Pager`): Объект класса :class:`yandex_music.Pager` представляющий пагинатор. + client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + def __init__(self, albums, pager, @@ -15,6 +31,16 @@ def __init__(self, @classmethod def de_json(cls, data, client): + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Returns: + :obj:`yandex_music.ArtistAlbums`: Объект класса :class:`yandex_music.ArtistAlbums`. + """ if not data: return None diff --git a/yandex_music/client.py b/yandex_music/client.py index e54e405b..aaabde78 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -923,6 +923,22 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No @log def artists_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): + """Получение треков артиста. + + Args: + artist_id (:obj:`str` | :obj:`int`): Уникальный идентификатор артиста. + page (:obj:`int`, optional): Номер страницы. + page_size (:obj:`int`, optional): Количество треков на странице. + sort_by (:obj:`str`, optional): Параметр для сортирвки. + timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания + ответа от сервера вместо указанного при создании пула. + **kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). + + Returns: + :obj:`yandex_music.ArtistAlbums`: Объекта класса :class:`yandex_music.ArtistsTracks` + представляющий страницу списка альбомов артиста + """ + url = f'{self.base_url}/artists/{artist_id}/direct-albums' params = { From d51d0b7887618180caaf0f37bbfd4045999a5fb1 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 22:10:38 +0400 Subject: [PATCH 10/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=82=D0=B5=D1=81=D1=82=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20ArtistAlbums?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_artist_albums.py | 39 +++++++++++++++++++++++++++++++++++++ yandex_music/__init__.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/test_artist_albums.py diff --git a/tests/test_artist_albums.py b/tests/test_artist_albums.py new file mode 100644 index 00000000..5b864691 --- /dev/null +++ b/tests/test_artist_albums.py @@ -0,0 +1,39 @@ +import pytest + +from yandex_music import ArtistAlbums + + +@pytest.fixture(scope='class') +def artist_albums(album, pager): + return ArtistAlbums([album], pager) + + +class TestArtistAlbums: + def test_expected_values(self, artist_albums, album, pager): + assert artist_albums.albums == [album] + assert artist_albums.pager == pager + + def test_de_json_required(self, client, album, pager): + json_dict = {'albums': [album.to_dict()], 'pager': pager.to_dict()} + artist_albums = ArtistAlbums.de_json(json_dict, client) + + assert artist_albums.albums == [album] + assert artist_albums.pager == pager + + def test_de_json_all(self, client, album, pager): + json_dict = {'albums': [album.to_dict()], 'pager': pager.to_dict()} + artist_albums = ArtistAlbums.de_json(json_dict, client) + + assert artist_albums.albums == [album] + assert artist_albums.pager == pager + + def test_equality(self, album, pager): + a = ArtistAlbums([album], pager) + b = ArtistAlbums([], pager) + c = ArtistAlbums([album], pager) + + assert a != b + assert hash(a) != hash(b) + assert a is not b + + assert a == c diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index bd45357a..a81b5b5a 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -113,4 +113,4 @@ 'PersonalPlaylistsData', 'Promotion', 'Landing', 'Chart', 'ChartItem', 'PlayContext', 'Title', 'Genre', 'Icon', 'Images', 'Id', 'Station', 'Dashboard', 'RotorSettings', 'AdParams', 'Restrictions', 'Value', 'Enum', 'DiscreteScale', 'StationResult', 'Sequence', 'StationTracksResult', 'BriefInfo', 'Description', 'PlaylistId', - 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager'] + 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums'] From 584741cb632b7d62f68bc85ad1b3ce27617c12d4 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Tue, 19 Nov 2019 22:33:52 +0400 Subject: [PATCH 11/30] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index aaabde78..88723a40 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -923,12 +923,12 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No @log def artists_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): - """Получение треков артиста. + """Получение альбомов артиста. Args: artist_id (:obj:`str` | :obj:`int`): Уникальный идентификатор артиста. page (:obj:`int`, optional): Номер страницы. - page_size (:obj:`int`, optional): Количество треков на странице. + page_size (:obj:`int`, optional): Количество альбомов на странице. sort_by (:obj:`str`, optional): Параметр для сортирвки. timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания ответа от сервера вместо указанного при создании пула. From bc2c054ce01c21b9673e8ba7b9b37625fe41cafd Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Wed, 20 Nov 2019 17:32:54 +0400 Subject: [PATCH 12/30] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20.rst?= =?UTF-8?q?=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D0=B3?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D0=B8=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=20artists=5Falbums=20=D0=BD=D0=B0=20artists=5Fdire?= =?UTF-8?q?ct=5Falbums?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/yandex_music.artist.artist_albums.rst | 7 +++++++ docs/source/yandex_music.artist.rst | 1 + yandex_music/artist/artist.py | 4 ++-- yandex_music/client.py | 8 ++++---- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 docs/source/yandex_music.artist.artist_albums.rst diff --git a/docs/source/yandex_music.artist.artist_albums.rst b/docs/source/yandex_music.artist.artist_albums.rst new file mode 100644 index 00000000..c734d47b --- /dev/null +++ b/docs/source/yandex_music.artist.artist_albums.rst @@ -0,0 +1,7 @@ +yandex_music.ArtistAlbums +========================= + +.. autoclass:: yandex_music.ArtistAlbums + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/yandex_music.artist.rst b/docs/source/yandex_music.artist.rst index 483b4be8..63446e86 100644 --- a/docs/source/yandex_music.artist.rst +++ b/docs/source/yandex_music.artist.rst @@ -11,4 +11,5 @@ yandex_music.artist.description yandex_music.artist.brief_info yandex_music.artist.artist_tracks + yandex_music.artist.artist_albums yandex_music.artist.pager diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index 5e3b838c..852cad20 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -98,9 +98,9 @@ def get_tracks(self, page=0, page_size=20, *args, **kwargs): def get_albums(self, page=0, page_size=20, sort_by='year', *args, **kwargs): """Сокращение для:: - client.artists_albums(artist.id, page, page_size, sort_by, *args, **kwargs) + client.artists_direct_albums(artist.id, page, page_size, sort_by, *args, **kwargs) """ - return self.client.artists_albums(self.id, page, page_size, sort_by, *args, **kwargs) + return self.client.artists_direct_albums(self.id, page, page_size, sort_by, *args, **kwargs) @classmethod def de_json(cls, data, client): diff --git a/yandex_music/client.py b/yandex_music/client.py index 88723a40..dbafcdb3 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -922,7 +922,7 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No return ArtistTracks.de_json(result, self) @log - def artists_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): + def artists_direct_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): """Получение альбомов артиста. Args: @@ -936,7 +936,7 @@ def artists_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='y Returns: :obj:`yandex_music.ArtistAlbums`: Объекта класса :class:`yandex_music.ArtistsTracks` - представляющий страницу списка альбомов артиста + представляющий страницу списка альбомов артиста """ url = f'{self.base_url}/artists/{artist_id}/direct-albums' @@ -1171,8 +1171,8 @@ def users_dislikes_tracks_remove(self, track_ids: str or list, user_id: str or i artistsBriefInfo = artists_brief_info #: Псевдоним для :attr:`artists_tracks` artistsTracks = artists_tracks - #: Псевдоним для :attr:`artists_albums` - artistsAlbums = artists_albums + #: Псевдоним для :attr:`artists_direct_albums` + artistsDirectAlbums = artists_direct_albums #: Псевдоним для :attr:`users_likes_tracks_add` usersLikesTracksAdd = users_likes_tracks_add #: Псевдоним для :attr:`users_likes_tracks_remove` From 674f15416ae6caa0e2162a64b67c904573f32f0e Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 20 Nov 2019 19:13:49 +0300 Subject: [PATCH 13/30] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/artist/artist_albums.py | 4 ++-- yandex_music/client.py | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/yandex_music/artist/artist_albums.py b/yandex_music/artist/artist_albums.py index 91e2ecb0..be6c9c34 100644 --- a/yandex_music/artist/artist_albums.py +++ b/yandex_music/artist/artist_albums.py @@ -13,8 +13,8 @@ class ArtistAlbums(YandexMusicObject): Args: albums (:obj:`list` из :obj:`yandex_music.Album`): Список альбомов артиста. pager (:obj:`yandex_music.Pager`): Объект класса :class:`yandex_music.Pager` представляющий пагинатор. - client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex - Music. + client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент + Yandex Music. **kwargs: Произвольные ключевые аргументы полученные от API. """ diff --git a/yandex_music/client.py b/yandex_music/client.py index dbafcdb3..282cd050 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -26,7 +26,6 @@ 'playlist': PlaylistsLikes.de_list, } - logging.getLogger(__name__).addHandler(logging.NullHandler()) @@ -907,7 +906,10 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No Returns: :obj:`yandex_music.ArtistsTracks`: Объекта класса :class:`yandex_music.ArtistsTracks` - представляющий страницу списка треков артиста + представляющий страницу списка треков артиста, иначе :obj:`None`. + + Raises: + :class:`yandex_music.YandexMusicError` """ url = f'{self.base_url}/artists/{artist_id}/tracks' @@ -922,21 +924,27 @@ def artists_tracks(self, artist_id: str or int, page=0, page_size=20, timeout=No return ArtistTracks.de_json(result, self) @log - def artists_direct_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', timeout=None, *args, **kwargs): + def artists_direct_albums(self, artist_id: str or int, page=0, page_size=20, sort_by='year', + timeout=None, *args, **kwargs): """Получение альбомов артиста. + Известные значения для sort_by: year, rating. + Args: artist_id (:obj:`str` | :obj:`int`): Уникальный идентификатор артиста. page (:obj:`int`, optional): Номер страницы. page_size (:obj:`int`, optional): Количество альбомов на странице. - sort_by (:obj:`str`, optional): Параметр для сортирвки. + sort_by (:obj:`str`, optional): Параметр для сортировки. timeout (:obj:`int` | :obj:`float`, optional): Если это значение указано, используется как время ожидания ответа от сервера вместо указанного при создании пула. **kwargs (:obj:`dict`, optional): Произвольные аргументы (будут переданы в запрос). Returns: :obj:`yandex_music.ArtistAlbums`: Объекта класса :class:`yandex_music.ArtistsTracks` - представляющий страницу списка альбомов артиста + представляющий страницу списка альбомов артиста, иначе :obj:`None`. + + Raises: + :class:`yandex_music.YandexMusicError` """ url = f'{self.base_url}/artists/{artist_id}/direct-albums' From ae0a7cae1244914b01c5104df0824214c8f9578a Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 20 Nov 2019 19:25:48 +0300 Subject: [PATCH 14/30] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20.rst=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D1=83=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=91=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/yandex_music.search.album_search_result.rst | 7 ------- docs/source/yandex_music.search.artist_search_result.rst | 7 ------- docs/source/yandex_music.search.playlist_search_result.rst | 7 ------- docs/source/yandex_music.search.rst | 5 ----- docs/source/yandex_music.search.track_search_result.rst | 7 ------- docs/source/yandex_music.search.video_search_result.rst | 7 ------- 6 files changed, 40 deletions(-) delete mode 100644 docs/source/yandex_music.search.album_search_result.rst delete mode 100644 docs/source/yandex_music.search.artist_search_result.rst delete mode 100644 docs/source/yandex_music.search.playlist_search_result.rst delete mode 100644 docs/source/yandex_music.search.track_search_result.rst delete mode 100644 docs/source/yandex_music.search.video_search_result.rst diff --git a/docs/source/yandex_music.search.album_search_result.rst b/docs/source/yandex_music.search.album_search_result.rst deleted file mode 100644 index db3b03f9..00000000 --- a/docs/source/yandex_music.search.album_search_result.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.AlbumSearchResult -============================== - -.. autoclass:: yandex_music.AlbumSearchResult - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/yandex_music.search.artist_search_result.rst b/docs/source/yandex_music.search.artist_search_result.rst deleted file mode 100644 index d5619e2e..00000000 --- a/docs/source/yandex_music.search.artist_search_result.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.ArtistSearchResult -=============================== - -.. autoclass:: yandex_music.ArtistSearchResult - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/yandex_music.search.playlist_search_result.rst b/docs/source/yandex_music.search.playlist_search_result.rst deleted file mode 100644 index fe26c4e6..00000000 --- a/docs/source/yandex_music.search.playlist_search_result.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.PlaylistSearchResult -================================= - -.. autoclass:: yandex_music.PlaylistSearchResult - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/yandex_music.search.rst b/docs/source/yandex_music.search.rst index 0069fde1..ede5bab6 100644 --- a/docs/source/yandex_music.search.rst +++ b/docs/source/yandex_music.search.rst @@ -4,11 +4,6 @@ .. toctree:: yandex_music.search.suggestions - yandex_music.search.track_search_result yandex_music.search.search - yandex_music.search.artist_search_result yandex_music.search.search_result - yandex_music.search.video_search_result yandex_music.search.best - yandex_music.search.playlist_search_result - yandex_music.search.album_search_result diff --git a/docs/source/yandex_music.search.track_search_result.rst b/docs/source/yandex_music.search.track_search_result.rst deleted file mode 100644 index aed130fd..00000000 --- a/docs/source/yandex_music.search.track_search_result.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.TrackSearchResult -============================== - -.. autoclass:: yandex_music.TrackSearchResult - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/yandex_music.search.video_search_result.rst b/docs/source/yandex_music.search.video_search_result.rst deleted file mode 100644 index 4c541c1f..00000000 --- a/docs/source/yandex_music.search.video_search_result.rst +++ /dev/null @@ -1,7 +0,0 @@ -yandex_music.VideoSearchResult -============================== - -.. autoclass:: yandex_music.VideoSearchResult - :members: - :undoc-members: - :show-inheritance: From dd2ac006740bcb6a123251715f543a516f08708d Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 20 Nov 2019 20:44:50 +0300 Subject: [PATCH 15/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20`captcha`=20?= =?UTF-8?q?=D0=BA=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D1=83=D1=8E=D1=89=D0=B5=D0=BC=D1=83=20=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8E.=20=D0=9E=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA?= =?UTF-8?q?=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BA?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8E?= =?UTF-8?q?.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=BC=D0=B5=D1=80=20=D0=B2=20README.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rst | 5 ++--- yandex_music/exceptions.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 91d92e6e..699d74c4 100644 --- a/README.rst +++ b/README.rst @@ -186,10 +186,9 @@ music.yandex.ru/album/**1193829**/track/**10994777** try: client = Client.from_credentials('login', 'pass', captcha_answer, captcha_key) except Captcha as e: - captcha = e.args[1] - captcha.download('captcha.png') + e.captcha.download('captcha.png') - captcha_key = captcha.x_captcha_key + captcha_key = e.captcha.x_captcha_key captcha_answer = input('Число с картинки: ') return client diff --git a/yandex_music/exceptions.py b/yandex_music/exceptions.py index 88243e31..6eb0949f 100644 --- a/yandex_music/exceptions.py +++ b/yandex_music/exceptions.py @@ -22,9 +22,20 @@ class Unauthorized(YandexMusicError): class Captcha(YandexMusicError): """Базовый класс, представляющий исключение связанное с капчей. + + Attributes: + captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Объект класса + :class:`yandex_music.utils.captcha_response.CaptchaResponse` представляющий капчу. + + Args: + msg (:obj:`str`): Сообщение с ошибкой. + captcha (:obj:`yandex_music.utils.captcha_response.CaptchaResponse`): Объект класса + :class:`yandex_music.utils.captcha_response.CaptchaResponse` представляющий капчу. """ - pass + def __init__(self, msg, captcha, *args, **kwargs): + self.captcha = captcha + super().__init__(msg, *args, **kwargs) class CaptchaRequired(Captcha): @@ -40,6 +51,7 @@ class CaptchaWrong(Captcha): pass + class NetworkError(YandexMusicError): """Базовый класс исключений, вызываемых для ошибок, связанных с запросами к серверу. @@ -57,4 +69,4 @@ class TimedOut(NetworkError): """ def __init__(self): - super(TimedOut, self).__init__('Timed out') + super().__init__('Timed out') From 641d1b993a6410576f079d3f4c0d2ef5627cc3e8 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Sat, 23 Nov 2019 00:51:47 +0400 Subject: [PATCH 16/30] =?UTF-8?q?callback-=D1=84=D1=83=D0=BD=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BA=D0=B0=D0=BF=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index b5ec4813..b5d5f209 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -8,7 +8,7 @@ ArtistAlbums from yandex_music.utils.request import Request from yandex_music.utils.difference import Difference -from yandex_music.exceptions import InvalidToken +from yandex_music.exceptions import InvalidToken, Captcha CLIENT_ID = '23cabbbdc6cd418abb4b39c32c41195d' CLIENT_SECRET = '53bc75238f0c4d08a118e51fe9203300' @@ -86,7 +86,8 @@ def __init__(self, token=None, base_url=None, oauth_url=None, request=None): self.account = self.account_status().account @classmethod - def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, *args, **kwargs): + def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, captch_callback=None, + *args, **kwargs): """Инициализция клиента по логину и паролю. Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить и @@ -97,14 +98,28 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k password (:obj:`str`): Пароль клиента (аутентификатор). x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. + captch_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна + принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать строку с кодом. **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. Returns: :obj:`yandex_music.Client`. """ - return cls(cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, - x_captcha_key=x_captcha_key), *args, **kwargs) + token = x_captcha_key = x_captcha_answer = None + if captch_callback: + while not token: + try: + token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, + x_captcha_key=x_captcha_key, captch_callback=captch_callback) + except Captcha as e: + x_captcha_answer = captch_callback(e.captcha) + x_captcha_key = e.captcha.x_captcha_key + else: + token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, + x_captcha_key=x_captcha_key, captch_callback=captch_callback) + + return cls(token, *args, **kwargs) @classmethod def from_token(cls, token, *args, **kwargs): From c19915ad8fa07e7b96391294bd4b58bab1d6928b Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Sat, 23 Nov 2019 01:03:00 +0400 Subject: [PATCH 17/30] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B1=D0=B5=D0=BB=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B5=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index b5d5f209..fa00c79b 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -99,7 +99,7 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. captch_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна - принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать строку с кодом. + принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать строку с кодом. **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. Returns: From 0e5ac0a18181bc69c4e21e7af4c0783f331d0ecb Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Sat, 23 Nov 2019 14:56:48 +0400 Subject: [PATCH 18/30] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B5?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BF=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index fa00c79b..1397babd 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -86,7 +86,7 @@ def __init__(self, token=None, base_url=None, oauth_url=None, request=None): self.account = self.account_status().account @classmethod - def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, captch_callback=None, + def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_key=None, captcha_callback=None, *args, **kwargs): """Инициализция клиента по логину и паролю. @@ -98,7 +98,7 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k password (:obj:`str`): Пароль клиента (аутентификатор). x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. - captch_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна + captcha_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать строку с кодом. **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. @@ -106,18 +106,18 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k :obj:`yandex_music.Client`. """ - token = x_captcha_key = x_captcha_answer = None - if captch_callback: + token = None + if captcha_callback: while not token: try: token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, - x_captcha_key=x_captcha_key, captch_callback=captch_callback) + x_captcha_key=x_captcha_key) except Captcha as e: - x_captcha_answer = captch_callback(e.captcha) + x_captcha_answer = captcha_callback(e.captcha) x_captcha_key = e.captcha.x_captcha_key else: token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, - x_captcha_key=x_captcha_key, captch_callback=captch_callback) + x_captcha_key=x_captcha_key) return cls(token, *args, **kwargs) From a28fd555fceb08f86560bb2fe58cc1509ed952c4 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Sat, 23 Nov 2019 15:22:38 +0400 Subject: [PATCH 19/30] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20callback=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BA=D0=B0=D0=BF=D1=87?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index 1397babd..0e675591 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -107,17 +107,16 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k """ token = None - if captcha_callback: - while not token: - try: - token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, - x_captcha_key=x_captcha_key) - except Captcha as e: + while not token: + try: + token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, + x_captcha_key=x_captcha_key) + except Captcha as e: + if captcha_callback: x_captcha_answer = captcha_callback(e.captcha) x_captcha_key = e.captcha.x_captcha_key - else: - token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, - x_captcha_key=x_captcha_key) + else: + raise e return cls(token, *args, **kwargs) From 05cdb3f152e6c1b5ef852021f27a04a00d63f194 Mon Sep 17 00:00:00 2001 From: Gleb Liutsko Date: Sat, 23 Nov 2019 15:42:08 +0400 Subject: [PATCH 20/30] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=BC=D0=B5=D1=80=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=BF=D1=87=D0=B8=20=D0=B2=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.rst b/README.rst index 84a770c2..adbf96d1 100644 --- a/README.rst +++ b/README.rst @@ -212,6 +212,16 @@ music.yandex.ru/album/**1193829**/track/**10994777** return client +Пример инициализации клиента с обработкой капчи при помощи callback-функции: + +.. code:: python + + def proc_captcha(captcha): + captcha.download('captcha.png') + return input('Число с картинки: ') + + client = Client.from_credentials('login', 'pass', captcha_callback=proc_captcha) + -------------------- Изучение по примерам -------------------- From 9dbe8d6d86840b7aac53d1cf61831af761329955 Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sat, 23 Nov 2019 16:35:55 +0300 Subject: [PATCH 21/30] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B3=20=D1=81=D0=B2=D1=8F?= =?UTF-8?q?=D0=B7=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=81=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=BE=D0=B9=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=20#149?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/utils/request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index 4a6feaf6..dae18b49 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -134,6 +134,6 @@ def retrieve(self, url, timeout=5, *args, **kwargs): return self._request_wrapper('GET', url, proxies=self.proxies, timeout=timeout, *args, **kwargs) def download(self, url, filename, timeout=5, *args, **kwargs): - result = self.retrieve(url, proxies=self.proxies, timeout=timeout, *args, *kwargs) + result = self.retrieve(url, timeout=timeout, *args, *kwargs) with open(filename, 'wb') as f: f.write(result.content) From d0453446ca29b014cc0957554b050165eb5d30bc Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 01:15:23 +0300 Subject: [PATCH 22/30] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3=20=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/client.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/yandex_music/client.py b/yandex_music/client.py index 0e675591..82b0f51e 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -90,8 +90,9 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k *args, **kwargs): """Инициализция клиента по логину и паролю. - Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить и - использовать при следующих инициализациях клиента. Не храните логины и пароли! + Note: + Данный метод получает токен каждый раз при вызове. Рекомендуется сгенерировать его самостоятельно, сохранить + и использовать при следующих инициализациях клиента. Не храните логины и пароли! Args: username (:obj:`str`): Логин клиента (идентификатор). @@ -99,25 +100,29 @@ def from_credentials(cls, username, password, x_captcha_answer=None, x_captcha_k x_captcha_answer (:obj:`str`, optional): Ответ на капчу (цифры с картинки). x_captcha_key (:obj:`str`, optional): Уникальный ключ капчи. captcha_callback (:obj:`function`, optional): Функция обратного вызова для обработки капчи, должна - принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать строку с кодом. + принимать объект класса :class:`yandex_music.exceptions.Captcha` и возвращать проверочный код. **kwargs (:obj:`dict`, optional): Аргументы для конструктора клиента. Returns: :obj:`yandex_music.Client`. + + Raises: + :class:`yandex_music.YandexMusicError` """ token = None while not token: try: - token = cls().generate_token_by_username_and_password(username, password, x_captcha_answer=x_captcha_answer, + token = cls().generate_token_by_username_and_password(username, password, + x_captcha_answer=x_captcha_answer, x_captcha_key=x_captcha_key) except Captcha as e: - if captcha_callback: - x_captcha_answer = captcha_callback(e.captcha) - x_captcha_key = e.captcha.x_captcha_key - else: + if not captcha_callback: raise e + x_captcha_answer = captcha_callback(e.captcha) + x_captcha_key = e.captcha.x_captcha_key + return cls(token, *args, **kwargs) @classmethod From f379280fc269b3d86c0519cd06cb97433a45c9ec Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 01:51:45 +0300 Subject: [PATCH 23/30] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D1=85=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20`Playlist`:=20`uid`,=20`kind`,?= =?UTF-8?q?=20`title`,=20`track=5Fcount`.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81=20`PlaylistAbsence`=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D1=8F=D1=8E=D1=89?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=20=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D1=8B=D0=B2=D0=B0=D1=8E=D1=89=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=83=20=D0=BE=D1=82=D1=81?= =?UTF-8?q?=D1=83=D1=82=D1=81=D1=82=D0=B2=D0=B8=D1=8F=20=D0=BF=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9=D0=BB=D0=B8=D1=81=D1=82=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/__init__.py | 4 +++- yandex_music/playlist/playlist.py | 24 ++++++++++++----------- yandex_music/playlist/playlist_absence.py | 23 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 yandex_music/playlist/playlist_absence.py diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index a81b5b5a..8a16d51d 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -32,6 +32,7 @@ from .playlist.user import User from .playlist.play_counter import PlayCounter from .playlist.playlist_id import PlaylistId +from .playlist.playlist_absence import PlaylistAbsence from .playlist.playlist import Playlist from .tracks_list import TracksList @@ -113,4 +114,5 @@ 'PersonalPlaylistsData', 'Promotion', 'Landing', 'Chart', 'ChartItem', 'PlayContext', 'Title', 'Genre', 'Icon', 'Images', 'Id', 'Station', 'Dashboard', 'RotorSettings', 'AdParams', 'Restrictions', 'Value', 'Enum', 'DiscreteScale', 'StationResult', 'Sequence', 'StationTracksResult', 'BriefInfo', 'Description', 'PlaylistId', - 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums'] + 'Vinyl', 'Supplement', 'Lyrics', 'VideoSupplement', 'ArtistTracks', 'Pager', 'ArtistAlbums', + 'PlaylistAbsence'] diff --git a/yandex_music/playlist/playlist.py b/yandex_music/playlist/playlist.py index ddcd82ed..f858928d 100644 --- a/yandex_music/playlist/playlist.py +++ b/yandex_music/playlist/playlist.py @@ -4,13 +4,14 @@ class Playlist(YandexMusicObject): def __init__(self, owner, - uid, - kind, - title, - track_count, cover, made_for, play_counter, + playlist_absence, + uid=None, + kind=None, + title=None, + track_count=None, tags=None, revision=None, snapshot=None, @@ -36,14 +37,15 @@ def __init__(self, client=None, **kwargs): self.owner = owner - self.uid = uid - self.kind = kind - self.title = title - self.track_count = track_count self.cover = cover self.made_for = made_for self.play_counter = play_counter + self.playlist_absence = playlist_absence + self.uid = uid + self.kind = kind + self.title = title + self.track_count = track_count self.revision = revision self.snapshot = snapshot self.visibility = visibility @@ -68,8 +70,7 @@ def __init__(self, self.tags = tags self.client = client - self._id_attrs = (self.uid, self.kind, self.title, self.track_count, self.cover, - self.made_for, self.play_counter) + self._id_attrs = (self.uid, self.kind, self.title, self.playlist_absence) @property def is_mine(self): @@ -127,12 +128,13 @@ def de_json(cls, data, client): return None data = super(Playlist, cls).de_json(data, client) - from yandex_music import User, MadeFor, Cover, PlayCounter, TrackShort + from yandex_music import User, MadeFor, Cover, PlayCounter, TrackShort, PlaylistAbsence data['owner'] = User.de_json(data.get('owner'), client) data['cover'] = Cover.de_json(data.get('cover'), client) data['made_for'] = MadeFor.de_json(data.get('made_for'), client) data['tracks'] = TrackShort.de_list(data.get('tracks'), client) data['play_counter'] = PlayCounter.de_json(data.get('play_counter'), client) + data['playlist_absence'] = PlaylistAbsence.de_json(data.get('playlist_absense'), client) # очепятка яндуха return cls(client=client, **data) diff --git a/yandex_music/playlist/playlist_absence.py b/yandex_music/playlist/playlist_absence.py new file mode 100644 index 00000000..46a06a1c --- /dev/null +++ b/yandex_music/playlist/playlist_absence.py @@ -0,0 +1,23 @@ +from yandex_music import YandexMusicObject + + +class PlaylistAbsence(YandexMusicObject): + def __init__(self, + kind, + reason, + client=None, + **kwargs): + self.kind = kind + self.reason = reason + + self.client = client + self._id_attrs = (self.kind, self.reason) + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(PlaylistAbsence, cls).de_json(data, client) + + return cls(client=client, **data) From dd6dd844db4ccb5d7a110a7196cfe70a704904f4 Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 01:58:41 +0300 Subject: [PATCH 24/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20`PlaylistAbsence`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/__init__.py | 1 + tests/conftest.py | 9 +++++++-- tests/test_playlist_absence.py | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/test_playlist_absence.py diff --git a/tests/__init__.py b/tests/__init__.py index 6c942b1c..4609f792 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -39,6 +39,7 @@ from .test_play_contexts_data import TestPlayContextsData from .test_play_counter import TestPlayCounter from .test_playlist import TestPlaylist +from .test_playlist_absence import TestPlaylistAbsence from .test_playlist_id import TestPlaylistId from .test_playlist_id import TestPlaylistId from .test_plus import TestPlus diff --git a/tests/conftest.py b/tests/conftest.py index 398741b1..52964cc4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ Account, Client, TrackShort, Value, DiscreteScale, PlaylistId, MixLink, Link, PassportPhone, User, Promotion, \ PersonalPlaylistsData, RotorSettings, TrackShortOld, PlayContextsData, Status, Settings, StationResult, Enum, \ TrackWithAds, VideoSupplement, ArtistEvent, ChartItem, Event, AlbumEvent, Day, PlayContext, Plus, Title, Label, \ - GeneratedPlaylist, Video, Vinyl, SearchResult, BlockEntity, Block + GeneratedPlaylist, Video, Vinyl, SearchResult, BlockEntity, Block, PlaylistAbsence from . import TestCounts, TestTrackId, TestCaseForms, TestRatings, TestIcon, TestAlbum, TestLyrics, \ TestTrack, TestInvocationInfo, TestPlaylist, TestAutoRenewable, TestStation, TestNormalization, TestMajor, \ TestTrackPosition, TestBest, TestChart, TestPermissions, TestPlus, TestProduct, TestCover, TestPlayCounter, \ @@ -15,7 +15,7 @@ TestUser, TestPassportPhone, TestPromotion, TestTitle, TestPersonalPlaylistsData, TestRotorSettings, \ TestTrackShortOld, TestPager, TestStatus, TestSettings, TestStationResult, TestLabel, TestTrackWithAds, \ TestVideoSupplement, TestEvent, TestDay, TestPlayContext, TestGeneratedPlaylist, TestVideo, TestVinyl, \ - TestSearchResult, TestBlockEntity, TestBlock + TestSearchResult, TestBlockEntity, TestBlock, TestPlaylistAbsence @pytest.fixture(scope='session') @@ -255,6 +255,11 @@ def play_counter(): return PlayCounter(TestPlayCounter.value, TestPlayCounter.description, TestPlayCounter.updated) +@pytest.fixture(scope='session') +def playlist_absence(): + return PlaylistAbsence(TestPlaylistAbsence.kind, TestPlaylistAbsence.reason) + + @pytest.fixture(scope='session') def results(playlist): return [playlist] diff --git a/tests/test_playlist_absence.py b/tests/test_playlist_absence.py new file mode 100644 index 00000000..4ce6ca3e --- /dev/null +++ b/tests/test_playlist_absence.py @@ -0,0 +1,35 @@ +from yandex_music import PlaylistAbsence + + +class TestPlaylistAbsence: + kind = 1003 + reason = 'playlist-is-deleted' + + def test_expected_values(self, playlist_absence): + assert playlist_absence.kind == self.kind + assert playlist_absence.reason == self.reason + + def test_de_json_required(self, client): + json_dict = {'kind': self.kind, 'reason': self.reason} + playlist_absence = PlaylistAbsence.de_json(json_dict, client) + + assert playlist_absence.kind == self.kind + assert playlist_absence.reason == self.reason + + def test_de_json_all(self, client): + json_dict = {'kind': self.kind, 'reason': self.reason} + playlist_absence = PlaylistAbsence.de_json(json_dict, client) + + assert playlist_absence.kind == self.kind + assert playlist_absence.reason == self.reason + + def test_equality(self): + a = PlaylistAbsence(self.kind, self.reason) + b = PlaylistAbsence(10, self.reason) + c = PlaylistAbsence(self.kind, self.reason) + + assert a != b + assert hash(a) != hash(b) + assert a is not b + + assert a == c From aa2595fd124743d8cc2897a23fefe6172d66f240 Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 02:23:24 +0300 Subject: [PATCH 25/30] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20`Playlist`.=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=B5=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20`playl?= =?UTF-8?q?ist=5Fabsence`.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B3=D0=BB=D1=83=D1=88=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9=20=D1=81?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D0=BE=D0=B3=D0=BE=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=BF=D0=B5?= =?UTF-8?q?=D1=87=D0=B0=D1=82=D0=BA=D0=B8=20=D1=81=D0=BE=20=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D0=BD=D1=8B=20=D0=AF=D0=BD=D0=B4=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 6 +++--- tests/test_playlist.py | 33 +++++++++++++++++-------------- yandex_music/playlist/playlist.py | 6 +++++- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 52964cc4..c3c7b4e4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -102,11 +102,11 @@ def album_without_tracks(album_factory, artist_without_tracks): @pytest.fixture(scope='session') -def playlist_factory(user, cover, made_for, track_short, play_counter): +def playlist_factory(user, cover, made_for, track_short, play_counter, playlist_absence): class PlaylistFactory: def get(self): - return Playlist(user, TestPlaylist.uid, TestPlaylist.kind, TestPlaylist.title, TestPlaylist.track_count, - cover, made_for, play_counter, TestPlaylist.tags, TestPlaylist.revision, + return Playlist(user, cover, made_for, play_counter, playlist_absence, TestPlaylist.uid, TestPlaylist.kind, + TestPlaylist.title, TestPlaylist.track_count, TestPlaylist.tags, TestPlaylist.revision, TestPlaylist.snapshot, TestPlaylist.visibility, TestPlaylist.collective, TestPlaylist.created, TestPlaylist.modified, TestPlaylist.available, TestPlaylist.is_banner, TestPlaylist.is_premiere, TestPlaylist.duration_ms, TestPlaylist.og_image, [track_short], diff --git a/tests/test_playlist.py b/tests/test_playlist.py index e19cbc04..879da403 100644 --- a/tests/test_playlist.py +++ b/tests/test_playlist.py @@ -28,7 +28,7 @@ class TestPlaylist: is_for_from = None regions = None - def test_expected_values(self, playlist, user, cover, made_for, track_short, play_counter): + def test_expected_values(self, playlist, user, cover, made_for, track_short, play_counter, playlist_absence): assert playlist.owner == user assert playlist.uid == self.uid assert playlist.kind == self.kind @@ -37,6 +37,7 @@ def test_expected_values(self, playlist, user, cover, made_for, track_short, pla assert playlist.cover == cover assert playlist.made_for == made_for assert playlist.play_counter == play_counter + assert playlist.playlist_absence == playlist_absence assert playlist.tags == self.tags assert playlist.revision == self.revision assert playlist.snapshot == self.snapshot @@ -60,10 +61,10 @@ def test_expected_values(self, playlist, user, cover, made_for, track_short, pla assert playlist.is_for_from == self.is_for_from assert playlist.regions == self.regions - def test_de_json_required(self, client, user, cover, made_for, play_counter): + def test_de_json_required(self, client, user, cover, made_for, play_counter, playlist_absence): json_dict = {'owner': user.to_dict(), 'uid': self.uid, 'kind': self.kind, 'title': self.title, 'track_count': self.track_count, 'cover': cover.to_dict(), 'made_for': made_for.to_dict(), - 'play_counter': play_counter.to_dict()} + 'play_counter': play_counter.to_dict(), 'playlist_absence': playlist_absence.to_dict()} playlist = Playlist.de_json(json_dict, client) assert playlist.owner == user @@ -75,15 +76,16 @@ def test_de_json_required(self, client, user, cover, made_for, play_counter): assert playlist.made_for == made_for assert playlist.play_counter == play_counter - def test_de_json_all(self, client, user, cover, made_for, track_short, play_counter): + def test_de_json_all(self, client, user, cover, made_for, track_short, play_counter, playlist_absence): json_dict = {'owner': user.to_dict(), 'uid': self.uid, 'kind': self.kind, 'title': self.title, 'track_count': self.track_count, 'cover': cover.to_dict(), 'made_for': made_for.to_dict(), - 'play_counter': play_counter.to_dict(), 'tags': self.tags, 'revision': self.revision, - 'snapshot': self.snapshot, 'visibility': self.visibility, 'collective': self.collective, - 'created': self.created, 'modified': self.modified, 'available': self.available, - 'is_banner': self.is_banner, 'is_premiere': self.is_premiere, 'duration_ms': self.duration_ms, - 'og_image': self.og_image, 'tracks': [track_short.to_dict()], 'prerolls': self.prerolls, - 'likes_count': self.likes_count, 'generated_playlist_type': self.generated_playlist_type, + 'play_counter': play_counter.to_dict(), 'playlist_absence': playlist_absence.to_dict(), + 'tags': self.tags, 'revision': self.revision, 'snapshot': self.snapshot, + 'visibility': self.visibility, 'collective': self.collective, 'created': self.created, + 'modified': self.modified, 'available': self.available, 'is_banner': self.is_banner, + 'is_premiere': self.is_premiere, 'duration_ms': self.duration_ms, 'og_image': self.og_image, + 'tracks': [track_short.to_dict()], 'prerolls': self.prerolls, 'likes_count': self.likes_count, + 'generated_playlist_type': self.generated_playlist_type, 'animated_cover_uri': self.animated_cover_uri, 'ever_played': self.ever_played, 'description': self.description, 'description_formatted': self.description_formatted, 'is_for_from': self.is_for_from, 'regions': self.regions} @@ -97,6 +99,7 @@ def test_de_json_all(self, client, user, cover, made_for, track_short, play_coun assert playlist.cover == cover assert playlist.made_for == made_for assert playlist.play_counter == play_counter + assert playlist.playlist_absence == playlist_absence assert playlist.tags == self.tags assert playlist.revision == self.revision assert playlist.snapshot == self.snapshot @@ -120,11 +123,11 @@ def test_de_json_all(self, client, user, cover, made_for, track_short, play_coun assert playlist.is_for_from == self.is_for_from assert playlist.regions == self.regions - def test_equality(self, user, cover, made_for, play_counter): - a = Playlist(user, self.uid, self.kind, self.title, self.track_count, cover, made_for, play_counter) - b = Playlist(user, 123, self.kind, self.title, 10, cover, made_for, play_counter) - c = Playlist(user, self.uid, 321, self.title, self.track_count, None, made_for, play_counter) - d = Playlist(user, self.uid, self.kind, self.title, self.track_count, cover, made_for, play_counter) + def test_equality(self, user, cover, made_for, play_counter, playlist_absence): + a = Playlist(user, cover, made_for, play_counter, playlist_absence) + b = Playlist(user, cover, made_for, play_counter, None) + c = Playlist(user, None, made_for, play_counter, playlist_absence) + d = Playlist(user, cover, made_for, play_counter, playlist_absence) assert a != b != c assert hash(a) != hash(b) != hash(c) diff --git a/yandex_music/playlist/playlist.py b/yandex_music/playlist/playlist.py index f858928d..ad2bf471 100644 --- a/yandex_music/playlist/playlist.py +++ b/yandex_music/playlist/playlist.py @@ -134,7 +134,11 @@ def de_json(cls, data, client): data['made_for'] = MadeFor.de_json(data.get('made_for'), client) data['tracks'] = TrackShort.de_list(data.get('tracks'), client) data['play_counter'] = PlayCounter.de_json(data.get('play_counter'), client) - data['playlist_absence'] = PlaylistAbsence.de_json(data.get('playlist_absense'), client) # очепятка яндуха + + data['playlist_absence'] = PlaylistAbsence.de_json(data.get('playlist_absence'), client) # на случай фикса + if data.get('playlist_absense'): # очепятка яндуха + data['playlist_absence'] = PlaylistAbsence.de_json(data.get('playlist_absense'), client) + data.pop('playlist_absense') return cls(client=client, **data) From 575544b86ed4c16b0879b42fe3234b56019b65da Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 02:35:43 +0300 Subject: [PATCH 26/30] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20`Playlist?= =?UTF-8?q?Absence`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...yandex_music.playlist.playlist_absence.rst | 7 +++++ docs/source/yandex_music.playlist.rst | 1 + yandex_music/playlist/playlist_absence.py | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 docs/source/yandex_music.playlist.playlist_absence.rst diff --git a/docs/source/yandex_music.playlist.playlist_absence.rst b/docs/source/yandex_music.playlist.playlist_absence.rst new file mode 100644 index 00000000..b5408ba0 --- /dev/null +++ b/docs/source/yandex_music.playlist.playlist_absence.rst @@ -0,0 +1,7 @@ +yandex_music.PlaylistAbsence +============================ + +.. autoclass:: yandex_music.PlaylistAbsence + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/yandex_music.playlist.rst b/docs/source/yandex_music.playlist.rst index 45936190..29a84555 100644 --- a/docs/source/yandex_music.playlist.rst +++ b/docs/source/yandex_music.playlist.rst @@ -6,6 +6,7 @@ yandex_music.playlist.user yandex_music.playlist.made_for yandex_music.playlist.play_counter + yandex_music.playlist.playlist_absence yandex_music.playlist.playlist yandex_music.playlist.case_forms yandex_music.playlist.playlist_id diff --git a/yandex_music/playlist/playlist_absence.py b/yandex_music/playlist/playlist_absence.py index 46a06a1c..ef6d8554 100644 --- a/yandex_music/playlist/playlist_absence.py +++ b/yandex_music/playlist/playlist_absence.py @@ -2,6 +2,22 @@ class PlaylistAbsence(YandexMusicObject): + """Класс представляющий причину отсутствия плейлиста. + + Attributes: + kind (:obj:`int`): Уникальный идентификатор плейлиста. + reason (:obj:`str`): Причина отсутствия. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Args: + kind (:obj:`int`): Уникальный идентификатор плейлиста. + reason (:obj:`str`): Причина отсутствия. + client (:obj:`yandex_music.Client`, optional): Объект класса :class:`yandex_music.Client` представляющий клиент + Yandex Music. + **kwargs: Произвольные ключевые аргументы полученные от API. + """ + def __init__(self, kind, reason, @@ -15,6 +31,16 @@ def __init__(self, @classmethod def de_json(cls, data, client): + """Десериализация объекта. + + Args: + data (:obj:`dict`): Поля и значения десериализуемого объекта. + client (:obj:`yandex_music.Client`): Объект класса :class:`yandex_music.Client` представляющий клиент Yandex + Music. + + Returns: + :obj:`yandex_music.PlaylistAbsence`: Объект класса :class:`yandex_music.PlaylistAbsence`. + """ if not data: return None From 4a4ab144fbb3305a0e78e694df48e61e0fdcbf4b Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 24 Nov 2019 03:01:24 +0300 Subject: [PATCH 27/30] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B3=20=D1=81=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=BE=D0=B9=20=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D1=82=D1=8B=D1=85=20=D0=BB=D0=B8=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=D1=85=D0=BE=D0=B4=D1=8F=D1=89=D0=B8=D1=85?= =?UTF-8?q?=20=D0=BE=D1=82=20API.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/utils/request.py | 2 +- yandex_music/utils/response.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/yandex_music/utils/request.py b/yandex_music/utils/request.py index dae18b49..6c2dd4f8 100644 --- a/yandex_music/utils/request.py +++ b/yandex_music/utils/request.py @@ -79,7 +79,7 @@ def _parse(self, json_data) -> Response: except (AttributeError, ValueError): raise YandexMusicError('Invalid server response') - if not data.get('result'): + if data.get('result') is None: data = {'result': data, 'error': data.get('error'), 'error_description': data.get('error_description')} return Response.de_json(data, self.client) diff --git a/yandex_music/utils/response.py b/yandex_music/utils/response.py index 6978393d..650de28b 100644 --- a/yandex_music/utils/response.py +++ b/yandex_music/utils/response.py @@ -24,7 +24,7 @@ def error(self): @property def result(self): - return self._result or self.data + return self.data if self._result is None else self._result @classmethod def de_json(cls, data, client): From 4dea34d6e0826d98548a2d1d1900f475d27c6038 Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 1 Dec 2019 14:25:09 +0300 Subject: [PATCH 28/30] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=8F=20various,=20com?= =?UTF-8?q?poser=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20Artist=20=D0=B8?= =?UTF-8?q?=20available=5Ffor=5Fpremium=5Fusers,=20lyrics=5Favailable=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20Track=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BD=D0=B5=20=D1=8F=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D1=8E=D1=82=D1=81=D1=8F=20=D0=BE=D0=B1=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D1=81=D0=B2=D1=8F=D0=B7=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=BA=D0=BE=D0=B9=20=D1=81=D0=B2=D0=BE=D0=B8=D1=85=20?= =?UTF-8?q?=D1=82=D1=80=D0=B5=D0=BA=D0=BE=D0=B2,=20=D1=83=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D1=8B=D1=85=20=D0=B4=D0=B0=D0=BD=D0=BD=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=82=D1=81=D1=83=D1=82=D1=81=D1=82=D0=B2=D1=83?= =?UTF-8?q?=D0=B5=D1=82=20#154?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yandex_music/artist/artist.py | 8 ++++---- yandex_music/track/track.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index 852cad20..da5cf40d 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -5,9 +5,9 @@ class Artist(YandexMusicObject): def __init__(self, id, name, - various, - composer, cover, + various=None, + composer=None, genres=None, op_image=None, no_pictures_from_search=None, @@ -32,10 +32,10 @@ def __init__(self, **kwargs): self.id = id self.name = name - self.various = various - self.composer = composer self.cover = cover + self.various = various + self.composer = composer self.genres = genres self.op_image = op_image self.no_pictures_from_search = no_pictures_from_search diff --git a/yandex_music/track/track.py b/yandex_music/track/track.py index da936d30..937be4e5 100644 --- a/yandex_music/track/track.py +++ b/yandex_music/track/track.py @@ -6,10 +6,10 @@ def __init__(self, id, title, available, - available_for_premium_users, artists, albums, - lyrics_available, + available_for_premium_users=None, + lyrics_available=None, real_id=None, og_image=None, type=None, @@ -31,11 +31,11 @@ def __init__(self, self.id = id self.title = title self.available = available - self.available_for_premium_users = available_for_premium_users self.artists = artists self.albums = albums - self.lyrics_available = lyrics_available + self.available_for_premium_users = available_for_premium_users + self.lyrics_available = lyrics_available self.real_id = real_id self.og_image = og_image self.type = type From f1f49b4c946a7a928fb3035f91bd62d3022231dd Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 1 Dec 2019 14:35:02 +0300 Subject: [PATCH 29/30] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 10 +++++----- tests/test_artist.py | 11 ++++------- tests/test_track.py | 14 ++++---------- yandex_music/artist/artist.py | 4 ++-- yandex_music/track/track.py | 3 +-- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c3c7b4e4..0bd1b3b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,7 +22,7 @@ def artist_factory(cover, counts, ratings, link, description): class ArtistFactory: def get(self, popular_tracks): - return Artist(TestArtist.id, TestArtist.name, TestArtist.various, TestArtist.composer, cover, + return Artist(TestArtist.id, TestArtist.name, cover, TestArtist.various, TestArtist.composer, TestArtist.genres, TestArtist.op_image, TestArtist.no_pictures_from_search, counts, TestArtist.available, ratings, [link], TestArtist.tickets_available, TestArtist.likes_count, popular_tracks, TestArtist.regions, TestArtist.decomposed, TestArtist.full_names, description, @@ -46,10 +46,10 @@ def artist_without_tracks(artist_factory): def track_factory(major, normalization): class TrackFactory: def get(self, artists, albums): - return Track(TestTrack.id, TestTrack.title, TestTrack.available, TestTrack.available_for_premium_users, - artists, albums, TestTrack.lyrics_available, TestTrack.real_id, TestTrack.og_image, - TestTrack.type, TestTrack.cover_uri, major, TestTrack.duration_ms, TestTrack.storage_dir, - TestTrack.file_size, normalization, TestTrack.error, TestTrack.regions, + return Track(TestTrack.id, TestTrack.title, TestTrack.available, artists, albums, + TestTrack.available_for_premium_users, TestTrack.lyrics_available, TestTrack.real_id, + TestTrack.og_image, TestTrack.type, TestTrack.cover_uri, major, TestTrack.duration_ms, + TestTrack.storage_dir, TestTrack.file_size, normalization, TestTrack.error, TestTrack.regions, TestTrack.available_as_rbt, TestTrack.content_warning, TestTrack.explicit, TestTrack.preview_duration_ms, TestTrack.available_full_without_permission) diff --git a/tests/test_artist.py b/tests/test_artist.py index b6b58591..23f371df 100644 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -50,14 +50,11 @@ def test_expected_values(self, artist, cover, counts, ratings, link, track_witho assert artist.end_date == self.end_date def test_de_json_required(self, client, cover): - json_dict = {'id': self.id, 'name': self.name, 'various': self.various, 'composer': self.composer, - 'cover': cover.to_dict()} + json_dict = {'id': self.id, 'name': self.name, 'cover': cover.to_dict()} artist = Artist.de_json(json_dict, client) assert artist.id == self.id assert artist.name == self.name - assert artist.various == self.various - assert artist.composer == self.composer assert artist.cover == cover def test_de_json_all(self, client, cover, counts, ratings, link, track_without_artists, description): @@ -100,9 +97,9 @@ def test_de_json_all(self, client, cover, counts, ratings, link, track_without_a assert artist.end_date == self.end_date def test_equality(self, cover): - a = Artist(self.id, self.name, self.various, self.composer, cover) - b = Artist(self.id, '', self.various, self.composer, None) - c = Artist(self.id, self.name, self.various, self.composer, cover) + a = Artist(self.id, self.name, cover) + b = Artist(self.id, '', None) + c = Artist(self.id, self.name, cover) assert a != b assert hash(a) != hash(b) diff --git a/tests/test_track.py b/tests/test_track.py index 84e53838..a37e7016 100644 --- a/tests/test_track.py +++ b/tests/test_track.py @@ -49,18 +49,14 @@ def test_expected_values(self, track, artist, album, major, normalization): def test_de_json_required(self, client, artist, album): json_dict = {'id': self.id, 'title': self.title, 'available': self.available, - 'available_for_premium_users': self.available_for_premium_users, - 'artists': [artist.to_dict()], 'albums': [album.to_dict()], - 'lyrics_available': self.lyrics_available} + 'artists': [artist.to_dict()], 'albums': [album.to_dict()]} track = Track.de_json(json_dict, client) assert track.id == self.id assert track.title == self.title assert track.available == self.available - assert track.available_for_premium_users == self.available_for_premium_users assert track.artists == [artist] assert track.albums == [album] - assert track.lyrics_available == self.lyrics_available def test_de_json_all(self, client, artist, album, major, normalization): json_dict = {'id': self.id, 'title': self.title, 'available': self.available, @@ -101,11 +97,9 @@ def test_de_json_all(self, client, artist, album, major, normalization): assert track.available_full_without_permission == self.available_full_without_permission def test_equality(self, artist, album): - a = Track(self.id, self.title, self.available, self.available_for_premium_users, [artist], [album], - self.lyrics_available) - b = Track(self.id, '', self.available, self.available_for_premium_users, [artist], [album], False) - c = Track(self.id, self.title, self.available, self.available_for_premium_users, [artist], [album], - self.lyrics_available) + a = Track(self.id, self.title, self.available, [artist], [album]) + b = Track(self.id, '', self.available, [artist], [None]) + c = Track(self.id, self.title, self.available, [artist], [album]) assert a != b assert hash(a) != hash(b) diff --git a/yandex_music/artist/artist.py b/yandex_music/artist/artist.py index da5cf40d..f8cb00f3 100644 --- a/yandex_music/artist/artist.py +++ b/yandex_music/artist/artist.py @@ -55,12 +55,12 @@ def __init__(self, self.db_aliases = db_aliases self.aliases = aliases - # Оставлено строкой потому что может прийти конкретная дата или просто год + # Может прийти конкретная дата или просто год self.init_date = init_date self.end_date = end_date self.client = client - self._id_attrs = (self.id, self.name, self.various, self.composer, self.cover) + self._id_attrs = (self.id, self.name, self.cover) def download_op_image(self, filename, size='200x200'): """Загрузка обложки. diff --git a/yandex_music/track/track.py b/yandex_music/track/track.py index 937be4e5..ffaea58a 100644 --- a/yandex_music/track/track.py +++ b/yandex_music/track/track.py @@ -56,8 +56,7 @@ def __init__(self, self.download_info = None self.client = client - self._id_attrs = (self.id, self.title, self.available, self.available_for_premium_users, - self.artists, self.albums, self.lyrics_available) + self._id_attrs = (self.id, self.title, self.available, self.artists, self.albums) def get_download_info(self, get_direct_links=False): self.download_info = self.client.tracks_download_info(self.track_id, get_direct_links) From 07c3ac0ddaaef63347102c04983aed92ff595a7e Mon Sep 17 00:00:00 2001 From: Il`ya Date: Sun, 1 Dec 2019 15:34:44 +0300 Subject: [PATCH 30/30] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8,=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.rst | 50 +++++++++++++++++++++++++++++++++++++++++++++++- SECURITY.md | 4 ++-- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c9c33f43..c2ffbe4b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,54 @@ Список изменений ================ +Версия 0.0.15 +============= + +**01.12.2019** + +**Переломные изменения** + +- У классов ``Artist``, ``Track`` и ``Playlist`` изменился перечень полей для генерации хеша. + +**Крупные изменения** + +- Добавлена возможность выполнять запросы через прокси-сервер для использовании библиотеки на зарубежных серверах (`#139`_). + - Добавлен пример использования в ``README``. +- Добавлена обработка капчи при авторизации с возможностью использования callback-функции для её обработки (`#140`_): + - Новые исключения: + - Captcha: + - CaptchaRequired. + - CaptchaWrong. + - Новые классы: + - CaptchaResponse. + - Новые примеры в ``README``: + - Пример обработки с использованием callback-функции. + - Пример полностью своей обработки капчи. +- Добавлена документация для класса ``Search`` (`#83`_). +- Добавлена возможность получения всех альбомов исполнителя (`#141`_): + - Новые классы: + - ArtistAlbums. + - Новые методы: + - ``artists_direct_albums`` у ``Client``. + - ``get_albums`` у ``Artist``. +- Добавлена обработка несуществующего плейлиста (`#147`_): + - Новые классы: + - ``PlaylistAbsence``. + +**Незначительные изменения и/или исправления** + +- Исправлен баг с загрузкой файлов (`#149`_). +- Исправлен баг некорректной десериализации плейлиста при отсутствии прав на него (`#147`_). +- Исправлен баг неправильной десериализации треков и артистов у собственных загруженных файлов (`#154`_). + +.. _`#139`: https://github.com/MarshalX/yandex-music-api/issues/139 +.. _`#140`: https://github.com/MarshalX/yandex-music-api/issues/140 +.. _`#83`: https://github.com/MarshalX/yandex-music-api/issues/83 +.. _`#141`: https://github.com/MarshalX/yandex-music-api/issues/141 +.. _`#149`: https://github.com/MarshalX/yandex-music-api/issues/149 +.. _`#147`: https://github.com/MarshalX/yandex-music-api/issues/147 +.. _`#154`: https://github.com/MarshalX/yandex-music-api/issues/154 + Версия 0.0.14 ============= @@ -26,7 +74,7 @@ **Незначительные изменения и/или исправления** - Поле ``cover_uri`` класса ``Album`` теперь опциональное. -- Поле ``region`` у класса Account теперь не обязательное. +- Поле ``region`` у класса ``Account`` теперь не обязательное. - Исправлен баг в ``.to_dict()`` методе, связанный с десериализцией объектов списков и словарей. - Исправлен баг в ``.to_dict()`` методе, связанный с не рекурсивной десериализацией. - Исправлена десериализация ``similar_artists`` в ``BriefInfo``. diff --git a/SECURITY.md b/SECURITY.md index a5e893da..834fc8dd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,8 @@ | Версия | Поддержка | | ------- | ------------------ | -| 0.0.13 | :white_check_mark: | -| < 0.0.13 | :x: | +| 0.0.15 | :white_check_mark: | +| < 0.0.15 | :x: | ## Сообщение об уязвимости diff --git a/requirements.txt b/requirements.txt index 663bd1f6..617cee6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests \ No newline at end of file +requests[socks] \ No newline at end of file diff --git a/setup.py b/setup.py index f4e50a57..70ad57bb 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ def requirements(): with open('README.rst', 'r', encoding='utf-8') as f: setup(name='yandex-music', - version='0.0.14', + version='0.0.15', author='Il`ya Semyonov', author_email='Ilya@marshal.by', license='LGPLv3',