Реши задачу:
selects

Условие

Нужно реализовать класс, который инкапуслирует общение с БД. Потребуется релизовать три метода, которые запрашивают данные из базы В этой задаче необходимо использовать sqlalchemy !!!

Методы init, teardown даны для того, чтобы не приходилось для каждого теста открывать отдельное соединение, а также чтобы в конце закрыть его.

Описание того, что нужно вернуть, указано в докстринге к каждому из методов заглушки. В качестве базы используется тестовая база из туториала по sqlite3. Пдфку с ее схемой можно найти там же где и сам файл или в лекции

Вот шаблон:

# selects.py
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey, select
from sqlalchemy.orm import declarative_base, relationship, Session
from typing import List, Tuple
import logging

Base = declarative_base()

class Album(Base):
    __tablename__ = 'albums'
    AlbumId = Column(Integer, primary_key=True)
    Title = Column(String)
    ArtistId = Column(Integer, ForeignKey('artists.ArtistId'))

    artist = relationship("Artist", back_populates="albums")
    tracks = relationship("Track", back_populates="album")


class Artist(Base):
    __tablename__ = 'artists'
    ArtistId = Column(Integer, primary_key=True)
    Name = Column(String)

    albums = relationship("Album", back_populates="artist")


class Genre(Base):
    __tablename__ = 'genres'
    GenreId = Column(Integer, primary_key=True)
    Name = Column(String)

    tracks = relationship("Track", back_populates="genre")


class MediaType(Base):
    __tablename__ = 'media_types'
    MediaTypeId = Column(Integer, primary_key=True)
    Name = Column(String)

    tracks = relationship("Track", back_populates="media_type")


class Playlist(Base):
    __tablename__ = 'playlists'
    PlaylistId = Column(Integer, primary_key=True)
    Name = Column(String)

    playlist_tracks = relationship("PlaylistTrack", back_populates="playlist")


class PlaylistTrack(Base):
    __tablename__ = 'playlist_track'
    PlaylistId = Column(Integer, ForeignKey('playlists.PlaylistId'), primary_key=True)
    TrackId = Column(Integer, ForeignKey('tracks.TrackId'), primary_key=True)

    playlist = relationship("Playlist", back_populates="playlist_tracks")
    track = relationship("Track", back_populates="playlist_tracks")


class Track(Base):
    __tablename__ = 'tracks'
    TrackId = Column(Integer, primary_key=True)
    Name = Column(String)
    AlbumId = Column(Integer, ForeignKey('albums.AlbumId'))
    MediaTypeId = Column(Integer, ForeignKey('media_types.MediaTypeId'))
    GenreId = Column(Integer, ForeignKey('genres.GenreId'))
    Composer = Column(String)
    Milliseconds = Column(Integer)
    Bytes = Column(Integer)
    UnitPrice = Column(Float)

    album = relationship("Album", back_populates="tracks")
    media_type = relationship("MediaType", back_populates="tracks")
    genre = relationship("Genre", back_populates="tracks")
    playlist_tracks = relationship("PlaylistTrack", back_populates="track")


class DataBaseHandler:
    def __init__(self, db_path: str):
        """
        Инициализирует соединение с базой данных.
        :param db_path: Путь к файлу базы данных Chinook.
        """
        self.engine = create_engine(f"sqlite:///{db_path}")
        # Закомментируйте следующую строку, если таблицы уже существуют и содержат необходимые данные
        # Base.metadata.create_all(self.engine)
        self.session = Session(bind=self.engine)
        # Настройка логирования
        logging.basicConfig(level=logging.DEBUG)
        self.logger = logging.getLogger(__name__)

    def teardown(self):
        """
        Закрывает соединение с базой данных.
        """
        self.session.close()
        self.engine.dispose()

    def get_most_expensive_track_names(self, limit: int) -> List[Tuple[str]]:
        """
        Возвращает имена самых дорогих треков.
        :param limit: Количество треков для возврата.
        :return: Список кортежей с именами треков.
        """
        self.logger.debug(f"Fetching the top {limit} most expensive tracks.")
        stmt = (
            select(Track.Name)
            .order_by(Track.UnitPrice.desc())
            .limit(limit)
        )
        self.logger.debug(f"Executing Query: {stmt}")
        result = self.session.execute(stmt).scalars().all()
        self.logger.debug(f"Most expensive tracks found: {result}")
        return [(name,) for name in result]

    def get_tracks_of_given_genres(self, genres: List[str], number_of_tracks: int) -> List[Tuple[str]]:
        """
        Возвращает треки, соответствующие заданным жанрам.
        :param genres: Список жанров.
        :param number_of_tracks: Количество треков для возврата.
        :return: Список кортежей с именами треков.
        """
        normalized_genres = [genre.strip().title() for genre in genres]
        self.logger.debug(f"Fetching {number_of_tracks} tracks for genres: {normalized_genres}")
        stmt = (
            select(Track.Name)
            .join(Genre, Track.GenreId == Genre.GenreId)
            .where(Genre.Name.in_(normalized_genres))
            .order_by(Track.TrackId.asc())
            .limit(number_of_tracks)
        )
        self.logger.debug(f"Executing Query: {stmt}")
        result = self.session.execute(stmt).scalars().all()
        self.logger.debug(f"Tracks found: {result}")
        return [(name,) for name in result]

    def get_tracks_that_belong_to_playlist_found_by_name(self, name_needle: str) -> List[Tuple[str, str]]:
        """
        Возвращает треки, принадлежащие плейлистам, найденным по имени.
        :param name_needle: Часть имени плейлиста для поиска.
        :return: Список кортежей с именами треков и именами плейлистов.
        """
        like_pattern = f"%{name_needle}%"
        self.logger.debug(f"Fetching tracks for playlists with names like: {like_pattern}")
        stmt = (
            select(Track.Name, Playlist.Name)
            .join(PlaylistTrack, Track.TrackId == PlaylistTrack.TrackId)
            .join(Playlist, PlaylistTrack.PlaylistId == Playlist.PlaylistId)
            .where(Playlist.Name.like(like_pattern))
            .order_by(Track.TrackId.asc())
        )
        self.logger.debug(f"Executing Query: {stmt}")
        result = self.session.execute(stmt).all()
        self.logger.debug(f"Tracks in playlists found: {result}")
        return result

Вот структура таблицы:
media_types
MediaTypeld: INTEGER
Name: NVARCHAR(120)
genres
Genreld: INTEGER
Name: NVARCHAR(120)
playlists
Playlistid: INTEGER
Name: NVARCHAR{120)
playlist track
Playlistid: INTEGER
Trackid: INTEGER
tracks
Trackid: INTEGER
Name: NVARCHAR(200)
Albumid: INTEGER
MediaTypeld: INTEGER
Genreld: INTEGER
Composer: NVARCHAR(220)
Milliseconds: INTEGER
Bytes: INTEGER
UnitPrice: NUMERIC
artists
Artistid: INTEGER
Name: NVARCHAR(120)
invoices
Invoiceld: INTEGER
Customerid INTEGER
InvoiceDate: DATETIME
Billing Address: NVAR...
BillingCity: NVARCHA.
4 more columns...
invoice items
Invoiceltemid: INTEGER
Invoiceld: INTEGER
Trackid INTEGER
UnitPrice: NUMERIC
Quantity: INTEGER
albums
Albumid: INTEGER
Title: NVARCHAR(160)
Artistid: INTEGER
customers
Customerid: INTEGER
FirstName: NVARCHAR(40)
LastName: NVARCHAR(20)
Company: NVARCHAR(B0)
Address: NVARCHAR(70)
City: NVARCHAR(40)
State: NVARCHAR(40)
Country: NVARCHAR(40)
PostalCode: NVARCHAR(10)
Phone: NVARCHAR(24)
Fax: NVARCHAR(24)
Email: NVARCHAR(60)
SupportRepid: INTEGER
employees
Employeeld: INTEGER
LastName: NVARCHAR(20)
FirstName: NVARCHAR(20)
Title: NVARCHAR(30)
ReportsTo: INTEGER
BirthDate: DATETIME
HireDate: DATETIME
Address: NVARCHAR(70)
7 more columns..