From c0edbd9bff916b41f3de04b249b21a66ef44ffa1 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Wed, 31 Aug 2022 15:14:47 -0400 Subject: [PATCH] db: Add a Year Table This table allows us to work with Year playlists that are represented only by the year. Signed-off-by: Anna Schumaker --- emmental/db/__init__.py | 4 +- emmental/db/emmental.sql | 31 +++++++++ emmental/db/years.py | 54 ++++++++++++++++ tests/db/test_db.py | 5 +- tests/db/test_years.py | 133 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 emmental/db/years.py create mode 100644 tests/db/test_years.py diff --git a/emmental/db/__init__.py b/emmental/db/__init__.py index 9f9a25e..95cb68d 100644 --- a/emmental/db/__init__.py +++ b/emmental/db/__init__.py @@ -13,6 +13,7 @@ from . import media from . import playlists from . import settings from . import table +from . import years SQL_SCRIPT = pathlib.Path(__file__).parent / "emmental.sql" @@ -39,6 +40,7 @@ class Connection(connection.Connection): self.media = media.Table(self, queue=self.artists.queue) self.genres = genres.Table(self) self.decades = decades.Table(self) + self.years = years.Table(self, queue=self.decades.queue) def close(self) -> None: """Close the database connection.""" @@ -62,7 +64,7 @@ class Connection(connection.Connection): def playlist_tables(self) -> Generator[playlist.Table, None, None]: """Iterate over each playlist table.""" for tbl in [self.playlists, self.artists, self.albums, self.media, - self.genres, self.decades]: + self.genres, self.decades, self.years]: yield tbl def set_active_playlist(self, plist: playlist.Playlist) -> None: diff --git a/emmental/db/emmental.sql b/emmental/db/emmental.sql index d6d0a75..9fafea8 100644 --- a/emmental/db/emmental.sql +++ b/emmental/db/emmental.sql @@ -274,6 +274,37 @@ CREATE TRIGGER decades_delete_trigger AFTER DELETE ON decades END; +/*********************** + * * + * Years * + * * + ***********************/ + +CREATE TABLE years ( + year INTEGER PRIMARY KEY, + propertyid INTEGER REFERENCES playlist_properties (propertyid) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE VIEW years_view AS + SELECT year, propertyid, FORMAT("%s", year) as name, active + FROM years + JOIN playlist_properties USING (propertyid); + +CREATE TRIGGER years_insert_trigger AFTER INSERT ON years + BEGIN + INSERT INTO playlist_properties (active) VALUES (False); + UPDATE years SET propertyid = last_insert_rowid() + WHERE year = NEW.year; + END; + +CREATE TRIGGER years_delete_trigger AFTER DELETE ON years + BEGIN + DELETE FROM playlist_properties WHERE propertyid = OLD.propertyid; + END; + + /****************************************** * * * Create Default Playlists * diff --git a/emmental/db/years.py b/emmental/db/years.py new file mode 100644 index 0000000..d64e6ca --- /dev/null +++ b/emmental/db/years.py @@ -0,0 +1,54 @@ +# Copyright 2022 (c) Anna Schumaker. +"""A custom Gio.ListModel for managing individual years.""" +import sqlite3 +from gi.repository import GObject +from . import playlist + + +class Year(playlist.Playlist): + """Our custom Year object.""" + + year = GObject.Property(type=int) + + @property + def primary_key(self) -> int: + """Get this year's primary key.""" + return self.year + + @GObject.Property(type=playlist.Playlist) + def parent(self) -> playlist.Playlist | None: + """Get this Year's parent playlist.""" + return self.table.sql.decades.lookup(self.year) + + +class Table(playlist.Table): + """Our Year Table.""" + + def do_construct(self, **kwargs) -> Year: + """Construct a new Year playlist.""" + return Year(**kwargs) + + def do_get_sort_key(self, year: Year) -> tuple: + """Get the sort key for a specific year.""" + return year.year + + def do_sql_delete(self, year: Year) -> sqlite3.Cursor: + """Delete a year.""" + return self.sql("DELETE FROM years WHERE year=?", year.year) + + def do_sql_glob(self, glob: str) -> sqlite3.Cursor: + """Search for years matching the search text.""" + return self.sql("SELECT year FROM years_view WHERE name GLOB ?", glob) + + def do_sql_insert(self, year: int) -> sqlite3.Cursor | None: + """Create a new Year playlist.""" + if self.sql("INSERT INTO years (year) VALUES (?)", year): + return self.sql("SELECT * FROM years_view WHERE year=?", year) + + def do_sql_select_all(self) -> sqlite3.Cursor: + """Load Years from the database.""" + return self.sql("SELECT * FROM years_view") + + def do_sql_select_one(self, year: int) -> sqlite3.Cursor: + """Look up a year.""" + return self.sql("SELECT year FROM years WHERE year=?", year) diff --git a/tests/db/test_db.py b/tests/db/test_db.py index ed2615c..5b702c3 100644 --- a/tests/db/test_db.py +++ b/tests/db/test_db.py @@ -46,14 +46,17 @@ class TestConnection(tests.util.TestCase): self.assertIsInstance(self.sql.media, emmental.db.media.Table) self.assertIsInstance(self.sql.genres, emmental.db.genres.Table) self.assertIsInstance(self.sql.decades, emmental.db.decades.Table) + self.assertIsInstance(self.sql.years, emmental.db.years.Table) self.assertEqual(self.sql.albums.queue, self.sql.artists.queue) self.assertEqual(self.sql.media.queue, self.sql.artists.queue) + self.assertEqual(self.sql.years.queue, self.sql.decades.queue) self.assertListEqual([tbl for tbl in self.sql.playlist_tables()], [self.sql.playlists, self.sql.artists, self.sql.albums, self.sql.media, - self.sql.genres, self.sql.decades]) + self.sql.genres, self.sql.decades, + self.sql.years]) def test_load(self): """Check that calling load() loads the tables.""" diff --git a/tests/db/test_years.py b/tests/db/test_years.py new file mode 100644 index 0000000..cfbac6a --- /dev/null +++ b/tests/db/test_years.py @@ -0,0 +1,133 @@ +# Copyright 2022 (c) Anna Schumaker. +"""Tests our year Gio.ListModel.""" +import emmental.db +import tests.util + + +class TestYearObject(tests.util.TestCase): + """Tests our year object.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.table = self.sql.years + self.year = emmental.db.years.Year(table=self.table, propertyid=123, + year=2023, name="2023") + + def test_init(self): + """Test that the Year is set up properly.""" + self.assertIsInstance(self.year, emmental.db.playlist.Playlist) + self.assertEqual(self.year.table, self.table) + self.assertEqual(self.year.propertyid, 123) + self.assertEqual(self.year.year, 2023) + self.assertEqual(self.year.primary_key, 2023) + self.assertEqual(self.year.name, "2023") + + def test_parent(self): + """Test finding the parent Decade for this Year.""" + self.assertIsNone(self.year.parent) + decade = self.sql.decades.create(2020) + self.assertEqual(self.year.parent, decade) + + +class TestYearTable(tests.util.TestCase): + """Tests our year table.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.table = self.sql.years + + def test_init(self): + """Test that the year model is configured correctly.""" + self.assertIsInstance(self.table, emmental.db.playlist.Table) + self.assertEqual(len(self.table), 0) + + def test_construct(self): + """Test constructing a year playlist.""" + year = self.table.construct(propertyid=1988, year=1988, name="1988") + self.assertIsInstance(year, emmental.db.years.Year) + self.assertEqual(year.table, self.table) + self.assertEqual(year.propertyid, 1988) + self.assertEqual(year.year, 1988) + self.assertEqual(year.name, "1988") + self.assertFalse(year.active) + + def test_create(self): + """Test creating a year playlist.""" + year = self.table.create(1988) + self.assertIsInstance(year, emmental.db.years.Year) + self.assertEqual(year.year, 1988) + self.assertEqual(year.name, "1988") + + cur = self.sql("SELECT COUNT(year) FROM years") + self.assertEqual(cur.fetchone()["COUNT(year)"], 1) + + row = self.sql("""SELECT COUNT(*) FROM playlist_properties + WHERE propertyid=?""", year.propertyid).fetchone() + self.assertEqual(row["COUNT(*)"], 1) + + self.assertIsNone(self.table.create(1988)) + + def test_delete(self): + """Test deleting a year playlist.""" + year = self.table.create(1988) + self.assertTrue(year.delete()) + + cur = self.sql("SELECT COUNT(year) FROM years") + self.assertEqual(cur.fetchone()["COUNT(year)"], 0) + self.assertEqual(len(self.table), 0) + self.assertIsNone(self.table.get_item(0)) + + row = self.sql("""SELECT COUNT(*) FROM playlist_properties + WHERE propertyid=?""", year.propertyid).fetchone() + self.assertEqual(row["COUNT(*)"], 0) + + self.assertFalse(year.delete()) + + def test_filter(self): + """Test filtering a year playlist.""" + self.table.create(1985) + self.table.create(1988) + + self.table.filter("*5", now=True) + self.assertSetEqual(self.table.get_filter().keys, {1985}) + self.table.filter("19*", now=True) + self.assertSetEqual(self.table.get_filter().keys, {1985, 1988}) + + def test_get_sort_key(self): + """Test getting a year playlist's sort key.""" + year = self.table.create(1988) + self.assertEqual(self.table.get_sort_key(year), 1988) + + def test_load(self): + """Test loading the year table from the database.""" + self.table.create(1985) + self.table.create(1988) + + years2 = emmental.db.years.Table(self.sql) + self.assertEqual(len(years2), 0) + + years2.load(now=True) + self.assertEqual(len(years2), 2) + + self.assertEqual(years2.get_item(0).year, 1985) + self.assertEqual(years2.get_item(0).name, "1985") + + self.assertEqual(years2.get_item(1).year, 1988) + self.assertEqual(years2.get_item(1).name, "1988") + + def test_lookup(self): + """Test looking up year playlists.""" + year = self.table.create(1988) + self.assertEqual(self.table.lookup(1988), year) + self.assertIsNone(self.table.lookup(1985)) + + def test_update(self): + """Test updating year attributes.""" + year = self.table.create(1980) + year.active = True + + row = self.sql("""SELECT active FROM playlist_properties + WHERE propertyid=?""", year.propertyid).fetchone() + self.assertEqual(row["active"], True)