diff --git a/groove/exceptions.py b/groove/exceptions.py index 1cff7df..8311b1e 100644 --- a/groove/exceptions.py +++ b/groove/exceptions.py @@ -23,7 +23,7 @@ class ConfigurationError(Exception): """ -class PlaylistImportError(Exception): +class PlaylistValidationError(Exception): """ - An error was discovered in a playlist template. + An error was discovered in the playlist definition. """ diff --git a/groove/playlist.py b/groove/playlist.py index 3d82718..3b193d9 100644 --- a/groove/playlist.py +++ b/groove/playlist.py @@ -5,7 +5,7 @@ from typing import Union, List from groove import db from groove.editor import PlaylistEditor, EDITOR_TEMPLATE -from groove.exceptions import PlaylistImportError +from groove.exceptions import PlaylistValidationError from slugify import slugify from sqlalchemy import func, delete @@ -20,8 +20,8 @@ class Playlist: """ def __init__(self, slug: str, + name: str, session: Session, - name: str = '', description: str = '', create_ok=True): self._session = session @@ -114,9 +114,13 @@ class Playlist: """ Return a dictionary of the playlist and its entries. """ - if not self.exists: - return {} - playlist = dict(self.record) + playlist = { + 'name': self.name, + 'slug': self.slug, + 'description': self.description + } + if self.record: + playlist.update(dict(self.record)) playlist['entries'] = [dict(entry) for entry in self.entries] return playlist @@ -225,7 +229,7 @@ class Playlist: stmt = db.playlist.insert(values) results = self.session.execute(stmt) self.session.commit() - logging.debug(f"Saved playlist with slug {self.slug}") + logging.debug(f"Inserted playlist with slug {self.slug}") return self.session.query(db.playlist).filter( db.playlist.c.id == results.inserted_primary_key[0] ).one() @@ -246,7 +250,10 @@ class Playlist: 'name': self.name, 'description': self.description } - logging.debug(f"Saving values: {values}") + if not self.name: + raise PlaylistValidationError("This playlist has no name.") + if not self.slug: + raise PlaylistValidationError("This playlist has no slug.") self._record = self._update(values) if self._record else self._insert(values) logging.debug(f"Saved playlist {self._record.id} with slug {self._record.slug}") self.save_entries() @@ -254,7 +261,7 @@ class Playlist: def save_entries(self): plid = self.record.id stmt = delete(db.entry).where(db.entry.c.playlist_id == plid) - logging.debug(f"Deleting entries associated with playlist {plid}: {stmt}") + logging.debug(f"Deleting stale entries associated with playlist {plid}: {stmt}") self.session.execute(stmt) return self.create_entries(self.entries) @@ -273,6 +280,10 @@ class Playlist: Returns: int: The number of tracks added. """ + if not tracks: + tracks = self.entries + if not tracks: + return 0 maxtrack = self.session.query(func.max(db.entry.c.track)).filter_by( playlist_id=self.record.id ).one()[0] or 0 @@ -288,9 +299,25 @@ class Playlist: self._entries = None return len(tracks) + @classmethod + def by_slug(cls, slug, session): + try: + row = session.query(db.playlist).filter( + db.playlist.c.slug == slug + ).one() + except NoResultFound as ex: + logging.error(f"Could not locate an existing playlist with slug {slug}.") + raise ex + return cls.from_row(row, session) + @classmethod def from_row(cls, row, session): - pl = Playlist(slug=row.slug, session=session) + pl = Playlist( + slug=row.slug, + name=row.name, + description=row.description, + session=session + ) pl._record = row return pl @@ -306,7 +333,7 @@ class Playlist: session=session, ) except (IndexError, KeyError): - PlaylistImportError("The specified source was not a valid playlist.") + PlaylistValidationError("The specified source was not a valid playlist.") pl._entries = pl._get_tracks_by_artist_and_title(entries=[ list(entry.items())[0] for entry in source[name]['entries'] diff --git a/groove/shell/load.py b/groove/shell/load.py index b749254..8876b50 100644 --- a/groove/shell/load.py +++ b/groove/shell/load.py @@ -24,5 +24,5 @@ class load(BasePrompt): session=self.manager.session, create_ok=True ) - print(self.parent.playlist.summary) + print(self.parent.playlist.info) return self.parent.commands['_playlist'].start() diff --git a/groove/webserver/webserver.py b/groove/webserver/webserver.py index e3b88ff..6e8ad51 100644 --- a/groove/webserver/webserver.py +++ b/groove/webserver/webserver.py @@ -89,8 +89,9 @@ def serve_playlist(slug, db): Retrieve a playlist and its entries by a slug. """ logging.debug(f"Looking up playlist: {slug}...") - playlist = Playlist(slug=slug, session=db, create_ok=False).load() - if not playlist.record: + try: + playlist = Playlist.by_slug(slug, session=db) + except NoResultFound: logging.debug(f"Playist {slug} doesn't exist.") return HTTPResponse(status=404, body="Not found") logging.debug(f"Loaded {playlist.record}") @@ -113,9 +114,10 @@ def build(): @bottle.auth_basic(is_authenticated) @server.route('/build/search/playlist/') def search_playlist(slug, db): - playlist = Playlist(slug=slug, session=db, create_ok=False).load() - if not playlist.record: - logging.debug(f"Playist {slug} doesn't exist.") + try: + playlist = Playlist.by_slug(slug, session=db) + except NoResultFound: + logging.debug(f"Playlist {slug} doesn't exist.") body = {} else: body = json.dumps(playlist.as_dict) diff --git a/test/conftest.py b/test/conftest.py index d62e12a..27262b1 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -5,6 +5,7 @@ from pathlib import Path from dotenv import load_dotenv import groove.db +from groove.playlist import Playlist from sqlalchemy import create_engine, insert from sqlalchemy.orm import sessionmaker @@ -77,3 +78,13 @@ def db(in_memory_db): {'playlist_id': '3', 'track': '2', 'track_id': '3'}, ]) yield in_memory_db + + +@pytest.fixture(scope='function') +def empty_playlist(db): + return Playlist( + name='an empty playlist fixture', + slug='an-empty-playlist', + description='a fixture', + session=db + ) diff --git a/test/test_playlists.py b/test/test_playlists.py index e7b2cf2..b97309f 100644 --- a/test/test_playlists.py +++ b/test/test_playlists.py @@ -1,73 +1,73 @@ -# 70, 73-81, 84, 88-97, 100-104 import pytest from groove import playlist +from groove.exceptions import PlaylistValidationError -def test_create(db): - pl = playlist.Playlist(slug='test-create-playlist', session=db, create_ok=True) - assert pl.record.id +def test_create(empty_playlist): + assert empty_playlist.record.id + + +@pytest.mark.parametrize('vals, expected', [ + (dict(name='missing-the-slug', ), TypeError), + (dict(name='missing-the-slug', slug=''), PlaylistValidationError), + (dict(slug='missing-the-name', name=''), PlaylistValidationError), +]) +def test_create_missing_fields(vals, expected, db): + with pytest.raises(expected): + assert playlist.Playlist(**vals, session=db, create_ok=True).record.id @pytest.mark.parametrize('tracks', [ ('01 Guns Blazing', ), ('01 Guns Blazing', '02 UNKLE'), ]) -def test_add(db, tracks): - pl = playlist.Playlist(slug='test-create-playlist', session=db) - count = pl.add(tracks) - assert count == len(tracks) +def test_add(tracks, empty_playlist): + assert empty_playlist.add(tracks) == len(tracks) -def test_add_no_matches(db): - pl = playlist.Playlist(slug='playlist-one', session=db) - assert pl.add(('no match', )) == 0 +def test_add_no_matches(empty_playlist): + assert empty_playlist.add(('no match', )) == 0 -def test_add_multiple_matches(db): - pl = playlist.Playlist(slug='playlist-one', session=db) - assert pl.add('UNKLE',) == 0 +def test_add_multiple_matches(empty_playlist): + assert empty_playlist.add('UNKLE',) == 0 -def test_delete(db): - pl = playlist.Playlist(slug='playlist-one', session=db) - expected = pl.record.id - assert pl.delete() == expected - assert not pl.exists - assert pl.deleted +def test_delete(empty_playlist): + expected = empty_playlist.record.id + assert empty_playlist.delete() == expected + assert not empty_playlist.exists + assert empty_playlist.deleted def test_delete_playlist_not_exist(db): - pl = playlist.Playlist(slug='playlist-doesnt-exist', session=db, create_ok=False) + pl = playlist.Playlist(name='foo', slug='foo', session=db, create_ok=False) assert not pl.delete() assert not pl.exists assert not pl.deleted -def test_cannot_create_after_delete(db): - pl = playlist.Playlist(slug='playlist-one', session=db) - pl.delete() +def test_cannot_create_after_delete(db, empty_playlist): + empty_playlist.delete() with pytest.raises(RuntimeError): - assert pl.record - assert not pl.exists + assert empty_playlist.record + assert not empty_playlist.exists def test_entries(db): - pl = playlist.Playlist(slug='playlist-one', session=db) # assert twice for branch coverage of cached values + pl = playlist.Playlist.by_slug('playlist-one', db) assert pl.entries assert pl.entries def test_playlist_not_exist_formatted(db): - pl = playlist.Playlist(slug='fnord', session=db, create_ok=False) - assert not repr(pl) - assert not pl.as_dict - - -def test_playlist_formatted(db): - pl = playlist.Playlist(slug='playlist-one', session=db) + pl = playlist.Playlist(name='foo', slug='foo', session=db, create_ok=False) assert repr(pl) - assert pl.as_string assert pl.as_dict +def test_playlist_formatted(db, empty_playlist): + assert repr(empty_playlist) + assert empty_playlist.as_string + assert empty_playlist.as_dict