Skip to content

Commit 390b21b

Browse files
committed
feat: convert to async
1 parent f6932c2 commit 390b21b

File tree

17 files changed

+617
-216
lines changed

17 files changed

+617
-216
lines changed

example.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import ytmusicapi
2+
import asyncio
3+
4+
artists = ['coldplay', 'michael jackson', 'dr dre', 'imagine dragons', 'taylor swift', 'eminem', 'adele', 'ed sheeran']
5+
6+
async def main():
7+
ytm = ytmusicapi.YTMusic()
8+
async with ytm._session:
9+
results = await asyncio.gather(*[ytm.search(artist, filter="songs", limit=1) for artist in artists])
10+
print(*[f"{result[0]['artists'][0]['name']}: {result[0]['title']}" for result in results], sep="\n")
11+
12+
asyncio.run(main())

pdm.lock

Lines changed: 393 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ classifiers = [
1010
]
1111
dependencies = [
1212
"requests >= 2.22",
13+
"aiohttp>=3.9.3",
1314
]
1415
dynamic = ["version", "readme"]
1516

@@ -28,8 +29,6 @@ build-backend = "setuptools.build_meta"
2829
[tool.setuptools.dynamic]
2930
readme = {file = ["README.rst"]}
3031

31-
[tool.setuptools_scm]
32-
3332
[tool.setuptools]
3433
include-package-data=false
3534

ytmusicapi/auth/oauth/credentials.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dataclasses import dataclass
33
from typing import Dict, Mapping, Optional
44

5-
import requests
5+
from aiohttp import ClientSession
66

77
from ytmusicapi.constants import (
88
OAUTH_CLIENT_ID,
@@ -47,7 +47,7 @@ def __init__(
4747
self,
4848
client_id: Optional[str] = None,
4949
client_secret: Optional[str] = None,
50-
session: Optional[requests.Session] = None,
50+
session: Optional[ClientSession] = None,
5151
proxies: Optional[Dict] = None,
5252
):
5353
"""
@@ -67,22 +67,22 @@ def __init__(
6767
self.client_id = client_id if client_id else OAUTH_CLIENT_ID
6868
self.client_secret = client_secret if client_secret else OAUTH_CLIENT_SECRET
6969

70-
self._session = session if session else requests.Session() # for auth requests
70+
self._session = session if session else ClientSession() # for auth requests
7171
if proxies:
7272
self._session.proxies.update(proxies)
7373

74-
def get_code(self) -> AuthCodeDict:
74+
async def get_code(self) -> AuthCodeDict:
7575
"""Method for obtaining a new user auth code. First step of token creation."""
76-
code_response = self._send_request(OAUTH_CODE_URL, data={"scope": OAUTH_SCOPE})
77-
return code_response.json()
76+
code_response = await self._send_request(OAUTH_CODE_URL, data={"scope": OAUTH_SCOPE})
77+
return await code_response.json()
7878

79-
def _send_request(self, url, data):
79+
async def _send_request(self, url, data):
8080
"""Method for sending post requests with required client_id and User-Agent modifications"""
8181

8282
data.update({"client_id": self.client_id})
83-
response = self._session.post(url, data, headers={"User-Agent": OAUTH_USER_AGENT})
84-
if response.status_code == 401:
85-
data = response.json()
83+
response = await self._session.post(url, data=data, headers={"User-Agent": OAUTH_USER_AGENT})
84+
if response.status == 401:
85+
data = await response.json()
8686
issue = data.get("error")
8787
if issue == "unauthorized_client":
8888
raise UnauthorizedOAuthClient("Token refresh error. Most likely client/token mismatch.")
@@ -94,31 +94,31 @@ def _send_request(self, url, data):
9494
)
9595
else:
9696
raise Exception(
97-
f"OAuth request error. status_code: {response.status_code}, url: {url}, content: {data}"
97+
f"OAuth request error. status_code: {response.status}, url: {url}, content: {data}"
9898
)
9999
return response
100100

101-
def token_from_code(self, device_code: str) -> RefreshableTokenDict:
101+
async def token_from_code(self, device_code: str) -> RefreshableTokenDict:
102102
"""Method for verifying user auth code and conversion into a FullTokenDict."""
103-
response = self._send_request(
103+
response = await self._send_request(
104104
OAUTH_TOKEN_URL,
105105
data={
106106
"client_secret": self.client_secret,
107107
"grant_type": "http://oauth.net/grant_type/device/1.0",
108108
"code": device_code,
109109
},
110110
)
111-
return response.json()
111+
return await response.json()
112112

113-
def refresh_token(self, refresh_token: str) -> BaseTokenDict:
113+
async def refresh_token(self, refresh_token: str) -> BaseTokenDict:
114114
"""
115115
Method for requesting a new access token for a given refresh_token.
116116
Token must have been created by the same OAuth client.
117117
118118
:param refresh_token: Corresponding refresh_token for a matching access_token.
119119
Obtained via
120120
"""
121-
response = self._send_request(
121+
response = await self._send_request(
122122
OAUTH_TOKEN_URL,
123123
data={
124124
"client_secret": self.client_secret,
@@ -127,4 +127,4 @@ def refresh_token(self, refresh_token: str) -> BaseTokenDict:
127127
},
128128
)
129129

130-
return response.json()
130+
return await response.json()

ytmusicapi/continuations.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from ytmusicapi.navigation import nav
22

33

4-
def get_continuations(
4+
async def get_continuations(
55
results, continuation_type, limit, request_func, parse_func, ctoken_path="", reloadable=False
66
):
77
items = []
@@ -11,7 +11,7 @@ def get_continuations(
1111
if reloadable
1212
else get_continuation_params(results, ctoken_path)
1313
)
14-
response = request_func(additionalParams)
14+
response = await request_func(additionalParams)
1515
if "continuationContents" in response:
1616
results = response["continuationContents"][continuation_type]
1717
else:
@@ -24,7 +24,7 @@ def get_continuations(
2424
return items
2525

2626

27-
def get_validated_continuations(
27+
async def get_validated_continuations(
2828
results, continuation_type, limit, per_page, request_func, parse_func, ctoken_path=""
2929
):
3030
items = []
@@ -35,7 +35,7 @@ def get_validated_continuations(
3535
)
3636
validate_func = lambda parsed: validate_response(parsed, per_page, limit, len(items))
3737

38-
response = resend_request_until_parsed_response_is_valid(
38+
response = await resend_request_until_parsed_response_is_valid(
3939
request_func, additionalParams, wrapped_parse_func, validate_func, 3
4040
)
4141
results = response["results"]
@@ -71,14 +71,14 @@ def get_continuation_contents(continuation, parse_func):
7171
return []
7272

7373

74-
def resend_request_until_parsed_response_is_valid(
74+
async def resend_request_until_parsed_response_is_valid(
7575
request_func, request_additional_params, parse_func, validate_func, max_retries
7676
):
77-
response = request_func(request_additional_params)
77+
response = await request_func(request_additional_params)
7878
parsed_object = parse_func(response)
7979
retry_counter = 0
8080
while not validate_func(parsed_object) and retry_counter < max_retries:
81-
response = request_func(request_additional_params)
81+
response = await request_func(request_additional_params)
8282
attempt = parse_func(response)
8383
if len(attempt["parsed"]) > len(parsed_object["parsed"]):
8484
parsed_object = attempt

ytmusicapi/helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ def initialize_context():
3232
}
3333

3434

35-
def get_visitor_id(request_func):
36-
response = request_func(YTM_DOMAIN)
37-
matches = re.findall(r"ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;", response.text)
35+
async def get_visitor_id(request_func):
36+
response = await request_func(YTM_DOMAIN)
37+
matches = re.findall(r"ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;", await response.text())
3838
visitor_id = ""
3939
if len(matches) > 0:
4040
ytcfg = json.loads(matches[0])

ytmusicapi/mixins/_protocol.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Dict, Optional, Protocol
44

5-
from requests import Response
5+
from aiohttp import ClientResponse
66

77
from ytmusicapi.auth.types import AuthType
88
from ytmusicapi.parsers.i18n import Parser
@@ -20,10 +20,10 @@ class MixinProtocol(Protocol):
2020
def _check_auth(self) -> None:
2121
"""checks if self has authentication"""
2222

23-
def _send_request(self, endpoint: str, body: Dict, additionalParams: str = "") -> Dict:
23+
async def _send_request(self, endpoint: str, body: Dict, additionalParams: str = "") -> Dict:
2424
"""for sending post requests to YouTube Music"""
2525

26-
def _send_get_request(self, url: str, params: Optional[Dict] = None) -> Response:
26+
async def _send_get_request(self, url: str, params: Optional[Dict] = None) -> ClientResponse:
2727
"""for sending get requests to YouTube Music"""
2828

2929
@property

0 commit comments

Comments
 (0)