From 1968f3e437154f0bdc026f28a3ce6540b803355e Mon Sep 17 00:00:00 2001 From: Jakub Suchenek Date: Wed, 6 Dec 2023 20:31:57 +0100 Subject: [PATCH] Release v1.0.0 --- .vscode/launch.json | 1 + {yt_api => src/yt_api}/__init__.py | 0 {yt_api => src/yt_api}/add_playlist.py | 25 ++++++++++--- {yt_api => src/yt_api}/add_video.py | 5 +-- {yt_api => src/yt_api}/authentication.py | 0 {yt_api => src/yt_api}/database/__init__.py | 0 {yt_api => src/yt_api}/database/core.py | 0 {yt_api => src/yt_api}/database/playlist.py | 31 +++------------- {yt_api => src/yt_api}/database/track.py | 31 +++------------- {yt_api => src/yt_api}/models/__init__.py | 0 {yt_api => src/yt_api}/models/localization.py | 0 {yt_api => src/yt_api}/models/thumbnail.py | 0 src/yt_api/tests/__init__.py | 0 src/yt_api/tests/core.py | 15 ++++++++ src/yt_api/tests/test_playlist.py | 36 +++++++++++++++++++ src/yt_api/tests/test_track.py | 36 +++++++++++++++++++ 16 files changed, 119 insertions(+), 61 deletions(-) rename {yt_api => src/yt_api}/__init__.py (100%) rename {yt_api => src/yt_api}/add_playlist.py (78%) rename {yt_api => src/yt_api}/add_video.py (95%) rename {yt_api => src/yt_api}/authentication.py (100%) rename {yt_api => src/yt_api}/database/__init__.py (100%) rename {yt_api => src/yt_api}/database/core.py (100%) rename {yt_api => src/yt_api}/database/playlist.py (64%) rename {yt_api => src/yt_api}/database/track.py (64%) rename {yt_api => src/yt_api}/models/__init__.py (100%) rename {yt_api => src/yt_api}/models/localization.py (100%) rename {yt_api => src/yt_api}/models/thumbnail.py (100%) create mode 100644 src/yt_api/tests/__init__.py create mode 100644 src/yt_api/tests/core.py create mode 100644 src/yt_api/tests/test_playlist.py create mode 100644 src/yt_api/tests/test_track.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 9197dd3..7268da1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,6 +7,7 @@ "request": "launch", "program": "${file}", "console": "integratedTerminal", + "env": {"PYTHONPATH": "${workspaceFolder}/src"}, "justMyCode": true } ] diff --git a/yt_api/__init__.py b/src/yt_api/__init__.py similarity index 100% rename from yt_api/__init__.py rename to src/yt_api/__init__.py diff --git a/yt_api/add_playlist.py b/src/yt_api/add_playlist.py similarity index 78% rename from yt_api/add_playlist.py rename to src/yt_api/add_playlist.py index 10cf7a5..55189e2 100644 --- a/yt_api/add_playlist.py +++ b/src/yt_api/add_playlist.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- """ -Creates a private playlist in the authorizing user's YouTube channel. +Creates a playlist in the authorizing user's YouTube channel. """ +from enum import Enum + from googleapiclient.discovery import Resource from googleapiclient.errors import HttpError @@ -9,6 +11,12 @@ from models import Localization, Thumbnail +class PrivacyStatus(Enum): + PRIVATE = 'private' + PUBLIC = 'public' + UNLISTED = 'unlisted' + + class Playlist: def __init__(self, payload: dict): self.kind: str = payload.get('kind') @@ -44,8 +52,15 @@ def __init__(self, payload: dict): -def add_playlist(youtube: Resource, title: str, description: str) -> Playlist: +def add_playlist( + youtube: Resource, + title: str, + description: str, + privacy_status: PrivacyStatus = None + ) -> Playlist: """Creates playlist then returns it as `Playlist`.""" + if not privacy_status: + privacy_status = PrivacyStatus.UNLISTED response: dict = youtube.playlists().insert( part = 'snippet,status', body = dict( @@ -54,7 +69,7 @@ def add_playlist(youtube: Resource, title: str, description: str) -> Playlist: description = description ), status = dict( - privacyStatus = 'private' + privacyStatus = privacy_status.value ) ) ).execute() @@ -66,8 +81,8 @@ def add_playlist(youtube: Resource, title: str, description: str) -> Playlist: # Connect to YouTube youtube = get_authenticated_service() - playlist_title = 'Test playlist' - playlist_description = 'Playlist created with YouTube API.' + playlist_title = input('Playlist title: ') or 'Test playlist' + playlist_description = input('Playlist description: ') or 'Playlist created with `yt_api` by AnonymousX86.' # Create playlist try: diff --git a/yt_api/add_video.py b/src/yt_api/add_video.py similarity index 95% rename from yt_api/add_video.py rename to src/yt_api/add_video.py index b4d3917..b5d02d0 100644 --- a/yt_api/add_video.py +++ b/src/yt_api/add_video.py @@ -70,9 +70,10 @@ def add_video(youtube: Resource, playlist_id: str, video_id: str) -> bool: # Connect to YouTube youtube = get_authenticated_service() - playlist_id = 'PLHpqNKpoWiNX5Qbl0syonrL3El3Ojt9Kf' - video_id = 'dQw4w9WgXcQ' + playlist_id = input('Playlist ID: ') + video_id = input('Video ID: ') or 'dQw4w9WgXcQ' + # Add video to playlist try: playlist_item = add_video(youtube, playlist_id, video_id) if playlist_item: diff --git a/yt_api/authentication.py b/src/yt_api/authentication.py similarity index 100% rename from yt_api/authentication.py rename to src/yt_api/authentication.py diff --git a/yt_api/database/__init__.py b/src/yt_api/database/__init__.py similarity index 100% rename from yt_api/database/__init__.py rename to src/yt_api/database/__init__.py diff --git a/yt_api/database/core.py b/src/yt_api/database/core.py similarity index 100% rename from yt_api/database/core.py rename to src/yt_api/database/core.py diff --git a/yt_api/database/playlist.py b/src/yt_api/database/playlist.py similarity index 64% rename from yt_api/database/playlist.py rename to src/yt_api/database/playlist.py index bacfaea..f4037ec 100644 --- a/yt_api/database/playlist.py +++ b/src/yt_api/database/playlist.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -from sqlalchemy import Column, inspect, Integer, String -from sqlalchemy.orm import Session +from sqlalchemy import Column, Integer, String -from core import Base, create_session +from .core import Base, create_session class Playlist(Base): @@ -43,27 +42,5 @@ def get_playlist(cls, *, spotify_id: str = None, youtube_id: str = None) -> 'Pla playlist = session.query(cls).filter_by(youtube_id=youtube_id).first() return playlist - -def test_playlist(session: Session) -> bool: - inspector = inspect(session.bind) - return 'playlists' in inspector.get_table_names() - - -if __name__ == '__main__': - from logging import basicConfig, getLogger, INFO - from rich.logging import RichHandler - - basicConfig( - level=INFO, - format='%(message)s', - datefmt='[%X]', - handlers=[RichHandler(markup=True, rich_tracebacks=True)] - ) - log = getLogger('sqlalchemy.engine') - log.setLevel(INFO) - - session = create_session() - if (valid := test_playlist(session)): - log.info('The `playlists` table exists and is correctly structured.') - else: - log.error('The `playlists` table does not exist or is not correctly structured.') + def remove(self) -> None: + self.remove_playlist(spotify_id=self.spotify_id) diff --git a/yt_api/database/track.py b/src/yt_api/database/track.py similarity index 64% rename from yt_api/database/track.py rename to src/yt_api/database/track.py index adbebf8..871f4b0 100644 --- a/yt_api/database/track.py +++ b/src/yt_api/database/track.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- -from sqlalchemy import Column, inspect, Integer, String -from sqlalchemy.orm import Session +from sqlalchemy import Column, Integer, String -from core import Base, create_session +from .core import Base, create_session class Track(Base): @@ -43,27 +42,5 @@ def get_track(cls, *, spotify_id: str = None, youtube_id: str = None) -> 'Track track = session.query(cls).filter_by(youtube_id=youtube_id).first() return track - -def test_track(session: Session) -> bool: - inspector = inspect(session.bind) - return 'tracks' in inspector.get_table_names() - - -if __name__ == '__main__': - from logging import basicConfig, getLogger, INFO - from rich.logging import RichHandler - - basicConfig( - level=INFO, - format='%(message)s', - datefmt='[%X]', - handlers=[RichHandler(markup=True, rich_tracebacks=True)] - ) - log = getLogger('sqlalchemy.engine') - log.setLevel(INFO) - - session = create_session() - if (valid := test_track(session)): - log.info('The `tracks` table exists and is correctly structured.') - else: - log.error('The `tracks` table does not exist or is not correctly structured.') + def remove(self) -> None: + self.remove_track(spotify_id=self.spotify_id) diff --git a/yt_api/models/__init__.py b/src/yt_api/models/__init__.py similarity index 100% rename from yt_api/models/__init__.py rename to src/yt_api/models/__init__.py diff --git a/yt_api/models/localization.py b/src/yt_api/models/localization.py similarity index 100% rename from yt_api/models/localization.py rename to src/yt_api/models/localization.py diff --git a/yt_api/models/thumbnail.py b/src/yt_api/models/thumbnail.py similarity index 100% rename from yt_api/models/thumbnail.py rename to src/yt_api/models/thumbnail.py diff --git a/src/yt_api/tests/__init__.py b/src/yt_api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/yt_api/tests/core.py b/src/yt_api/tests/core.py new file mode 100644 index 0000000..229882a --- /dev/null +++ b/src/yt_api/tests/core.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from random import choice + + +_LOWERCASE = [*'qwertyuiopasdfghjklzxcvbnm'] +_UPPER = list(map(lambda x: x.upper(), _LOWERCASE)) +_LETTERS = _LOWERCASE + _UPPER +_PREFIX = 'TEST_' + + +def random_string(length: int) -> str: + result = _PREFIX + for _ in range(length - len(_PREFIX)): + result += choice(_LETTERS) + return result diff --git a/src/yt_api/tests/test_playlist.py b/src/yt_api/tests/test_playlist.py new file mode 100644 index 0000000..2b9c001 --- /dev/null +++ b/src/yt_api/tests/test_playlist.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from unittest import TestCase, main as test_main + +from yt_api.database import Playlist +from yt_api.tests.core import random_string + + +class TestPlaylist(TestCase): + def setUp(self) -> None: + self.spotify_id = random_string(22) + self.youtoube_id = random_string(34) + + def test_add_and_get_playlist(self) -> None: + Playlist.add_playlist( + spotify_id=self.spotify_id, + youtube_id=self.youtoube_id + ) + playlist_by_spotify = Playlist.get_playlist(spotify_id=self.spotify_id) + self.assertEqual(playlist_by_spotify.spotify_id, self.spotify_id) + self.assertEqual(playlist_by_spotify.youtube_id, self.youtoube_id) + playlist_by_youtube = Playlist.get_playlist(youtube_id=self.youtoube_id) + self.assertEqual(playlist_by_youtube.spotify_id, self.spotify_id) + self.assertEqual(playlist_by_youtube.youtube_id, self.youtoube_id) + + def test_remove_playlist(self) -> None: + Playlist.add_playlist( + spotify_id=self.spotify_id, + youtube_id=self.youtoube_id + ) + Playlist.remove_playlist(spotify_id=self.spotify_id) + playlist = Playlist.get_playlist(spotify_id=self.spotify_id) + self.assertIsNone(playlist) + + +if __name__ == '__main__': + test_main() diff --git a/src/yt_api/tests/test_track.py b/src/yt_api/tests/test_track.py new file mode 100644 index 0000000..0cdff5c --- /dev/null +++ b/src/yt_api/tests/test_track.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from unittest import TestCase, main as test_main + +from yt_api.database import Track +from yt_api.tests.core import random_string + + +class TestTrack(TestCase): + def setUp(self) -> None: + self.spotify_id = random_string(22) + self.youtoube_id = random_string(11) + + def test_add_and_get_track(self) -> None: + Track.add_track( + spotify_id=self.spotify_id, + youtube_id=self.youtoube_id + ) + track_by_spotify = Track.get_track(spotify_id=self.spotify_id) + self.assertEqual(track_by_spotify.spotify_id, self.spotify_id) + self.assertEqual(track_by_spotify.youtube_id, self.youtoube_id) + track_by_youtube = Track.get_track(youtube_id=self.youtoube_id) + self.assertEqual(track_by_youtube.spotify_id, self.spotify_id) + self.assertEqual(track_by_youtube.youtube_id, self.youtoube_id) + + def test_remove_track(self) -> None: + Track.add_track( + spotify_id=self.spotify_id, + youtube_id=self.youtoube_id + ) + Track.remove_track(spotify_id=self.spotify_id) + track = Track.get_track(spotify_id=self.spotify_id) + self.assertIsNone(track) + + +if __name__ == '__main__': + test_main()