Skip to content

Commit

Permalink
add mypy
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Dec 31, 2023
1 parent f3e7471 commit 70a260b
Show file tree
Hide file tree
Showing 23 changed files with 312 additions and 148 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Ruff
name: lint

on:
pull_request:
Expand All @@ -13,4 +13,13 @@ jobs:
- uses: chartboost/ruff-action@v1
- uses: chartboost/ruff-action@v1
with:
args: format --check
args: format --check
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.11"
- run: pip install mypy==1.8.0
- run: mypy --install-types --non-interactive
17 changes: 8 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
repos:
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.32.0
hooks:
- id: yapf
additional_dependencies: [toml]
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.9
hooks:
# Run the linter.
- id: ruff
# Run the formatter.
- id: ruff-format
4 changes: 3 additions & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Before making changes to the code, install the development requirements using

.. code-block::
pip install -e .[dev]
pip install pipx
pipx install pdm pre-commit
pdm install
Before committing, stage your files and run style and linter checks:

Expand Down
77 changes: 76 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ extend-select = [
"I", # isort
]

[tool.mypy]
files = [
"ytmusicapi/"
]
mypy_path = "ytmusicapi"

[tool.pdm.dev-dependencies]
dev = [
"coverage>=7.4.0",
'sphinx<7',
'sphinx-rtd-theme',
"ruff>=0.1.9",
"mypy>=1.8.0",
]
1 change: 1 addition & 0 deletions tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ def test_get_search_suggestions(self):
# add search term to history
first_pass = self.yt_auth.search("b")
self.assertGreater(len(first_pass), 0)
time.sleep(3)
# get results
results = self.yt_auth.get_search_suggestions("b", detailed_runs=True)
self.assertGreater(len(results), 0)
Expand Down
3 changes: 2 additions & 1 deletion ytmusicapi/auth/browser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import platform
from typing import Optional

from requests.structures import CaseInsensitiveDict

Expand All @@ -13,7 +14,7 @@ def is_browser(headers: CaseInsensitiveDict) -> bool:
return all(key in headers for key in browser_structure)


def setup_browser(filepath=None, headers_raw=None):
def setup_browser(filepath: Optional[str] = None, headers_raw: Optional[str] = None) -> str:
contents = []
if not headers_raw:
eof = "Ctrl-D" if platform.system() != "Windows" else "'Enter, Ctrl-Z, Enter'"
Expand Down
76 changes: 43 additions & 33 deletions ytmusicapi/auth/oauth/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import time
from typing import Dict, Optional
from abc import ABC
from typing import Mapping, Optional

from requests.structures import CaseInsensitiveDict

Expand All @@ -13,7 +14,7 @@ class Credentials:
client_id: str
client_secret: str

def get_code(self) -> Dict:
def get_code(self) -> Mapping:
raise NotImplementedError()

def token_from_code(self, device_code: str) -> RefreshableTokenDict:
Expand All @@ -23,17 +24,17 @@ def refresh_token(self, refresh_token: str) -> BaseTokenDict:
raise NotImplementedError()


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

access_token: str
refresh_token: str
expires_in: int
expires_at: int
is_expiring: bool
_access_token: str
_refresh_token: str
_expires_in: int
_expires_at: int
_is_expiring: bool

scope: DefaultScope
token_type: Bearer
_scope: DefaultScope
_token_type: Bearer

def __repr__(self) -> str:
"""Readable version."""
Expand All @@ -57,6 +58,34 @@ def as_auth(self) -> str:
"""Returns Authorization header ready str of token_type and access_token."""
return f"{self.token_type} {self.access_token}"

@property
def access_token(self) -> str:
return self._access_token

@property
def refresh_token(self) -> str:
return self._refresh_token

@property
def token_type(self) -> Bearer:
return self._token_type

@property
def scope(self) -> DefaultScope:
return self._scope

@property
def expires_at(self) -> int:
return self._expires_at

@property
def expires_in(self) -> int:
return self._expires_in

@property
def is_expiring(self) -> bool:
return self.expires_in < 60


class OAuthToken(Token):
"""Wrapper for an OAuth token implementing expiration methods."""
Expand All @@ -68,7 +97,7 @@ def __init__(
scope: str,
token_type: str,
expires_at: Optional[int] = None,
expires_in: Optional[int] = None,
expires_in: int = 0,
):
"""
Expand All @@ -84,10 +113,11 @@ def __init__(
self._access_token = access_token
self._refresh_token = refresh_token
self._scope = scope
self._token_type = token_type

# set/calculate token expiration using current epoch
self._expires_at: int = expires_at if expires_at else int(time.time() + expires_in)
self._token_type = token_type
self._expires_at: int = expires_at if expires_at else int(time.time()) + expires_in
self._expires_in: int = expires_in

@staticmethod
def is_oauth(headers: CaseInsensitiveDict) -> bool:
Expand All @@ -109,26 +139,6 @@ def update(self, fresh_access: BaseTokenDict):
self._access_token = fresh_access["access_token"]
self._expires_at = int(time.time() + fresh_access["expires_in"])

@property
def access_token(self) -> str:
return self._access_token

@property
def refresh_token(self) -> str:
return self._refresh_token

@property
def token_type(self) -> Bearer:
return self._token_type

@property
def scope(self) -> DefaultScope:
return self._scope

@property
def expires_at(self) -> int:
return self._expires_at

@property
def expires_in(self) -> int:
return int(self.expires_at - time.time())
Expand Down
9 changes: 4 additions & 5 deletions ytmusicapi/auth/oauth/refreshing.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def __init__(self, token: OAuthToken, credentials: Credentials, local_cache: Opt
# values to new file location via setter
self._local_cache = local_cache

@property
def token_type(self) -> Bearer:
return self.token.token_type

@property
def local_cache(self) -> str | None:
return self._local_cache
Expand Down Expand Up @@ -78,11 +82,6 @@ def store_token(self, path: Optional[str] = None) -> None:
with open(file_path, encoding="utf8", mode="w") as file:
json.dump(self.token.as_dict(), file, indent=True)

@property
def token_type(self) -> Bearer:
# pass underlying value
return self.token.token_type

def as_dict(self) -> RefreshableTokenDict:
# override base class method with call to underlying token's method
return self.token.as_dict()
28 changes: 28 additions & 0 deletions ytmusicapi/mixins/_protocol.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""protocol that defines the functions available to mixins"""
from typing import Dict, Optional, Protocol

from requests import Response

from ytmusicapi.auth.types import AuthType
from ytmusicapi.parsers.i18n import Parser


class MixinProtocol(Protocol):
"""protocol that defines the functions available to mixins"""

auth_type: AuthType

parser: Parser

headers: Dict[str, str]

proxies: Optional[Dict[str, str]]

def _check_auth(self) -> None:
pass

def _send_request(self, endpoint: str, body: Dict, additionalParams: str = "") -> Dict:
pass

def _send_get_request(self, url: str, params: Optional[Dict] = None) -> Response:
pass
Loading

0 comments on commit 70a260b

Please sign in to comment.