Попробую сформулировать вопрос.
Сейчас я изучаю Python и SQLAlchemy делая проект для себя. Создал таблицу User
Model = declarative_base()
class User(Model):
__tablename__ = "users"
id = Column(Integer, primary_key = True)
nickname = Column(String)
email = Column(String)
password = Column(String)
Добавил туда несколько записей.
Допустим теперь я хочу проверить - существует ли какой либо пользователь с указанным email? Я делаю следующее:
bool(session.query(exists().where(User.email == email)).scalar())
Как по мне - этот код абсолютно не читаем. Поэтому я вынес его (и другие подобные функции работы с базой) в отдельный класс Users. И вызываю их по мере необходимости в логике приложения.
class Users:
def exists(email):
return bool(session.query(exists().where(User.email == email)).scalar())
def add(nick, email, password):
pass # Тут код
def email_exists(email):
pass # Тут код
def update(id, nick, email, password):
pass # Тут код
Теперь я хочу понять. Насколько я продвинулся в велосипедостроении? Как нужно делать "по хорошему"? Я открыл несколько проектов с открытым исходным кодом и не встретил там ничего подобного. Поэтому у меня закрадывается мысль, что это не совсем верный путь.
Ответ
Я когда-то делал подобным образом. Как по мне - нормальная реализация методов моделей.
Неплохим решением будет создать базовый класс, от которого можно наследовать другие модели, а в самих методах добавлять нужную логику.
Для своих нужд, работая с фреймворком Flask, написал такую такую штуковину:
from ._base import db
from sqlalchemy.exc import IntegrityError, InterfaceError
from flask import flash
from sqlalchemy import event
from sqlalchemy.event import listen
from sqlalchemy.orm.interfaces import MapperExtension
from ..utils.redis import redis_store
from sqlalchemy import inspect
from sqlalchemy.ext.declarative import as_declarative, declared_attr
from pickle import dumps, loads
@as_declarative()
class BaseExtension(MapperExtension):
def after_insert(self, mapper, connection, instance):
row = instance.query.filter_by(id = instance.id).first()
payload = {'model': instance.__class__.__name__, 'data': instance.id, 'type': 'INSERT', 'row': row}
redis_store.publish('realtime', dumps(payload))
def after_update(self, mapper, connection, instance):
row = instance.query.filter_by(id = instance.id).first()
payload = {'model': instance.__class__.__name__, 'data': instance.id, 'type': 'UPDATE', 'row': row}
redis_store.publish('realtime', dumps(payload))
def after_delete(self, mapper, connection, instance):
row = instance.query.filter_by(id = instance.id).first()
payload = {'model': instance.__class__.__name__, 'data': instance.id, 'type': 'DELETE', 'row': row}
redis_store.publish('realtime', dumps(payload))
class Base(db.Model):
__abstract__ = True
__mapper_args__ = { 'extension': BaseExtension() }
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime, default=db.func.current_timestamp())
modified_at = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
@classmethod
def create(cls,**kwargs):
c = cls(**kwargs)
db.session.add(c)
try:
db.session.commit()
flash((c.__tablename__).capitalize() + u' created successfully!', 'success')
except IntegrityError:
db.session.rollback()
flash((c.__tablename__).capitalize() + u' created failed!' + u' IntegrityError', 'error')
except InterfaceError:
db.session.rollback()
flash((c.__tablename__).capitalize() + u' created failed!' + u' InterfaceError', 'error')
return c
def __repr__(self):
mapper = inspect(self).mapper
ent = []
object_data = {col.key: getattr(self, col.key) if not col.key == 'created_at' and not col.key == 'password' and not col.key == 'modified_at' else None for col in mapper.column_attrs}
return "{0}".format(object_data)
Сейчас я вижу, сколько тут всего ненужного и неправильного, но во всяком случае, для саморазвития, это был хороший опыт.
Во-первых, те поля, которые повторяются во всех моделях (id, created_at, modified_al) описываются единожды.
Во-вторых, наследуемый create(), с обработкой некоторых исключений.
В-третьих, события ORM (мой метод реализации которых считается устаревшим) after_insert, after_update, after_delete обрабатываются, и данные отдаются в redis. Я это делал для "real-time" передачи данных (нотификации, обновления данных в дашборде и пр.)
В-четвёртых, __repr__, который так же универсален для всех моделей.
Так же, сюда можно добавить так называемый 'soft-delete', который позволит не удалять данные из БД насовсем, а так же методы, чтобы доставать из базы "не удалённые", все и только удалённые.
Кучу всего можно придумать при необходимости.
В конечном счёте, один раз реализованный базовый класс сэкономит много времени и места в моделях.