Страницы

Поиск по вопросам

среда, 9 января 2019 г.

Как правильно оформить модели SQLAlchemy?

Попробую сформулировать вопрос.
Сейчас я изучаю 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', который позволит не удалять данные из БД насовсем, а так же методы, чтобы доставать из базы "не удалённые", все и только удалённые.
Кучу всего можно придумать при необходимости.
В конечном счёте, один раз реализованный базовый класс сэкономит много времени и места в моделях.

Комментариев нет:

Отправить комментарий