Skip to content
Merged

Dev #14

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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# AudioStats
[![Coverage Status](https://coveralls.io/repos/github/Olezhich/AudioStats/badge.svg?branch=dev)](https://coveralls.io/github/Olezhich/AudioStats?branch=dev)
[![License](https://img.shields.io/github/license/Olezhich/AudioStats )](https://github.com/Olezhich/AudioStats/blob/main/LICENSE )
[![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://python.org)

Statistics Analyser for foobar2000
> Statistics Analyser for foobar2000

## Features
- Collecting album and track metadata into db
- Working with `flac` + `cue` music libraries

## Technologies
- `SqlAlchemy orm`
- `Asyncio`
6 changes: 4 additions & 2 deletions audiostats/db/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ def __init__(self, db_url : str):

async def _upsert_album(self, album : AlbumDTO):
async with self._session_factory as sf:
async with UnitOfWork(sf()) as uow:
unit_of_work = UnitOfWork(sf)
async with unit_of_work() as uow:
await uow.albums.upsert(album)

async def upsert_albums(self, albums : Iterator[AlbumDTO]):
Expand All @@ -31,6 +32,7 @@ async def upsert_albums(self, albums : Iterator[AlbumDTO]):

async def get_all_albums(self):
async with self._session_factory as sf:
async with UnitOfWork(sf()) as uow:
unit_of_work = UnitOfWork(sf)
async with unit_of_work() as uow:
return await uow.albums.all()

35 changes: 31 additions & 4 deletions audiostats/db/session.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
from asyncio import Semaphore
from contextlib import asynccontextmanager

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession


class SessionFactory:
def __init__(self, db_url : str):
self._engine = create_async_engine(url=db_url)
def __init__(self, db_url : str, max_sessions : int=20):
self._engine = create_async_engine(url=db_url, pool_size=max_sessions, max_overflow=0)
self._session_maker = async_sessionmaker(bind=self._engine)
self._semaphore = Semaphore(max_sessions)

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
await self._engine.dispose()

def __call__(self, *args, **kwargs) -> async_sessionmaker[AsyncSession]:
return self._session_maker
@asynccontextmanager
async def get_session(self) -> AsyncSession:
session = None
try:
await self._semaphore.acquire()
session = self._session_maker()
yield session
except Exception:
raise
finally:
if session:
await session.close()
self._semaphore.release()

async def __call__(self, *args, **kwargs) -> AsyncSession:
await self._semaphore.acquire()
session = self._session_maker()

async def close_session():
await session.close()
self._semaphore.release()

session.close = close_session()

return session
45 changes: 31 additions & 14 deletions audiostats/db/uow.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
from contextlib import asynccontextmanager

from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
import logging

from .repositories import AlbumRepository
from .session import SessionFactory

logger = logging.getLogger(__name__)

class UnitOfWork:
def __init__(self, session_factory: async_sessionmaker[AsyncSession]):
def __init__(self, session_factory: SessionFactory):
self._session_factory = session_factory
self._session : AsyncSession | None = None

self.albums : AlbumRepository | None = None
logger.debug(f'UoF initialized: {self}')

async def __aenter__(self):
self._session = self._session_factory()
self.albums = AlbumRepository(self._session)
logger.debug(f'AlbumRepo initialised: {self.albums}, Current Session: {self._session}')
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
await self._session.rollback()
else:
await self._session.commit()
await self._session.close()
logger.debug(f'Session closed')
@asynccontextmanager
async def __call__(self):
async with self._session_factory.get_session() as session:
self._session = session
self.albums = AlbumRepository(self._session)
try:
yield self
await self._session.commit()
except Exception:
await self._session.rollback()
raise

# async def __aenter__(self):
# async with self._session_factory as sf:
# await self._session = sf.get_session()
# self.albums = AlbumRepository(self._session)
# logger.debug(f'AlbumRepo initialised: {self.albums}, Current Session: {self._session}')
# return self
#
# async def __aexit__(self, exc_type, exc_val, exc_tb):
# if exc_type is not None:
# await self._session.rollback()
# else:
# await self._session.commit()
# await self._session.close()
# logger.debug(f'Session closed')