async def test_del_artist_from_release_bad_artist(db, graphql_query, snapshot): query = """ mutation { delArtistFromRelease(releaseId: 2, artistId: 9999, role: MAIN) { release { ...ReleaseFields } artist { ...ArtistFields } } } """ rls = release.from_id(2, db) assert rls is not None before_artists = release.artists(rls, db) success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) after_artists = release.artists(rls, db) assert before_artists == after_artists
async def test_create_release(db, graphql_query, snapshot): query = """ mutation { createRelease( title: "NewRelease" artists: [ { artist_id: 2, role: MAIN, }, { artist_id: 3, role: MAIN, }, ] releaseType: ALBUM releaseYear: 2020 releaseDate: "2020-10-23" ) { ...ReleaseFields } } """ success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) rls = release.from_id(data["data"]["createRelease"]["id"], db) assert rls is not None assert rls.title == "NewRelease" assert rls.release_type == ReleaseType.ALBUM assert rls.release_year == 2020 assert rls.release_date == date(2020, 10, 23) assert {2, 3} == {a["artist"].id for a in release.artists(rls, db)}
def test_fix_album_artists_track_artists(factory: Factory, db: Connection): rls = factory.release(artists=[], conn=db) art1 = factory.artist(conn=db) art2 = factory.artist(conn=db) factory.track( release_id=rls.id, artists=[ { "artist_id": art1.id, "role": ArtistRole.MAIN }, { "artist_id": art2.id, "role": ArtistRole.FEATURE }, ], conn=db, ) _fix_album_artists(db) rls = release.from_id(rls.id, db) # type: ignore assert rls is not None album_artists = release.artists(rls, db) assert len(album_artists) == 1 assert album_artists[0]["artist"].id == art1.id
async def test_update_release(db, graphql_query, snapshot): query = """ mutation { updateRelease( id: 2 title: "aa" releaseType: SINGLE releaseYear: 2020 releaseDate: "2020-10-23" rating: 1 ) { ...ReleaseFields } } """ success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) rls = release.from_id(2, db) assert rls.title == "aa" assert rls.release_type == ReleaseType.SINGLE assert rls.release_year == 2020 assert rls.release_date == date(2020, 10, 23) assert rls.rating == 1
def _fix_album_artists(conn: Connection) -> None: """ Because not all albums contain the album artist tag, we look at all releases with no album artists and either assign them their track artists or assign them to the Unknown Artist. If the album's tracks have artists, then we conditionally assign the album artist based off the track's artists. See the code for the specific logic. :param conn: A connection to the database. """ logger.info("Fixing album artists...") cursor = conn.execute(""" SELECT rls.id FROM music__releases AS rls WHERE NOT EXISTS( SELECT 1 FROM music__releases_artists WHERE release_id = rls.id ) """) release_ids = [row[0] for row in cursor] for rid in release_ids: rls = release.from_id(rid, conn) assert rls is not None tracks = release.tracks(rls, conn) amaps = chain.from_iterable(track.artists(trk, conn) for trk in tracks) artists = { amap["artist"] for amap in amaps if amap["role"] in MAIN_ROLES } for art in artists: release.add_artist(rls, art.id, ArtistRole.MAIN, conn)
def test_fetch_or_create_release_no_fetch(db: Connection): assert release.from_id(3, db) != _fetch_or_create_release( mock.Mock( album="Departure", artist_album=["Bacchus"], date=mock.Mock(year=2020, date="2020-01-01"), label=None, genre=[], ), db, )
def test_fetch_or_create_release_fetch(factory: Factory, db: Connection): rls = factory.release(conn=db) assert release.from_id(rls.id, db) == _fetch_or_create_release( tf=mock.Mock( album=rls.title, artist_album=[ art["artist"].name for art in release.artists(rls, conn=db) ], ), conn=db, )
def resolve_del_release_from_collection( _, info: GraphQLResolveInfo, collectionId: int, releaseId: int, ) -> dict: col = collection.from_id(collectionId, info.context.db) if not col: raise NotFound(f"Collection {collectionId} does not exist.") col = collection.del_release(col, releaseId, info.context.db) rls = release.from_id(releaseId, info.context.db) return {"collection": col, "release": rls}
def resolve_del_artist_from_release( _, info: GraphQLResolveInfo, releaseId: int, artistId: int, role: ArtistRole, ) -> dict: rls = release.from_id(releaseId, info.context.db) if not rls: raise NotFound(f"Release {releaseId} does not exist.") rls = release.del_artist(rls, artistId, role, info.context.db) art = artist.from_id(artistId, info.context.db) return {"release": rls, "artist": art}
async def test_update_release_bad_date(db, graphql_query, snapshot): query = """ mutation { updateRelease( id: 2 releaseDate: "bbbbb" ) { ...ReleaseFields } } """ success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) rls = release.from_id(2, db) assert rls.release_date == date(1970, 2, 5)
def resolve_update_release( _, info: GraphQLResolveInfo, id: int, **changes, ) -> release.T: rls = release.from_id(id, info.context.db) if not rls: raise NotFound(f"Release {id} does not exist.") # Convert the "releaseDate" update from a string to a `date` object. If it is not in # the changes dict, do nothing. try: changes["releaseDate"] = date.fromisoformat(changes["releaseDate"]) except ValueError: raise ParseError("Invalid release date.") except KeyError: pass return release.update(rls, info.context.db, **convert_keys_case(changes))
async def test_add_artist_to_release(db, graphql_query, snapshot): query = """ mutation { addArtistToRelease(releaseId: 2, artistId: 5, role: MAIN) { release { ...ReleaseFields } artist { ...ArtistFields } } } """ success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) rls = release.from_id(2, db) assert rls is not None assert 5 in [a["artist"].id for a in release.artists(rls, db)]
def test_fix_release_types(factory: Factory, db: Connection, num_tracks, release_type): rls = factory.release(release_type=ReleaseType.UNKNOWN, conn=db) for i in range(num_tracks): factory.track( title="a", filepath=Path(f"/lol{i}.flac"), sha256=bytes([i] * 32), release_id=rls.id, artists=[], duration=4, track_number="1", disc_number="1", conn=db, ) _fix_release_types(db) new_rls = release.from_id(rls.id, db) assert new_rls is not None assert new_rls.release_type == release_type
async def test_del_artist_from_release(db, graphql_query, snapshot): query = """ mutation { delArtistFromRelease(releaseId: 2, artistId: 2, role: MAIN) { release { ...ReleaseFields } artist { ...ArtistFields } } } """ rls = release.from_id(2, db) assert rls is not None assert 2 in [a["artist"].id for a in release.artists(rls, db)] success, data = await graphql_query(query) assert success is True snapshot.assert_match(data) assert 2 not in [a["artist"].id for a in release.artists(rls, db)]
def _fetch_or_create_release(tf: TagFile, conn: Connection) -> release.T: """ Try to match the album and album artist fields of the tagfile against the database. If a matching release is found, return it. Otherwise, create and return a new release. If the track has no album, return the Unknown Release (ID: 1). If a new release is created, add it to the inbox collection and relevant label/genre collections. We also flag the release for cover art extraction in the future (not as scary as it sounds!). :param tf: The track whose release we want to fetch. :param conn: A connection to the database. :return: The release the track belongs to. """ if not tf.album: logger.debug(f"Fetched `Unknown Release` for track `{tf.path}`.") rls = release.from_id(1, conn) assert rls is not None return rls release_date: Optional[date] = None try: release_date = date.fromisoformat(tf.date.date) except (TypeError, ValueError): pass artist_ids = uniq_list( _fetch_or_create_artist(art, conn).id for art in tf.artist_album) artists = [{ "artist_id": aid, "role": ArtistRole.MAIN } for aid in artist_ids] # Try to create a release with the given title and album artists. If it raises a # duplicate error, return the duplicate entity. try: rls = release.create( title=tf.album, # The tags might contain duplicate artists.. artists=artists, release_type=_get_release_type(tf), release_year=tf.date.year, release_date=release_date, conn=conn, allow_duplicate=False, ) except Duplicate as e: logger.debug( f"Return existing release {e.entity.id} for track `{tf.path}`.") return e.entity logger.debug(f"Created new release {rls.id} for track `{tf.path}`.") # Add release to the inbox and its label/genres. _insert_into_inbox_collections(rls, conn) _insert_into_label_collection(rls, tf.label, conn) _insert_into_genre_collections(rls, tf.genre, conn) # Flag the release to have its cover art extracted and stored. conn.execute( "INSERT INTO music__releases_images_to_fetch (release_id) VALUES (?)", (rls.id, ), ) return rls
def resolve_release(_: Any, info: GraphQLResolveInfo, id: int) -> release.T: if rls := release.from_id(id, info.context.db): return rls
def test_fetch_or_create_release_unknown(db: Connection): assert release.from_id(1, db) == _fetch_or_create_release( mock.Mock(album=None), db)