Страницы

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

четверг, 2 января 2020 г.

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

#python #архитектура #sqlalchemy #model


Попробую сформулировать вопрос.

Сейчас я изучаю 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 # Тут код


Теперь я хочу понять. Насколько я продвинулся в велосипедостроении? Как нужно делать
"по хорошему"? Я открыл несколько проектов с открытым исходным кодом и не встретил
там ничего подобного. Поэтому у меня закрадывается мысль, что это не совсем верный путь.
    


Ответы

Ответ 1



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

Ответ 2



Вообще подход то правильный в духе ОО, но как правило session - не стоит использовать в модели, тем более как у вас в примере глобальную переменную session, лучше тогда ее передавать в метод/свойство. И в вашем примере есть некоторая неправильность на мой взгляд - методы указанные в примере должны быть classmethod - методами класса, ведь они не привязаны к экземпляру сущности Я бы посоветовал вам по возможности вместо обычных свойств/методов делать гибридные - на всякий случай. Вдруг потом понадобится делать запросы используя кусок логики свойства в конечных запросах они порой повышают читаемость.

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

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