Skip to content

chore: Update pdm.lock #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 9 additions & 27 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
ytmusicapi: Unofficial API for YouTube Music
############################################

.. |pypi-downloads| image:: https://img.shields.io/pypi/dm/ytmusicapi?style=flat-square
:alt: PyPI Downloads
:target: https://pypi.org/project/ytmusicapi/

.. |gitter| image:: https://badges.gitter.im/sigma67/ytmusicapi.svg
:alt: Ask questions at https://gitter.im/sigma67/ytmusicapi
:target: https://gitter.im/sigma67/ytmusicapi

.. |code-coverage| image:: https://img.shields.io/codecov/c/github/sigma67/ytmusicapi?style=flat-square
:alt: Code coverage
:target: https://codecov.io/gh/sigma67/ytmusicapi

.. |latest-release| image:: https://img.shields.io/github/v/release/sigma67/ytmusicapi?style=flat-square
:alt: Latest release
:target: https://github.com/sigma67/ytmusicapi/releases/latest

.. |commits-since-latest| image:: https://img.shields.io/github/commits-since/sigma67/ytmusicapi/latest?style=flat-square
:alt: Commits since latest release
:target: https://github.com/sigma67/ytmusicapi/commits


|pypi-downloads| |gitter| |code-coverage| |latest-release| |commits-since-latest|
This is an async prototype of `ytmusicapi <https://github.com/sigma67/ytmusicapi>`_. Use with caution.

ytmusicapi is a Python 3 library to send requests to the YouTube Music API.
It emulates YouTube Music web client requests using the user's cookie data for authentication.
Expand Down Expand Up @@ -96,12 +75,15 @@ Usage
------
.. code-block:: python

from ytmusicapi import YTMusic
import ytmusicapi
import asyncio

async def main():
ytm = ytmusicapi.YTMusic()
results = await ytm.search("coldpaly", filter="songs", limit=1)
print(results)

yt = YTMusic('oauth.json')
playlistId = yt.create_playlist('test', 'test description')
search_results = yt.search('Oasis Wonderwall')
yt.add_playlist_items(playlistId, [search_results[0]['videoId']])
asyncio.run(main())

The `tests <https://github.com/sigma67/ytmusicapi/blob/master/tests/>`_ are also a great source of usage examples.

Expand Down
12 changes: 12 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ytmusicapi
import asyncio

artists = ['coldplay', 'michael jackson', 'dr dre', 'imagine dragons', 'taylor swift', 'eminem', 'adele', 'ed sheeran']

async def main():
ytm = ytmusicapi.YTMusic()
async with ytm._session:
results = await asyncio.gather(*[ytm.search(artist, filter="songs", limit=1) for artist in artists])
print(*[f"{result[0]['artists'][0]['name']}: {result[0]['title']}" for result in results], sep="\n")

asyncio.run(main())
975 changes: 800 additions & 175 deletions pdm.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ classifiers = [
]
dependencies = [
"requests >= 2.22",
"aiohttp>=3.9.3",
]
dynamic = ["version", "readme"]

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

[tool.setuptools_scm]

[tool.setuptools]
include-package-data=false

Expand Down
34 changes: 17 additions & 17 deletions ytmusicapi/auth/oauth/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dataclasses import dataclass
from typing import Dict, Mapping, Optional

import requests
from aiohttp import ClientSession

from ytmusicapi.constants import (
OAUTH_CLIENT_ID,
Expand Down Expand Up @@ -47,7 +47,7 @@ def __init__(
self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
session: Optional[requests.Session] = None,
session: Optional[ClientSession] = None,
proxies: Optional[Dict] = None,
):
"""
Expand All @@ -67,22 +67,22 @@ def __init__(
self.client_id = client_id if client_id else OAUTH_CLIENT_ID
self.client_secret = client_secret if client_secret else OAUTH_CLIENT_SECRET

self._session = session if session else requests.Session() # for auth requests
self._session = session if session else ClientSession() # for auth requests
if proxies:
self._session.proxies.update(proxies)

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

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

data.update({"client_id": self.client_id})
response = self._session.post(url, data, headers={"User-Agent": OAUTH_USER_AGENT})
if response.status_code == 401:
data = response.json()
response = await self._session.post(url, data=data, headers={"User-Agent": OAUTH_USER_AGENT})
if response.status == 401:
data = await response.json()
issue = data.get("error")
if issue == "unauthorized_client":
raise UnauthorizedOAuthClient("Token refresh error. Most likely client/token mismatch.")
Expand All @@ -94,31 +94,31 @@ def _send_request(self, url, data):
)
else:
raise Exception(
f"OAuth request error. status_code: {response.status_code}, url: {url}, content: {data}"
f"OAuth request error. status_code: {response.status}, url: {url}, content: {data}"
)
return response

def token_from_code(self, device_code: str) -> RefreshableTokenDict:
async def token_from_code(self, device_code: str) -> RefreshableTokenDict:
"""Method for verifying user auth code and conversion into a FullTokenDict."""
response = self._send_request(
response = await self._send_request(
OAUTH_TOKEN_URL,
data={
"client_secret": self.client_secret,
"grant_type": "http://oauth.net/grant_type/device/1.0",
"code": device_code,
},
)
return response.json()
return await response.json()

def refresh_token(self, refresh_token: str) -> BaseTokenDict:
async def refresh_token(self, refresh_token: str) -> BaseTokenDict:
"""
Method for requesting a new access token for a given refresh_token.
Token must have been created by the same OAuth client.

:param refresh_token: Corresponding refresh_token for a matching access_token.
Obtained via
"""
response = self._send_request(
response = await self._send_request(
OAUTH_TOKEN_URL,
data={
"client_secret": self.client_secret,
Expand All @@ -127,4 +127,4 @@ def refresh_token(self, refresh_token: str) -> BaseTokenDict:
},
)

return response.json()
return await response.json()
14 changes: 7 additions & 7 deletions ytmusicapi/continuations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ytmusicapi.navigation import nav


def get_continuations(
async def get_continuations(
results, continuation_type, limit, request_func, parse_func, ctoken_path="", reloadable=False
):
items = []
Expand All @@ -11,7 +11,7 @@ def get_continuations(
if reloadable
else get_continuation_params(results, ctoken_path)
)
response = request_func(additionalParams)
response = await request_func(additionalParams)
if "continuationContents" in response:
results = response["continuationContents"][continuation_type]
else:
Expand All @@ -24,7 +24,7 @@ def get_continuations(
return items


def get_validated_continuations(
async def get_validated_continuations(
results, continuation_type, limit, per_page, request_func, parse_func, ctoken_path=""
):
items = []
Expand All @@ -35,7 +35,7 @@ def get_validated_continuations(
)
validate_func = lambda parsed: validate_response(parsed, per_page, limit, len(items))

response = resend_request_until_parsed_response_is_valid(
response = await resend_request_until_parsed_response_is_valid(
request_func, additionalParams, wrapped_parse_func, validate_func, 3
)
results = response["results"]
Expand Down Expand Up @@ -71,14 +71,14 @@ def get_continuation_contents(continuation, parse_func):
return []


def resend_request_until_parsed_response_is_valid(
async def resend_request_until_parsed_response_is_valid(
request_func, request_additional_params, parse_func, validate_func, max_retries
):
response = request_func(request_additional_params)
response = await request_func(request_additional_params)
parsed_object = parse_func(response)
retry_counter = 0
while not validate_func(parsed_object) and retry_counter < max_retries:
response = request_func(request_additional_params)
response = await request_func(request_additional_params)
attempt = parse_func(response)
if len(attempt["parsed"]) > len(parsed_object["parsed"]):
parsed_object = attempt
Expand Down
6 changes: 3 additions & 3 deletions ytmusicapi/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def initialize_context():
}


def get_visitor_id(request_func):
response = request_func(YTM_DOMAIN)
matches = re.findall(r"ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;", response.text)
async def get_visitor_id(request_func):
response = await request_func(YTM_DOMAIN)
matches = re.findall(r"ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;", await response.text())
visitor_id = ""
if len(matches) > 0:
ytcfg = json.loads(matches[0])
Expand Down
6 changes: 3 additions & 3 deletions ytmusicapi/mixins/_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Dict, Optional, Protocol

from requests import Response
from aiohttp import ClientResponse

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

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

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

@property
Expand Down
Loading