From 8577935be3580ada648016da529008d297302923 Mon Sep 17 00:00:00 2001 From: Olezhich Date: Tue, 16 Sep 2025 18:56:36 +0300 Subject: [PATCH] readme update --- README.md | 12 ++++++++++- audiostats/db/api.py | 6 ++++-- audiostats/db/session.py | 35 +++++++++++++++++++++++++++---- audiostats/db/uow.py | 45 +++++++++++++++++++++++++++------------- 4 files changed, 77 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index a17a02c..11383e1 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/audiostats/db/api.py b/audiostats/db/api.py index f30637d..90d929b 100644 --- a/audiostats/db/api.py +++ b/audiostats/db/api.py @@ -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]): @@ -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() diff --git a/audiostats/db/session.py b/audiostats/db/session.py index 4fb838f..2d811a0 100644 --- a/audiostats/db/session.py +++ b/audiostats/db/session.py @@ -1,10 +1,14 @@ +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 @@ -12,5 +16,28 @@ async def __aenter__(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 \ No newline at end of file + @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 \ No newline at end of file diff --git a/audiostats/db/uow.py b/audiostats/db/uow.py index 99c3516..1d5c2e0 100644 --- a/audiostats/db/uow.py +++ b/audiostats/db/uow.py @@ -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')