Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ jobs:
run: |
poetry run alembic upgrade head

- name: Run Ruff formatter check
run: |
poetry run ruff format . --check

- name: Run Ruff linter check
run: |
poetry run ruff check .
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,6 @@ __marimo__/
#IDE
.idea

postgres_container.test.yml
postgres_container.test.yml

Makefile
5 changes: 3 additions & 2 deletions audiostats/app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import tomlcfg
import handlers


class App(tomlcfg.BaseModel):
def __init__(self):
super().__init__('./config/config.toml')
self._PlayListHandler = handlers.PlayListHandler(self._config['PlayList'])

def update_playlist(self, playlist : list[str]):
def update_playlist(self, playlist: list[str]):
"""called when updating a playlist to enter data about new objects in the music library into db"""
#do smth business logic
# do smth business logic
77 changes: 50 additions & 27 deletions audiostats/application/dto_mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,72 @@

from audiostats.handlers.models import AlbumDTO, TrackDTO, StatusDTO

def update_track_orm_f_dto(old : Track, new : TrackDTO) -> None:

def update_track_orm_f_dto(old: Track, new: TrackDTO) -> None:
old.title = new.title
old.number = new.number
old.path = new.path
old.offset = new.offset
old.duration = new.duration

def create_track_orm_f_dto(track : TrackDTO) -> Track:

def create_track_orm_f_dto(track: TrackDTO) -> Track:
created = Track()
update_track_orm_f_dto(created, track)
return created

def update_album_orm_meta_f_dto(old : Album, new : AlbumDTO):

def update_album_orm_meta_f_dto(old: Album, new: AlbumDTO):
old.title = new.title
old.performer = new.performer
old.year = new.year
old.path = new.path
old.cover = new.cover

def create_album_dto_f_orm(album : Album):
return AlbumDTO(title=album.title,
performer=album.performer,
year=album.year,
path=album.path,
cover=album.cover,
tracks=[TrackDTO(title=track.title,
number=track.number,
path=track.path,
offset=track.offset,
duration=track.duration) for track in album.tracks],
statuses=[StatusDTO(status=status.status,
success=status.success) for status in album.album_statuses])

def diff_album_meta(old : Album, new : AlbumDTO) -> bool:
return any([old.title != new.title,

def create_album_dto_f_orm(album: Album):
return AlbumDTO(
title=album.title,
performer=album.performer,
year=album.year,
path=album.path,
cover=album.cover,
tracks=[
TrackDTO(
title=track.title,
number=track.number,
path=track.path,
offset=track.offset,
duration=track.duration,
)
for track in album.tracks
],
statuses=[
StatusDTO(status=status.status, success=status.success)
for status in album.album_statuses
],
)


def diff_album_meta(old: Album, new: AlbumDTO) -> bool:
return any(
[
old.title != new.title,
old.performer != new.performer,
old.year != new.year,
old.path != new.path,
old.cover != new.cover])

def diff_track(old : Track, new : TrackDTO) -> bool:
return any([old.title != new.title,
old.number != new.number,
old.path != new.path,
old.offset != new.offset,
old.duration != new.duration])
old.cover != new.cover,
]
)


def diff_track(old: Track, new: TrackDTO) -> bool:
return any(
[
old.title != new.title,
old.number != new.number,
old.path != new.path,
old.offset != new.offset,
old.duration != new.duration,
]
)
11 changes: 7 additions & 4 deletions audiostats/db/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

logger = logging.getLogger(__name__)


class DBApi:
def __init__(self, db_url : str, workers : int = 5, queue_sz : int = 10):
def __init__(self, db_url: str, workers: int = 5, queue_sz: int = 10):
self._session_factory = SessionFactory(db_url)
self._queue: asyncio.Queue[AlbumDTO | None] = asyncio.Queue(maxsize=queue_sz)
self._num_workers = workers
Expand All @@ -29,8 +30,11 @@ async def _album_upserter(self):
finally:
self._queue.task_done()

async def upsert_albums(self, albums : Iterator[AlbumDTO]):
workers = [asyncio.create_task(self._album_upserter()) for _ in range(self._num_workers)]
async def upsert_albums(self, albums: Iterator[AlbumDTO]):
workers = [
asyncio.create_task(self._album_upserter())
for _ in range(self._num_workers)
]

for album in albums:
await self._queue.put(album)
Expand All @@ -53,4 +57,3 @@ async def get_all_albums_w_status(self):
unit_of_work = UnitOfWork(sf)
async with unit_of_work() as uow:
return await uow.albums.all_w_status()

86 changes: 56 additions & 30 deletions audiostats/db/models.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
from datetime import datetime

from sqlalchemy import String, Integer, UniqueConstraint, ForeignKey, Float, Enum, DateTime, func
from sqlalchemy import (
String,
Integer,
UniqueConstraint,
ForeignKey,
Float,
Enum,
DateTime,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase

from audiostats.domain.enums import Status, Success

MAX_PATH_FIELD_LEN = 200
MAX_STR_FIELD_LEN = 50

class Base(DeclarativeBase):
...

class Base(DeclarativeBase): ...


class Album(Base):
"""Represents **albums** table line as orm object
Expand All @@ -31,21 +41,30 @@ class Album(Base):
UniqueConstraint('performer', 'title', name='uq_album_performer_title'),
)

id : Mapped[int] = mapped_column(Integer, primary_key=True)
title : Mapped[str] = mapped_column(String(MAX_STR_FIELD_LEN), nullable=False)
performer : Mapped[str | None] = mapped_column(String(MAX_STR_FIELD_LEN), nullable=True)
year : Mapped[int | None] = mapped_column(Integer, nullable=True)
path : Mapped[str | None] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
cover : Mapped[str | None] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
tracks : Mapped[list['Track']] = relationship('Track', back_populates='album', lazy='noload')
album_statuses : Mapped[list['AlbumStatus']] = relationship('AlbumStatus', back_populates='album', lazy='noload')
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String(MAX_STR_FIELD_LEN), nullable=False)
performer: Mapped[str | None] = mapped_column(
String(MAX_STR_FIELD_LEN), nullable=True
)
year: Mapped[int | None] = mapped_column(Integer, nullable=True)
path: Mapped[str | None] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
cover: Mapped[str | None] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
tracks: Mapped[list['Track']] = relationship(
'Track', back_populates='album', lazy='noload'
)
album_statuses: Mapped[list['AlbumStatus']] = relationship(
'AlbumStatus', back_populates='album', lazy='noload'
)

def __repr__(self):
return f'<Album(year={self.year}, performer={self.performer}, title={self.title})>'
return (
f'<Album(year={self.year}, performer={self.performer}, title={self.title})>'
)

def __str__(self):
return f'{self.year} - {self.performer} - {self.title}'


class Track(Base):
"""Represents **tracks** table line as orm object

Expand All @@ -58,16 +77,21 @@ class Track(Base):
:ivar duration: Track duration `(in seconds)`
:ivar album: Relationship to the parent album
"""

__tablename__ = 'tracks'

id : Mapped[int] = mapped_column(Integer, primary_key=True)
title : Mapped[str] = mapped_column(String(MAX_STR_FIELD_LEN), nullable=False)
album_id : Mapped[int] = mapped_column(Integer, ForeignKey('albums.id', ondelete='CASCADE'), index=True)
number : Mapped[int | None] = mapped_column(Integer, nullable=True)
path : Mapped[str] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
offset : Mapped[float | None] = mapped_column(Float, nullable=True)
duration : Mapped[float | None] = mapped_column(Float, nullable=True)
album : Mapped["Album"]= relationship('Album', back_populates='tracks', lazy='noload')
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String(MAX_STR_FIELD_LEN), nullable=False)
album_id: Mapped[int] = mapped_column(
Integer, ForeignKey('albums.id', ondelete='CASCADE'), index=True
)
number: Mapped[int | None] = mapped_column(Integer, nullable=True)
path: Mapped[str] = mapped_column(String(MAX_PATH_FIELD_LEN), nullable=True)
offset: Mapped[float | None] = mapped_column(Float, nullable=True)
duration: Mapped[float | None] = mapped_column(Float, nullable=True)
album: Mapped['Album'] = relationship(
'Album', back_populates='tracks', lazy='noload'
)

def __repr__(self):
return f'<Track(title={self.title}, album_id={self.album_id}, number={self.number})>'
Expand All @@ -89,13 +113,15 @@ class AlbumStatus(Base):

__tablename__ = 'album_statuses'

id : Mapped[int] = mapped_column(Integer, primary_key=True)
album_id : Mapped[int] = mapped_column(Integer, ForeignKey('albums.id', ondelete='CASCADE'), index=True)
time_stamp : Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=func.now(), default=func.now())
status : Mapped[Status] = mapped_column(Enum(Status), nullable=False)
success : Mapped[Success] = mapped_column(Enum(Success), nullable=False)
album : Mapped["Album"] = relationship('Album', back_populates='album_statuses', lazy='noload')




id: Mapped[int] = mapped_column(Integer, primary_key=True)
album_id: Mapped[int] = mapped_column(
Integer, ForeignKey('albums.id', ondelete='CASCADE'), index=True
)
time_stamp: Mapped[datetime] = mapped_column(
DateTime, nullable=False, server_default=func.now(), default=func.now()
)
status: Mapped[Status] = mapped_column(Enum(Status), nullable=False)
success: Mapped[Success] = mapped_column(Enum(Success), nullable=False)
album: Mapped['Album'] = relationship(
'Album', back_populates='album_statuses', lazy='noload'
)
52 changes: 34 additions & 18 deletions audiostats/db/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,34 @@
from .models import Album, AlbumStatus
from audiostats.handlers.models import AlbumDTO

from audiostats.application.dto_mappers import create_album_dto_f_orm, update_album_orm_meta_f_dto, update_track_orm_f_dto, create_track_orm_f_dto, diff_track, diff_album_meta
from audiostats.application.dto_mappers import (
create_album_dto_f_orm,
update_album_orm_meta_f_dto,
update_track_orm_f_dto,
create_track_orm_f_dto,
diff_track,
diff_album_meta,
)


logger = logging.getLogger(__name__)


class AlbumRepository:
def __init__(self, session : AsyncSession):
def __init__(self, session: AsyncSession):
self._session = session

async def upsert(self, album_data : AlbumDTO):
album = await self.find_by_title_performer(album_data.title, album_data.performer)
async def upsert(self, album_data: AlbumDTO):
album = await self.find_by_title_performer(
album_data.title, album_data.performer
)
album_status = None

if not album: #if new album
if not album: # if new album
album = Album()
album_status = Status.ADDED
self._session.add(album)
elif diff_album_meta(album, album_data): #if album meta modified
elif diff_album_meta(album, album_data): # if album meta modified
album_status = Status.MODIFIED

update_album_orm_meta_f_dto(album, album_data)
Expand All @@ -49,27 +59,33 @@ async def upsert(self, album_data : AlbumDTO):
await self._session.delete(track)

if album_status:
status = AlbumStatus(album=album, status=album_status, success=Success.SUCCESS)
status = AlbumStatus(
album=album, status=album_status, success=Success.SUCCESS
)
self._session.add(status)

logger.info(f'Album upserted: {album_data}')

async def find_by_title_performer(self, title : str, performer : str | None) -> Album | None:
async def find_by_title_performer(
self, title: str, performer: str | None
) -> Album | None:
result = await self._session.execute(
select(Album).where(
Album.title == title, Album.performer == performer).options(
selectinload(Album.tracks)
))
select(Album)
.where(Album.title == title, Album.performer == performer)
.options(selectinload(Album.tracks))
)
return result.scalar_one_or_none()

async def all(self) -> list[Album]:
result = await self._session.scalars(select(Album).options(
selectinload(Album.tracks)
))
result = await self._session.scalars(
select(Album).options(selectinload(Album.tracks))
)
return [create_album_dto_f_orm(album) for album in result.all()]

async def all_w_status(self) -> list[Album]:
result = await self._session.scalars(select(Album).options(
selectinload(Album.tracks)
).options(selectinload(Album.album_statuses)))
result = await self._session.scalars(
select(Album)
.options(selectinload(Album.tracks))
.options(selectinload(Album.album_statuses))
)
return [create_album_dto_f_orm(album) for album in result.all()]
Loading