Обертки функций внутри класса с использованием декораторов

У меня есть класс, который взаимодействует с базой данных, и поэтому есть повторяющиеся действия (установить сеанс, зафиксировать, закрыть сеанс) до и после каждого метода-члена класса.

Следующее:

class UserDatabaseManager(object):

    DEFAULT_DB_PATH = 'test.db'

    def __init__(self, dbpath=DEFAULT_DB_PATH):
        dbpath = 'sqlite:///' + dbpath
        self.engine = create_engine(dbpath, echo=True)

    def add_user(self, username, password):
        Session = sessionmaker(bind=self.engine)
        session = Session()
        # <============================== To be wrapped
        user = User(username, password)
        session.add(user)
        # ==============================>
        session.commit()
        session.close()

    def delete_user(self, user):
        Session = sessionmaker(bind=self.engine)
        session = Session()
        # <============================== To be wrapped
        # Delete user here
        # ==============================>
        session.commit()
        session.close()

Что такое идиоматический способ абстрагирования повторных вызовов сеанса с помощью функции-оболочки?

Я бы предпочел сделать это с декораторами, объявив частный _Decorators класс внутри UserDatabaseManager и реализации функции оболочки внутри, но тогда такой класс не сможет получить доступ к self.engine Атрибут экземпляра внешнего класса.

2 ответа

Решение

Простой (и, на мой взгляд, самый идиоматичный) способ сделать это - обернуть шаблонный код настройки / демонтажа в диспетчере контекста, используя contextlib.contextmanager, Вы тогда просто используете with утверждение в функциях, которые выполняют работу (вместо того, чтобы пытаться обернуть эту функцию самой).

Например:

from contextlib import contextmanager

class UserDatabaseManager(object):

    DEFAULT_DB_PATH = 'test.db'

    def __init__(self, dbpath=DEFAULT_DB_PATH):
        dbpath = 'sqlite:///' + dbpath
        self.engine = create_engine(dbpath, echo=True)

    @contextmanager
    def session(self):
        try:
            Session = sessionmaker(bind=self.engine)
            session = Session()
            yield session
            session.commit()
        except:
            session.rollback()
        finally:
            session.close()

    def add_user(self, username, password):
        with self.session() as session:
            user = User(username, password)
            session.add(user)

    def delete_user(self, user):
        with self.session() as session:
            session.delete(user)

Вы можете создать простую функцию вне класса, чтобы обернуть каждый метод:

def create_session(**kwargs):
   def outer(f):
     def wrapper(cls, *args):
       Session = sessionmaker(bind=getattr(cls, 'engine'))
       session = Session()
       getattr(session, kwargs.get('action', 'add'))(f(cls, *args))
       session.commit()
       session.close()
     return wrapper
   return outer

class UserDatabaseManager(object):
  DEFAULT_DB_PATH = 'test.db'
  def __init__(self, dbpath=DEFAULT_DB_PATH):
    dbpath = 'sqlite:///' + dbpath
    self.engine = create_engine(dbpath, echo=True)
  @create_session(action = 'add')
  def add_user(self, username, password):
    return User(username, password)

  @create_session(action = 'delete')
  def delete_user(self, user):
     return User(username, password)

Как правило, операции настройки и удаления, подобные описанным выше, лучше всего помещать в контекстный менеджер:

class UserDatabaseManager(object):
  DEFAULT_DB_PATH = 'test.db'
  def __init__(self, dbpath=DEFAULT_DB_PATH):
     dbpath = 'sqlite:///' + dbpath
     self.engine = create_engine(dbpath, echo=True)

class UserAction(UserDatabaseManager):
  def __init__(self, path):
    UserDatabaseManager.__init__(self, path)
  def __enter__(self):
    self.session = sessionmaker(bind=self.engine)()
    return self.session
  def __exit__(self, *args):
     self.session.commit()
     self.session.close()

with UserAction('/the/path') as action:
   action.add(User(username, password))

with UserAction('/the/path') as action:
   action.remove(User(username, password))
Другие вопросы по тегам