Сравнение экземпляров объектов SQLAlchemy для равноправия атрибутов

Приложение My Flask-Restful имеет несколько "объектов". В первой версии приложения это простые структуры данных без каких-либо действий, реализованных как диктовки или списки диктов.

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

В следующей версии приложения я заполняю "объекты" из SQLite DB, используя SQLAlchemy. Объекты теперь реализованы как декларативные классы SQLAlchemy или списки таких классов.

Чтобы сравнить "старые" и "новые" экземпляры на основе равенства атрибутов, мне пришлось добавить переопределение __eq__ в мои объекты SQLAlchemy. т.е. экземпляры считаются равными/неизменными, когда атрибуты имеют одинаковые значения. (Я разместил пример кода внизу этого вопроса).

Технически это работает, но вызывает некоторые архитектурные сигнальные колокола: "Я плыву в неправильном направлении?"

a) Если я добавлю __eq__ и __ne__ переопределяет объекты SQAlchemy, может ли это вызвать SQLAlchemy проблему, когда я позже захочу для повторного сохранения объектов обратно в базу данных?

b) Насколько далеко до моего приложения должны появляться объекты SQLAlchemy: существует ли "питонская лучшая практика"? То есть нормально или нормально распространять объекты SQLAlchemy с бизнес-логикой/поведением, не связанными с постоянством БД (например, отслеживание изменений); или они должны использоваться только как простые DTO между базой данных и сервером, с бизнес-логикой в ​​других объектах?

Примечание: мне ясно, что данные, представленные клиентам через REST apis и SSE, должны быть абстрагированы от деталей реализации на веб-сервере и БД, так что это не является частью этого вопроса.

sqlalchemy id равенство против равенства ссылок https://codereview.stackexchange.com/info/93511/data-transfer-objects-vs-entities-in-java-rest-server-application http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model/

class EqualityMixin(object):
# extended from the concept in :
# https://stackoverflow.com/info/390250/elegant-ways-to-support-equivalence-equality-in-python-classes

    def __eq__(self, other):
        classes_match = isinstance(other, self.__class__)
        a, b = deepcopy(self.__dict__), deepcopy(other.__dict__)
        #compare based on equality our attributes, ignoring SQLAlchemy internal stuff
        a.pop('_sa_instance_state', None)
        b.pop('_sa_instance_state', None)
        attrs_match = (a == b)
        return classes_match and attrs_match

    def __ne__(self, other):
        return not self.__eq__(other)

Ответы

Ответ 1

Я расскажу о том, что происходит за базовым классом, чтобы показать, что переопределения __eq__ и __ne__ прекрасны. Когда вы создаете экземпляр класса Base, вызывая declarative_base(), он использует метакласмент за кулисами для его настройки (возможно, стоит прочитать это объяснение это описание метакласса чтобы лучше понять, почему это связано). Он выполняет некоторую настраиваемую настройку, например добавление настраиваемого конструктора в класс Base и настройку того, как он будет отображаться из объекта в таблицу.

declarative_base() будет возвращать новый экземпляр класса Base метакласса DeclarativeMeta. Здесь задействованы все метаклассы, так что в то время, когда вы создаете класс, который расширяет ваш Base, он отображает его в таблицу. Если вы пропустите этот путь немного, вы увидите, как он сопоставляет столбцы, которые вы объявляете на своем объекте, в таблицу.

self.cls.__mapper__ = mp_ = mapper_cls(
        self.cls, # cls is your model
        self.local_table,
        **self.mapper_args # the columns you have defined
    )

Хотя фактический Mapper, который делает это, выглядит очень сложным и низким уровнем, на данном этапе он работает с первичными ключами и столбцами, а не с фактическими объектами. Это не подтверждает, что оно никогда не использовалось, поэтому я просмотрел способы использования == и != в источнике и не видел никаких причин для беспокойства.

Что касается вашего второго вопроса, я могу только на самом деле предложить свое собственное мнение - я много раз искал эту тему в прошлом и не нашел большого смысла в использовании "стандартного" SQL-алхимии. До сих пор я использовал SQL Alchemy для нескольких проектов, и кажется, что ваше использование объектов может расширяться настолько, насколько вы можете до сих пор разумно абстрагироваться от жизненного цикла session. Для меня это похоже на то, что "магия" Алхимии абстрагируется от самих моделей, что, когда сеансы обрабатываются хорошо, они достаточно далеки от уровня данных, что ему не кажется, что бизнес-логика в классах будет путь.