Skip to content

Commit

Permalink
format
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Dec 31, 2023
1 parent fad01a6 commit 66abc3b
Show file tree
Hide file tree
Showing 35 changed files with 1,292 additions and 1,160 deletions.
20 changes: 10 additions & 10 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
import os
import sys

sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, '../..')
sys.path.insert(0, os.path.abspath("."))
sys.path.insert(0, "../..")
from ytmusicapi import __version__ # noqa: E402

on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
on_rtd = os.environ.get("READTHEDOCS", None) == "True"

# -- Project information -----------------------------------------------------

project = 'ytmusicapi'
copyright = '2022, sigma67'
author = 'sigma67'
project = "ytmusicapi"
copyright = "2022, sigma67"
author = "sigma67"

# The full version, including alpha/beta/rc tags
version = __version__
Expand All @@ -38,17 +38,17 @@
extensions = ["sphinx.ext.autodoc"]

# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"

# The master toctree document.
master_doc = 'index'
master_doc = "index"

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []

html_theme = "sphinx_rtd_theme"
html_theme = "sphinx_rtd_theme"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ command_line = "-m unittest discover tests"

[tool.ruff]
#fix = true
line-length = 99
line-length = 110
ignore = [ "F403", "F405", "F821", "E731" ]
extend-select = [
"I", # isort
Expand Down
144 changes: 55 additions & 89 deletions tests/test.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions ytmusicapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# package is not installed
pass

__copyright__ = 'Copyright 2023 sigma67'
__license__ = 'MIT'
__title__ = 'ytmusicapi'
__copyright__ = "Copyright 2023 sigma67"
__license__ = "MIT"
__title__ = "ytmusicapi"
__all__ = ["YTMusic", "setup_oauth", "setup"]
3 changes: 2 additions & 1 deletion ytmusicapi/auth/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def setup_browser(filepath=None, headers_raw=None):
missing_headers = {"cookie", "x-goog-authuser"} - set(k.lower() for k in user_headers.keys())
if missing_headers:
raise Exception(
"The following entries are missing in your headers: " + ", ".join(missing_headers)
"The following entries are missing in your headers: "
+ ", ".join(missing_headers)
+ ". Please try a different request (such as /browse) and make sure you are logged in."
)

Expand Down
2 changes: 1 addition & 1 deletion ytmusicapi/auth/oauth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .credentials import OAuthCredentials
from .refreshing import RefreshingToken

__all__ = ['OAuthCredentials', 'RefreshingToken', 'OAuthToken']
__all__ = ["OAuthCredentials", "RefreshingToken", "OAuthToken"]
52 changes: 28 additions & 24 deletions ytmusicapi/auth/oauth/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@


class Credentials:
""" Base class representation of YouTubeMusicAPI OAuth Credentials """
"""Base class representation of YouTubeMusicAPI OAuth Credentials"""

client_id: str
client_secret: str

Expand All @@ -23,7 +24,8 @@ def refresh_token(self, refresh_token: str) -> BaseTokenDict:


class Token:
""" Base class representation of the YouTubeMusicAPI OAuth token. """
"""Base class representation of the YouTubeMusicAPI OAuth token."""

access_token: str
refresh_token: str
expires_in: int
Expand All @@ -34,38 +36,40 @@ class Token:
token_type: Bearer

def __repr__(self) -> str:
""" Readable version. """
return f'{self.__class__.__name__}: {self.as_dict()}'
"""Readable version."""
return f"{self.__class__.__name__}: {self.as_dict()}"

def as_dict(self) -> RefreshableTokenDict:
""" Returns dictionary containing underlying token values. """
"""Returns dictionary containing underlying token values."""
return {
'access_token': self.access_token,
'refresh_token': self.refresh_token,
'scope': self.scope,
'expires_at': self.expires_at,
'expires_in': self.expires_in,
'token_type': self.token_type
"access_token": self.access_token,
"refresh_token": self.refresh_token,
"scope": self.scope,
"expires_at": self.expires_at,
"expires_in": self.expires_in,
"token_type": self.token_type,
}

def as_json(self) -> str:
return json.dumps(self.as_dict())

def as_auth(self) -> str:
""" Returns Authorization header ready str of token_type and access_token. """
return f'{self.token_type} {self.access_token}'
"""Returns Authorization header ready str of token_type and access_token."""
return f"{self.token_type} {self.access_token}"


class OAuthToken(Token):
""" Wrapper for an OAuth token implementing expiration methods. """

def __init__(self,
access_token: str,
refresh_token: str,
scope: str,
token_type: str,
expires_at: Optional[int] = None,
expires_in: Optional[int] = None):
"""Wrapper for an OAuth token implementing expiration methods."""

def __init__(
self,
access_token: str,
refresh_token: str,
scope: str,
token_type: str,
expires_at: Optional[int] = None,
expires_in: Optional[int] = None,
):
"""
:param access_token: active oauth key
Expand Down Expand Up @@ -102,8 +106,8 @@ def update(self, fresh_access: BaseTokenDict):
expires_at attribute set using current epoch, avoid expiration desync
by passing only recently requested tokens dicts or updating values to compensate.
"""
self._access_token = fresh_access['access_token']
self._expires_at = int(time.time() + fresh_access['expires_in'])
self._access_token = fresh_access["access_token"]
self._expires_at = int(time.time() + fresh_access["expires_in"])

@property
def access_token(self) -> str:
Expand Down
40 changes: 20 additions & 20 deletions ytmusicapi/auth/oauth/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ class OAuthCredentials(Credentials):
Class for handling OAuth credential retrieval and refreshing.
"""

def __init__(self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
session: Optional[requests.Session] = None,
proxies: Optional[Dict] = None):
def __init__(
self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
session: Optional[requests.Session] = None,
proxies: Optional[Dict] = None,
):
"""
:param client_id: Optional. Set the GoogleAPI client_id used for auth flows.
Requires client_secret also be provided if set.
Expand All @@ -38,7 +40,7 @@ def __init__(self,
# id, secret should be None, None or str, str
if not isinstance(client_id, type(client_secret)):
raise KeyError(
'OAuthCredential init failure. Provide both client_id and client_secret or neither.'
"OAuthCredential init failure. Provide both client_id and client_secret or neither."
)

# bind instance to OAuth client for auth flows
Expand All @@ -50,34 +52,34 @@ def __init__(self,
self._session.proxies.update(proxies)

def get_code(self) -> AuthCodeDict:
""" Method for obtaining a new user auth code. First step of token creation. """
"""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()

def _send_request(self, url, data):
""" Method for sending post requests with required client_id and User-Agent modifications """
"""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()
issue = data.get('error')
if issue == 'unauthorized_client':
raise UnauthorizedOAuthClient(
'Token refresh error. Most likely client/token mismatch.')
issue = data.get("error")
if issue == "unauthorized_client":
raise UnauthorizedOAuthClient("Token refresh error. Most likely client/token mismatch.")

elif issue == 'invalid_client':
elif issue == "invalid_client":
raise BadOAuthClient(
'OAuth client failure. Most likely client_id and client_secret mismatch or '
'YouTubeData API is not enabled.')
"OAuth client failure. Most likely client_id and client_secret mismatch or "
"YouTubeData API is not enabled."
)
else:
raise Exception(
f'OAuth request error. status_code: {response.status_code}, url: {url}, content: {data}'
f"OAuth request error. status_code: {response.status_code}, url: {url}, content: {data}"
)
return response

def token_from_code(self, device_code: str) -> RefreshableTokenDict:
""" Method for verifying user auth code and conversion into a FullTokenDict. """
"""Method for verifying user auth code and conversion into a FullTokenDict."""
response = self._send_request(
OAUTH_TOKEN_URL,
data={
Expand All @@ -88,9 +90,7 @@ def token_from_code(self, device_code: str) -> RefreshableTokenDict:
)
return response.json()

def prompt_for_token(self,
open_browser: bool = False,
to_file: Optional[str] = None) -> RefreshingToken:
def prompt_for_token(self, open_browser: bool = False, to_file: Optional[str] = None) -> RefreshingToken:
"""
Method for CLI token creation via user inputs.
Expand Down
10 changes: 5 additions & 5 deletions ytmusicapi/auth/oauth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from typing import Literal, TypedDict, Union

DefaultScope = Union[str, Literal['https://www.googleapis.com/auth/youtube']]
Bearer = Union[str, Literal['Bearer']]
DefaultScope = Union[str, Literal["https://www.googleapis.com/auth/youtube"]]
Bearer = Union[str, Literal["Bearer"]]


class BaseTokenDict(TypedDict):
""" Limited token. Does not provide a refresh token. Commonly obtained via a token refresh. """
"""Limited token. Does not provide a refresh token. Commonly obtained via a token refresh."""

access_token: str #: str to be used in Authorization header
expires_in: int #: seconds until expiration from request timestamp
Expand All @@ -16,14 +16,14 @@ class BaseTokenDict(TypedDict):


class RefreshableTokenDict(BaseTokenDict):
""" Entire token. Including refresh. Obtained through token setup. """
"""Entire token. Including refresh. Obtained through token setup."""

expires_at: int #: UNIX epoch timestamp in seconds
refresh_token: str #: str used to obtain new access token upon expiration


class AuthCodeDict(TypedDict):
""" Keys for the json object obtained via code response during auth flow. """
"""Keys for the json object obtained via code response during auth flow."""

device_code: str #: code obtained via user confirmation and oauth consent
user_code: str #: alphanumeric code user is prompted to enter as confirmation. formatted as XXX-XXX-XXX.
Expand Down
9 changes: 3 additions & 6 deletions ytmusicapi/auth/oauth/refreshing.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@ def from_file(cls, file_path: str, credentials: Credentials, sync=True):

return cls(OAuthToken(**file_pack), credentials, file_path if sync else None)

def __init__(self,
token: OAuthToken,
credentials: Credentials,
local_cache: Optional[str] = None):
def __init__(self, token: OAuthToken, credentials: Credentials, local_cache: Optional[str] = None):
"""
:param token: Underlying Token being maintained.
:param credentials: OAuth client being used for refreshing.
Expand All @@ -56,7 +53,7 @@ def local_cache(self) -> str | None:

@local_cache.setter
def local_cache(self, path: str):
""" Update attribute and dump token to new path. """
"""Update attribute and dump token to new path."""
self._local_cache = path
self.store_token()

Expand All @@ -78,7 +75,7 @@ def store_token(self, path: Optional[str] = None) -> None:
file_path = path if path else self.local_cache

if file_path:
with open(file_path, encoding="utf8", mode='w') as file:
with open(file_path, encoding="utf8", mode="w") as file:
json.dump(self.token.as_dict(), file, indent=True)

@property
Expand Down
Loading

0 comments on commit 66abc3b

Please sign in to comment.