def create(name: str, conn: Connection) -> T: """ Create an artist and persist it to the database. :param name: The name of the artist. :param conn: A connection to the database. :return: The newly created artist. :raises Duplicate: If an artist with the same name already exists. The duplicate artist is passed as the ``entity`` argument. """ if art := from_name(name, conn): raise Duplicate(f'Artist "{name}" already exists.', art)
def create(path: Union[Path, str], conn: Connection) -> T: """ Create an image with the given path. :param path: The path of the image file. :param conn: A connection to the database. :return: The newly created image. :raises Duplicate: If an image with the given path already exists. The duplicate image is passed as the ``entity`` argument. """ if img := from_path(path, conn): raise Duplicate("An image with the given path already exists.", img)
def update(art: T, conn: Connection, **changes) -> T: """ Update an artist and persist changes to the database. To update a value, pass it in as a keyword argument. To keep the original value, do not pass in a keyword argument. :param art: The artist to update. :param conn: A connection to the database. :param name: New artist name. :type name: :py:obj:`str` :return: The updated artist. :raises Duplicate: If an artist already exists with the new name. """ if "name" in changes and (dupl := from_name(changes["name"], conn)) and dupl != art: raise Duplicate(f'Artist "{changes["name"]}" already exists.', dupl)
def update(ply: T, conn: Connection, **changes) -> T: """ Update a playlist and persist changes to the database. To update a value, pass it in as a keyword argument. To keep the original value, do not pass in a keyword argument. **Note: The type and user_id of a playlist cannot be changed.** :param ply: The playlist to update. :param conn: A connection to the database. :param name: New playlist name. :type name: :py:obj:`str` :return: The updated playlist. :raises Immutable: If the playlist cannot be updated. :raises Duplicate: If the new name conflicts with another playlist. """ if ply.type == PlaylistType.SYSTEM: raise Immutable("System playlists cannot be modified.") if ("name" in changes and (dupl := from_name_type_user( changes["name"], ply.type, conn, ply.user_id)) and dupl != ply): raise Duplicate(f'Playlist "{changes["name"]}" already exists.', dupl)
def create( name: str, type: PlaylistType, conn: Connection, user_id: Optional[int] = None, override_immutable: bool = False, ) -> T: """ Create a playlist and persist it to the database. :param name: The name of the playlist. :param type: The type of the playlist. :param conn: A connection to the database. :param user_id: The ID of the user that this playlist belongs to. Should be set for Personal and System playlists; unset otherwise. :param override_immutable: Whether to allow creation of immutable playlists. For internal use. :return: The newly created playlist. :raises Duplicate: If an playlist with the same name and type already exists. The duplicate playlist is passed as the ``entity`` argument. :raises InvalidArgument: If the user_id argument is passed with a non-personal playlist type. """ if type == PlaylistType.SYSTEM and not override_immutable: raise InvalidPlaylistType("Cannot create system playlists.") if type in [PlaylistType.PERSONAL, PlaylistType.SYSTEM ] and user_id is None: raise InvalidArgument( "Missing user_id argument for personal/system collection.") if type not in [PlaylistType.PERSONAL, PlaylistType.SYSTEM ] and user_id is not None: raise InvalidArgument( "The user_id argument can only be set for personal/system collections." ) if ply := from_name_type_user(name, type, conn, user_id): raise Duplicate(f'Playlist "{name}" already exists.', ply)
def update(col: T, conn: Connection, **changes) -> T: """ Update a collection and persist changes to the database. To update a value, pass it in as a keyword argument. To keep the original value, do not pass in a keyword argument. **Note: The type of a collection cannot be changed.** :param col: The collection to update. :param conn: A connection to the database. :param name: New collection name. :type name: :py:obj:`str` :return: The updated collection. :raises Immutable: If the collection cannot be updated. :raises Duplicate: If the new name conflicts with another collection. """ if col.type == CollectionType.SYSTEM: raise Immutable("System collections cannot be modified.") if ("name" in changes and (dupl := from_name_type_user( changes["name"], col.type, conn, col.user_id)) and dupl != col): raise Duplicate(f'Collection "{changes["name"]}" already exists.', dupl)
def calculate_track_full_sha256(trk: T, conn: Connection) -> bytes: """ Given a track, calculate its full SHA256. If the newly calculated SHA256 is equivalent to an existing track's SHA256, delete the passed-in track and raise a Duplicate error with the existing track. :param trk: The track. :param conn: A connection to the DB. :return: The calculated SHA256. :raises FileNotFoundError: If the track no longer exists. :raises Duplicate: If the calculated sha256 is the same as an existing track. The existing track is attached to the error. """ logger.debug(f"Calculating SHA256 for {trk.filepath}.") sha256sum = calculate_sha256(trk.filepath) # The newly calculated sha256 is a duplicate of another track... # To deduplicate, delete the new track. if dup := from_sha256(sha256sum, conn): logger.info( f"Track {trk.id} is a hash-duplicate of {dup.id}. Deleting {trk.id}." ) delete(trk, conn) raise Duplicate("Duplicate SHA256 detected.", dup)
:raises Duplicate: If a release with the same name and artists already exists. The duplicate release is passed as the ``entity`` argument. """ if bad_ids := [ d["artist_id"] for d in artists if not artist.exists(d["artist_id"], conn) ]: logger.debug( f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.") raise NotFound( f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.") if not allow_duplicate and (rls := _find_duplicate_release( title, artists, conn)): logger.debug(f"Release already exists with ID {rls.id}.") raise Duplicate( "A release with the same name and artists already exists.", rls) # Insert the release into the database. cursor = conn.execute( """ INSERT INTO music__releases ( title, image_id, release_type, release_year, release_date, rating ) VALUES (?, ?, ?, ?, ?, ?) """, (title, image_id, release_type.value, release_year, release_date, rating), ) id_ = cursor.lastrowid # Insert the release artists into the database. for mapping in artists:
logger.debug(f"Release {release_id} does not exist.") raise NotFound(f"Release {release_id} does not exist.") if bad_ids := [ d["artist_id"] for d in artists if not artist.exists(d["artist_id"], conn) ]: logger.debug( f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.") raise NotFound( f"Artist(s) {', '.join(str(i) for i in bad_ids)} do not exist.") # First, check to see if a track with the same filepath exists. if trk := from_filepath(filepath, conn): logger.debug("A track with this filepath already exists.") raise Duplicate("A track with this filepath already exists.", trk) # Next, check to see if a track with the same sha256 exists. if trk := _check_for_duplicate_sha256(sha256_initial, filepath, conn): return trk # Track is not a duplicate, so we can insert and return. cursor = conn.execute( """ INSERT INTO music__tracks ( title, filepath, sha256_initial, release_id, track_number, disc_number,