yutipy 2.0.0__tar.gz → 2.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of yutipy might be problematic. Click here for more details.

Files changed (65) hide show
  1. {yutipy-2.0.0 → yutipy-2.1.1}/PKG-INFO +1 -1
  2. {yutipy-2.0.0 → yutipy-2.1.1}/docs/api_reference.rst +8 -0
  3. yutipy-2.1.1/tests/test_deezer.py +132 -0
  4. yutipy-2.1.1/tests/test_itunes.py +87 -0
  5. yutipy-2.1.1/tests/test_kkbox.py +104 -0
  6. yutipy-2.1.1/tests/test_musicyt.py +88 -0
  7. yutipy-2.1.1/tests/test_spotify.py +276 -0
  8. {yutipy-2.0.0 → yutipy-2.1.1}/tests/test_utils.py +2 -2
  9. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/deezer.py +5 -5
  10. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/itunes.py +7 -7
  11. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/kkbox.py +181 -72
  12. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/models.py +13 -0
  13. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/musicyt.py +13 -12
  14. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/spotify.py +211 -40
  15. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/yutipy_music.py +2 -0
  16. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/PKG-INFO +1 -1
  17. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/SOURCES.txt +0 -1
  18. yutipy-2.0.0/tests/test_deezer.py +0 -54
  19. yutipy-2.0.0/tests/test_itunes.py +0 -49
  20. yutipy-2.0.0/tests/test_kkbox.py +0 -44
  21. yutipy-2.0.0/tests/test_musicyt.py +0 -56
  22. yutipy-2.0.0/tests/test_spotify.py +0 -130
  23. yutipy-2.0.0/tests/test_yutipy_music.py +0 -48
  24. {yutipy-2.0.0 → yutipy-2.1.1}/.gitattributes +0 -0
  25. {yutipy-2.0.0 → yutipy-2.1.1}/.github/FUNDING.yml +0 -0
  26. {yutipy-2.0.0 → yutipy-2.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {yutipy-2.0.0 → yutipy-2.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {yutipy-2.0.0 → yutipy-2.1.1}/.github/dependabot.yml +0 -0
  29. {yutipy-2.0.0 → yutipy-2.1.1}/.github/workflows/pytest-unit-testing.yml +0 -0
  30. {yutipy-2.0.0 → yutipy-2.1.1}/.github/workflows/release.yml +0 -0
  31. {yutipy-2.0.0 → yutipy-2.1.1}/.gitignore +0 -0
  32. {yutipy-2.0.0 → yutipy-2.1.1}/.readthedocs.yaml +0 -0
  33. {yutipy-2.0.0 → yutipy-2.1.1}/LICENSE +0 -0
  34. {yutipy-2.0.0 → yutipy-2.1.1}/MANIFEST.in +0 -0
  35. {yutipy-2.0.0 → yutipy-2.1.1}/README.md +0 -0
  36. {yutipy-2.0.0 → yutipy-2.1.1}/docs/Makefile +0 -0
  37. {yutipy-2.0.0 → yutipy-2.1.1}/docs/_static/yutipy_header.png +0 -0
  38. {yutipy-2.0.0 → yutipy-2.1.1}/docs/_static/yutipy_logo.png +0 -0
  39. {yutipy-2.0.0 → yutipy-2.1.1}/docs/available_platforms.rst +0 -0
  40. {yutipy-2.0.0 → yutipy-2.1.1}/docs/cli.rst +0 -0
  41. {yutipy-2.0.0 → yutipy-2.1.1}/docs/conf.py +0 -0
  42. {yutipy-2.0.0 → yutipy-2.1.1}/docs/faq.rst +0 -0
  43. {yutipy-2.0.0 → yutipy-2.1.1}/docs/index.rst +0 -0
  44. {yutipy-2.0.0 → yutipy-2.1.1}/docs/installation.rst +0 -0
  45. {yutipy-2.0.0 → yutipy-2.1.1}/docs/make.bat +0 -0
  46. {yutipy-2.0.0 → yutipy-2.1.1}/docs/requirements.txt +0 -0
  47. {yutipy-2.0.0 → yutipy-2.1.1}/docs/usage_examples.rst +0 -0
  48. {yutipy-2.0.0 → yutipy-2.1.1}/pyproject.toml +0 -0
  49. {yutipy-2.0.0 → yutipy-2.1.1}/requirements-dev.txt +0 -0
  50. {yutipy-2.0.0 → yutipy-2.1.1}/requirements.txt +0 -0
  51. {yutipy-2.0.0 → yutipy-2.1.1}/setup.cfg +0 -0
  52. {yutipy-2.0.0 → yutipy-2.1.1}/tests/__init__.py +0 -0
  53. {yutipy-2.0.0 → yutipy-2.1.1}/tests/test_models.py +0 -0
  54. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/__init__.py +0 -0
  55. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/cli/__init__.py +0 -0
  56. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/cli/config.py +0 -0
  57. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/cli/search.py +0 -0
  58. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/exceptions.py +0 -0
  59. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/logger.py +0 -0
  60. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/utils/__init__.py +0 -0
  61. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy/utils/helpers.py +0 -0
  62. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/dependency_links.txt +0 -0
  63. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/entry_points.txt +0 -0
  64. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/requires.txt +0 -0
  65. {yutipy-2.0.0 → yutipy-2.1.1}/yutipy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yutipy
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: A simple package for retrieving music information from various music platforms APIs.
5
5
  Author: Cheap Nightbot
6
6
  Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
@@ -82,6 +82,14 @@ MusicInfos
82
82
  :noindex:
83
83
  :exclude-members: album_art, album_art_source, album_title, album_type, artists, genre, id, isrc, lyrics, release_date, tempo, title, type, upc, url
84
84
 
85
+ UserPlaying
86
+ -----------
87
+
88
+ .. autoclass:: yutipy.models.UserPlaying
89
+ :members:
90
+ :noindex:
91
+ :exclude-members: album_art, album_art_source, album_title, album_type, artists, genre, id, isrc, lyrics, release_date, tempo, title, type, upc, url, is_playing
92
+
85
93
  Exceptions
86
94
  =============
87
95
 
@@ -0,0 +1,132 @@
1
+ import pytest
2
+
3
+ from yutipy.deezer import Deezer
4
+ from yutipy.models import MusicInfo
5
+
6
+
7
+ @pytest.fixture
8
+ def deezer():
9
+ return Deezer()
10
+
11
+
12
+ class MockSearchResponse:
13
+ status_code = 200
14
+
15
+ @staticmethod
16
+ def raise_for_status():
17
+ pass
18
+
19
+ @staticmethod
20
+ def json():
21
+ return {
22
+ "data": [
23
+ {
24
+ "id": "1234567",
25
+ "title": "Test Track",
26
+ "link": "https://www.deezer.com/track/1234567",
27
+ "type": "track",
28
+ "artist": {"id": "1", "name": "Artist X"},
29
+ "album": {
30
+ "id": "110678",
31
+ "title": "Test Album",
32
+ "cover_xl": "https://example.com/image/1234567",
33
+ "type": "album",
34
+ },
35
+ },
36
+ {
37
+ "id": "789253",
38
+ "title": "Test Album",
39
+ "link": "https://www.deezer.com/track/789253",
40
+ "type": "album",
41
+ "cover_xl": "https://example.com/image/789253",
42
+ "record_type": "album",
43
+ "artist": {"id": "2", "name": "Artist Y"},
44
+ },
45
+ ]
46
+ }
47
+
48
+
49
+ class MockResponse:
50
+ status_code = 200
51
+
52
+ @staticmethod
53
+ def raise_for_status():
54
+ pass
55
+
56
+ @staticmethod
57
+ def json():
58
+ return {
59
+ "id": "1234567",
60
+ "isrc": "ISRC",
61
+ "upc": "UPC",
62
+ "release_date": "2001-03-12",
63
+ "bpm": 0,
64
+ "genres": {
65
+ "data": [
66
+ {
67
+ "id": 113,
68
+ "name": "Dance",
69
+ "type": "genre",
70
+ }
71
+ ]
72
+ },
73
+ }
74
+
75
+
76
+ @pytest.fixture
77
+ def mock_response(deezer, monkeypatch):
78
+ def mock_get(*args, **kwargs):
79
+ return MockSearchResponse()
80
+
81
+ monkeypatch.setattr(deezer._Deezer__session, "get", mock_get)
82
+
83
+
84
+ def test_search_valid(deezer, mock_response):
85
+ result = deezer.search("Artist X", "Test Track", normalize_non_english=False)
86
+ assert result is not None
87
+ assert isinstance(result, MusicInfo)
88
+ assert result.title == "Test Track"
89
+
90
+
91
+ def test_search_invalid(deezer, mock_response):
92
+ result = deezer.search(
93
+ "Nonexistent Artist", "Nonexistent Song", normalize_non_english=False
94
+ )
95
+ assert result is None
96
+
97
+
98
+ def test_get_upc_isrc_track(deezer, monkeypatch):
99
+ def mock_get(*args, **kwargs):
100
+ return MockResponse()
101
+
102
+ monkeypatch.setattr(deezer._Deezer__session, "get", mock_get)
103
+
104
+ track_id = 1234567
105
+ result = deezer._get_upc_isrc(track_id, "track")
106
+ assert result is not None
107
+ assert "isrc" in result
108
+ assert "release_date" in result
109
+
110
+
111
+ def test_get_upc_isrc_album(deezer, monkeypatch):
112
+
113
+ def mock_get(*args, **kwargs):
114
+ return MockResponse()
115
+
116
+ monkeypatch.setattr(deezer._Deezer__session, "get", mock_get)
117
+
118
+ album_id = 1234567
119
+ result = deezer._get_upc_isrc(album_id, "album")
120
+ assert result is not None
121
+ assert "upc" in result
122
+ assert "release_date" in result
123
+
124
+
125
+ def test_search_no_results(deezer, mock_response):
126
+ result = deezer.search("Adele", "Nonexistent Song", normalize_non_english=False)
127
+ assert result is None
128
+
129
+
130
+ def test_close_session(deezer):
131
+ deezer.close_session()
132
+ assert deezer.is_session_closed
@@ -0,0 +1,87 @@
1
+ import pytest
2
+ from pytest import raises
3
+
4
+ from yutipy.itunes import Itunes
5
+ from yutipy.models import MusicInfo
6
+ from yutipy.exceptions import InvalidValueException
7
+
8
+
9
+ @pytest.fixture
10
+ def itunes():
11
+ return Itunes()
12
+
13
+
14
+ class MockResponse:
15
+ status_code = 200
16
+
17
+ @staticmethod
18
+ def raise_for_status():
19
+ pass
20
+
21
+ @staticmethod
22
+ def json():
23
+ return {
24
+ "results": [
25
+ {
26
+ "wrapperType": "track",
27
+ "kind": "song",
28
+ "collectionId": 12345678,
29
+ "trackId": 91011123,
30
+ "artistName": "Artist X",
31
+ "collectionName": "Test Album",
32
+ "trackName": "Test Track",
33
+ "collectionViewUrl": "https://itunes.apple.com/12345678",
34
+ "trackViewUrl": "https://itunes.apple.com/91011123",
35
+ "artworkUrl100": "https://example.com/image/12345678",
36
+ "trackCount": 14,
37
+ "releaseDate": "2016-08-11T12:00:00Z",
38
+ "primaryGenreName": "Rock",
39
+ }
40
+ ]
41
+ }
42
+
43
+
44
+ @pytest.fixture
45
+ def mock_response(itunes, monkeypatch):
46
+ def mock_get(*args, **kwargs):
47
+ return MockResponse()
48
+
49
+ monkeypatch.setattr(itunes._Itunes__session, "get", mock_get)
50
+
51
+
52
+ def test_search_valid(itunes, mock_response):
53
+ artist = "Artist X"
54
+ song = "Test Track"
55
+ result = itunes.search(artist, song, normalize_non_english=False)
56
+ assert result is not None
57
+ assert isinstance(result, MusicInfo)
58
+ assert result.artists == artist
59
+ assert result.title == song
60
+
61
+
62
+ def test_search_invalid(itunes, mock_response):
63
+ artist = "Nonexistent Artist"
64
+ song = "Nonexistent Song"
65
+ result = itunes.search(artist, song, normalize_non_english=False)
66
+ assert result is None
67
+
68
+
69
+ def test_search_empty_artist(itunes, mock_response):
70
+ artist = ""
71
+ song = "Test Track"
72
+
73
+ with raises(InvalidValueException):
74
+ itunes.search(artist, song)
75
+
76
+
77
+ def test_search_empty_song(itunes, mock_response):
78
+ artist = "Artist X"
79
+ song = ""
80
+
81
+ with raises(InvalidValueException):
82
+ itunes.search(artist, song)
83
+
84
+
85
+ def test_close_session(itunes):
86
+ itunes.close_session()
87
+ assert itunes.is_session_closed
@@ -0,0 +1,104 @@
1
+ import pytest
2
+ from pytest import raises
3
+
4
+ from yutipy.exceptions import InvalidValueException
5
+ from yutipy.models import MusicInfo
6
+ from yutipy.kkbox import KKBox
7
+
8
+
9
+ @pytest.fixture(scope="module")
10
+ def kkbox():
11
+ def mock_get_access_token():
12
+ return {
13
+ "access_token": "test_access_token",
14
+ "expires_in": 3600,
15
+ "requested_at": 1234567890,
16
+ }
17
+
18
+ kkbox_instance = KKBox(
19
+ client_id="test_client_id", client_secret="test_client_secret", defer_load=True
20
+ )
21
+
22
+ kkbox_instance._KKBox__get_access_token = mock_get_access_token
23
+ kkbox_instance.load_token_after_init()
24
+ return kkbox_instance
25
+
26
+
27
+ class MockResponse:
28
+ status_code = 200
29
+
30
+ @staticmethod
31
+ def raise_for_status():
32
+ pass
33
+
34
+ @staticmethod
35
+ def json():
36
+ return {
37
+ "tracks": {
38
+ "data": [
39
+ {
40
+ "id": "123456",
41
+ "name": "Test Track",
42
+ "isrc": "ISRC",
43
+ "url": "https://kkbox.com/track/123456",
44
+ "album": {
45
+ "id": "78910",
46
+ "name": "Test Album",
47
+ "url": "https://kkbox.com/album/78910",
48
+ "release_date": "2001-10-12",
49
+ "images": [
50
+ {"url": "https://example.com/image/78910_128"},
51
+ {"url": "https://example.com/image/78910_512"},
52
+ {"url": "https://example.com/image/78910_1000"},
53
+ ],
54
+ "artist": {
55
+ "id": "65791",
56
+ "name": "Artist X",
57
+ },
58
+ },
59
+ }
60
+ ]
61
+ }
62
+ }
63
+
64
+
65
+ @pytest.fixture
66
+ def mock_response(kkbox, monkeypatch):
67
+ def mock_get(*args, **kwargs):
68
+ return MockResponse()
69
+
70
+ monkeypatch.setattr(kkbox._KKBox__session, "get", mock_get)
71
+
72
+
73
+ def test_search(kkbox, mock_response):
74
+ artist = "Artist X"
75
+ song = "Test Track"
76
+ result = kkbox.search(artist, song, normalize_non_english=False)
77
+ assert result is not None
78
+ assert isinstance(result, MusicInfo)
79
+ assert result.title == song
80
+ assert artist in result.artists
81
+
82
+
83
+ def test_get_html_widget(kkbox):
84
+ html_widget = kkbox.get_html_widget(id="8rceGrek59bDS0HmQH", content_type="song")
85
+ assert html_widget is not None
86
+ assert isinstance(html_widget, str)
87
+
88
+ with raises(InvalidValueException):
89
+ kkbox.get_html_widget(id="8rceGrek59bDS0HmQH", content_type="track")
90
+
91
+ with raises(InvalidValueException):
92
+ kkbox.get_html_widget(
93
+ id="8rceGrek59bDS0HmQH", content_type="song", territory="US"
94
+ )
95
+
96
+ with raises(InvalidValueException):
97
+ kkbox.get_html_widget(
98
+ id="8rceGrek59bDS0HmQH", content_type="song", widget_lang="JP"
99
+ )
100
+
101
+
102
+ def test_close_session(kkbox):
103
+ kkbox.close_session()
104
+ assert kkbox.is_session_closed
@@ -0,0 +1,88 @@
1
+ import pytest
2
+ from pytest import raises
3
+ from yutipy.musicyt import MusicYT
4
+ from yutipy.models import MusicInfo
5
+ from yutipy.exceptions import InvalidValueException
6
+
7
+
8
+ @pytest.fixture
9
+ def music_yt():
10
+ return MusicYT()
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_ytmusic_search(music_yt, monkeypatch):
15
+ def mock_search(*args, **kwargs):
16
+ return [
17
+ {
18
+ "category": "Songs",
19
+ "resultType": "song",
20
+ "videoId": "ZrOKjDZOtkA",
21
+ "title": "Test Song",
22
+ "artists": [{"name": "Test Artist", "id": "ADlkasoiuUUer34ldb"}],
23
+ "album": {"name": "Test Album", "id": "MIekd34_934"},
24
+ "microformat": {
25
+ "microformatDataRenderer": {"uploadDate": "1969-12-31"}
26
+ },
27
+ "thumbnails": [{"url": "https://example.com/image/ZrOKjDZOtkA"}],
28
+ }
29
+ ]
30
+
31
+ def mock_get_watch_playlist(*args, **kwargs):
32
+ return {"lyrics": "MPLYt_HNNclO0Ddoc-17"}
33
+
34
+ def mock_get_lyrics(*args, **kwargs):
35
+ return {"lyrics": "Never Gonna Give You Up!"}
36
+
37
+ # Patch the `search` and other methods of the `YTMusic` class
38
+ monkeypatch.setattr("ytmusicapi.YTMusic.search", mock_search)
39
+ monkeypatch.setattr(
40
+ "ytmusicapi.YTMusic.get_watch_playlist", mock_get_watch_playlist
41
+ )
42
+ monkeypatch.setattr("ytmusicapi.YTMusic.get_lyrics", mock_get_lyrics)
43
+
44
+
45
+ def test_search_valid(music_yt, mock_ytmusic_search):
46
+ artist = "Test Artist"
47
+ song = "Test Song"
48
+ result = music_yt.search(artist, song, normalize_non_english=False)
49
+ assert result is not None
50
+ assert isinstance(result, MusicInfo)
51
+ assert artist in result.artists
52
+ assert result.title == song
53
+
54
+
55
+ def test_search_invalid(music_yt, mock_ytmusic_search):
56
+ artist = ";laksjdflkajsdfj;asdjf"
57
+ song = "jaksjd;fljkas;dfkjasldkjf"
58
+ result = music_yt.search(artist, song)
59
+ assert result is None
60
+
61
+
62
+ def test_search_partial_match(music_yt, mock_ytmusic_search):
63
+ artist = "Test"
64
+ song = "Test"
65
+ result = music_yt.search(artist, song, normalize_non_english=False)
66
+ assert result is not None
67
+ assert song in result.title
68
+
69
+
70
+ def test_search_empty_artist(music_yt, mock_ytmusic_search):
71
+ artist = ""
72
+ song = "Song"
73
+
74
+ with raises(InvalidValueException):
75
+ music_yt.search(artist, song, normalize_non_english=False)
76
+
77
+
78
+ def test_search_empty_song(music_yt, mock_ytmusic_search):
79
+ artist = "Artist"
80
+ song = ""
81
+
82
+ with raises(InvalidValueException):
83
+ music_yt.search(artist, song, normalize_non_english=False)
84
+
85
+
86
+ def test_close_session(music_yt):
87
+ music_yt.close_session()
88
+ assert music_yt.is_session_closed