<reponame>Berailitz/library_monitor
import functools
import logging
from contextlib import contextmanager
from typing import Callable, List
from sqlalchemy import create_engine, exists
from sqlalchemy.orm import joinedload, relationship, scoped_session, sessionmaker
from sqlalchemy.orm.session import Session
from .config import DATABASE_URI, NOTICE_COUNTER
from .models import Base, Book, Chat, Location


class SQLManager(object):
    """Manager sessions.

    :member session_maker: Attached :obj: sessionmaker.
    :type session_maker: :obj:sessionmaker.
    """

    def __init__(self):
        Chat.books = relationship("Book")
        engine = create_engine(DATABASE_URI)
        Base.metadata.create_all(engine)
        session_factory = sessionmaker(bind=engine)
        self.session_maker = scoped_session(session_factory)

    @contextmanager
    def create_session(self):
        """Create session to talk to database, rollback and raise error if failed.
        """
        session = self.session_maker
        try:
            yield session
        except Exception as identifier:
            logging.exception(identifier)
            session.rollback()
            raise identifier
        finally:
            session.close()
            self.session_maker.remove()


def load_session(func: Callable):
    """Load new session and add session as the first argument of `func`.

    :param func: Target function.
    :type func: function.
    """
    @functools.wraps(func)
    def wrapper(*args, **kw):
        """See `load_session`
        """
        with args[0].sql_manager.create_session() as my_session:
            return func(my_session, *args[1:], **kw)
    return wrapper


class SQLHandler(object):
    """Handler for SQL requests.
    """

    def __init__(self, sql_manager=None):
        self.sql_manager = sql_manager

    @load_session
    def get_chats(my_session: Session) -> List[Chat]:
        return my_session.query(Chat).options(joinedload('books')).all()

    @load_session
    def count_notice(my_session: Session, target_book: Book):
        """
        Decrease `notice_counter` by 1 and
        delete reocrd if `notice_counter` is 0."""
        online_book = my_session.query(Book).filter(Book.id == target_book.id).filter(
            Book.location == target_book.location).filter(Book.chat_id == target_book.chat_id).one()
        online_book.notice_counter -= 1
        if online_book.notice_counter == 0:
            logging.warning(f"LibraryMonitor: Delete book `{target_book}`")
            my_session.delete(online_book)
        my_session.commit()

    @load_session
    def reset_notice_counter(my_session: Session, target_book: Book):
        """
        Reset `notice_counter` to `NOTICE_COUNTER`."""
        online_book = my_session.query(Book).filter(Book.id == target_book.id).filter(
            Book.location == target_book.location).filter(Book.chat_id == target_book.chat_id).one()
        online_book.notice_counter = NOTICE_COUNTER
        my_session.commit()

    @load_session
    def add_book(my_session: Session, book_id: str, book_name: str, chat_id: int, location: str) -> bool:
        if my_session.query(exists().where(Chat.id == chat_id)).scalar() is not None and \
                my_session.query(exists().where(Location.name == location)).scalar() is not None and \
                my_session.query(Book).filter(Book.id == book_id).filter(
                        Book.location == location).filter(Book.chat_id == chat_id).scalar() is None:
            # such chat exists
            new_book = Book(name=book_name, id=book_id,
                            location=location, chat_id=chat_id)
            my_session.add(new_book)
            my_session.commit()
            return True
        else:
            return False

import functools
import logging
from contextlib import contextmanager
from typing import Callable, List
from sqlalchemy import create_engine, exists
from sqlalchemy.orm import joinedload, relationship, scoped_session, sessionmaker
from sqlalchemy.orm.session import Session
from .config import DATABASE_URI, NOTICE_COUNTER
from .models import Base, Book, Chat, Location


class SQLManager(object):
    """Manager sessions.

    :member session_maker: Attached :obj: sessionmaker.
    :type session_maker: :obj:sessionmaker.
    """

    def __init__(self):
        Chat.books = relationship("Book")
        engine = create_engine(DATABASE_URI)
        Base.metadata.create_all(engine)
        session_factory = sessionmaker(bind=engine)
        self.session_maker = scoped_session(session_factory)

    @contextmanager
    def create_session(self):
        """Create session to talk to database, rollback and raise error if failed.
        """
        session = self.session_maker
        try:
            yield session
        except Exception as identifier:
            logging.exception(identifier)
            session.rollback()
            raise identifier
        finally:
            session.close()
            self.session_maker.remove()


def load_session(func: Callable):
    """Load new session and add session as the first argument of `func`.

    :param func: Target function.
    :type func: function.
    """
    @functools.wraps(func)
    def wrapper(*args, **kw):
        """See `load_session`
        """
        with args[0].sql_manager.create_session() as my_session:
            return func(my_session, *args[1:], **kw)
    return wrapper


class SQLHandler(object):
    """Handler for SQL requests.
    """

    def __init__(self, sql_manager=None):
        self.sql_manager = sql_manager

    @load_session
    def get_chats(my_session: Session) -> List[Chat]:
        return my_session.query(Chat).options(joinedload('books')).all()

    @load_session
    def count_notice(my_session: Session, target_book: Book):
        """
        Decrease `notice_counter` by 1 and
        delete reocrd if `notice_counter` is 0."""
        online_book = my_session.query(Book).filter(Book.id == target_book.id).filter(
            Book.location == target_book.location).filter(Book.chat_id == target_book.chat_id).one()
        online_book.notice_counter -= 1
        if online_book.notice_counter == 0:
            logging.warning(f"LibraryMonitor: Delete book `{target_book}`")
            my_session.delete(online_book)
        my_session.commit()

    @load_session
    def reset_notice_counter(my_session: Session, target_book: Book):
        """
        Reset `notice_counter` to `NOTICE_COUNTER`."""
        online_book = my_session.query(Book).filter(Book.id == target_book.id).filter(
            Book.location == target_book.location).filter(Book.chat_id == target_book.chat_id).one()
        online_book.notice_counter = NOTICE_COUNTER
        my_session.commit()

    @load_session
    def add_book(my_session: Session, book_id: str, book_name: str, chat_id: int, location: str) -> bool:
        if my_session.query(exists().where(Chat.id == chat_id)).scalar() is not None and \
                my_session.query(exists().where(Location.name == location)).scalar() is not None and \
                my_session.query(Book).filter(Book.id == book_id).filter(
                        Book.location == location).filter(Book.chat_id == chat_id).scalar() is None:
            # such chat exists
            new_book = Book(name=book_name, id=book_id,
                            location=location, chat_id=chat_id)
            my_session.add(new_book)
            my_session.commit()
            return True
        else:
            return False
Manager sessions.      :member session_maker: Attached :obj: sessionmaker.     :type session_maker: :obj:sessionmaker. Create session to talk to database, rollback and raise error if failed. Load new session and add session as the first argument of `func`.      :param func: Target function.     :type func: function. See `load_session` Handler for SQL requests. Decrease `notice_counter` by 1 and         delete reocrd if `notice_counter` is 0. Reset `notice_counter` to `NOTICE_COUNTER`. # such chat exists