Python Sql炼金术:子对象不';t在提交事件侦听器之前加载

Python Sql炼金术:子对象不';t在提交事件侦听器之前加载,python,sqlalchemy,flask-sqlalchemy,Python,Sqlalchemy,Flask Sqlalchemy,我正在配置我的应用程序,以便在指定的数据库表/列更新时自动更新elasticsearch(es)索引。我以迈克尔·格林伯格的为基础。我正在使用SQLAlchemy会话事件侦听器(提交之前和提交之后)来收集更改并确定何时更新es索引 我正在索引的表是一个用户表,它的子级是地址表。除了索引用户名,我还想索引他们的城市,这来自地址表。我的问题是地址字段填充为无,而不是加载地址。创建新用户(及其地址)时,我收到以下错误:“此会话处于“已提交”状态;此事务中无法发出进一步的SQL。'当我尝试为城市值编制索

我正在配置我的应用程序,以便在指定的数据库表/列更新时自动更新elasticsearch(es)索引。我以迈克尔·格林伯格的为基础。我正在使用SQLAlchemy会话事件侦听器(提交之前和提交之后)来收集更改并确定何时更新es索引

我正在索引的表是一个用户表,它的子级是地址表。除了索引用户名,我还想索引他们的城市,这来自地址表。我的问题是地址字段填充为无,而不是加载地址。创建新用户(及其地址)时,我收到以下错误:“此会话处于“已提交”状态;此事务中无法发出进一步的SQL。'当我尝试为城市值编制索引时

我还尝试过使用after_flush_postexc事件,得到了相同的结果。对于在“after_commit”或“after_flush_postexec”事件中加载子关系的配置,您有什么建议吗

型号.py


class SearchableMixin(object):
    @classmethod
    def before_commit(cls, session):
        session._changes = {
            'add': list(session.new),
            'update': list(session.dirty),
            'delete': list(session.deleted)
        }

    @classmethod
    def after_commit(cls, session):
        for obj in session._changes['add']:
            if isinstance(obj, SearchableMixin):
                #do stuff to add User name and city to index
        for obj in session._changes['update']:
            if isinstance(obj, SearchableMixin):
                # do stuff to update User name and city in index
        for obj in session._changes['delete']:
            if isinstance(obj, SearchableMixin):
                # do stuff to delete User name and city from index
        session._changes = None

class User(SearchableMixin, db.model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    address = db.relationship("Address", backref="user", lazy='joined', uselist=False)

    @hybrid_property
    def city(self):
        return self.address.city


class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    city = db.Column(db.String(50))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))


class CRUDMixin(object):
    @classmethod
    def create(cls, **kwargs):
        """Create a new record and save it the database."""
        instance = cls(**kwargs)
        saved = instance.save()
        return saved
        
    def update(self, commit=True, **kwargs):
        for attr, value in kwargs.items():
            if value != getattr(self, attr):
                setattr(self, attr, value)
        return commit and self.save() or self

    def save(self, commit=True):
        db.session.add(self)
        if commit:
            try:
                db.session.commit()
            except Exception:
                db.session.rollback()
                raise
        return self

    def delete(self, commit=True):
        """Remove the record from the database."""
        db.session.delete(self)
        return commit and db.session.commit()


class Model(CRUDMixin, db.Model):

    __abstract__ = True

class SearchableMixin(object):

    @classmethod
    def create(cls, **kwargs):
        try:
            new = super().create(**kwargs)
            add_to_index(new)
            db.session.commit()
            return new
        except Exception:
            raise

    def update(self, commit=True, **kwargs):
        try:
            super().update(commit, **kwargs)
            add_to_index(self)
            db.session.commit()
            return self
        except Exception:
            raise

    def delete(self, commit=True):
        try:
            super().delete(commit)
            remove_from_index(self)
            db.session.commit()
            return None
        except Exception:
            raise

class User(SearchableMixin, Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    address = db.relationship("Address", backref="user", lazy='joined', uselist=False)

    @hybrid_property
    def city(self):
        return self.address.city


class Address(Model):
    id = db.Column(db.Integer, primary_key=True)
    city = db.Column(db.String(50))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))


  [1]: https://stackoverflow.com/questions/62722027/sql-alchemy-wont-drop-tables-at-end-of-test-due-to-metadata-lock-on-db

这是我想出的解决办法。在解决这个问题的同时,它提出了另一个问题,我为此提出了一个[单独的问题][1]。然而,虽然远不是完美的,但它似乎确实有效

我没有使用sqlalchemy事件挂钩,而是首先将db.model子类化,添加了一个CRUD mixin,其中添加了创建、更新、保存和删除函数。然后在可搜索的Mixin中,我重写了那些create、update和delete函数,以调用相应的索引函数

型号.py


class SearchableMixin(object):
    @classmethod
    def before_commit(cls, session):
        session._changes = {
            'add': list(session.new),
            'update': list(session.dirty),
            'delete': list(session.deleted)
        }

    @classmethod
    def after_commit(cls, session):
        for obj in session._changes['add']:
            if isinstance(obj, SearchableMixin):
                #do stuff to add User name and city to index
        for obj in session._changes['update']:
            if isinstance(obj, SearchableMixin):
                # do stuff to update User name and city in index
        for obj in session._changes['delete']:
            if isinstance(obj, SearchableMixin):
                # do stuff to delete User name and city from index
        session._changes = None

class User(SearchableMixin, db.model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    address = db.relationship("Address", backref="user", lazy='joined', uselist=False)

    @hybrid_property
    def city(self):
        return self.address.city


class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    city = db.Column(db.String(50))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))


class CRUDMixin(object):
    @classmethod
    def create(cls, **kwargs):
        """Create a new record and save it the database."""
        instance = cls(**kwargs)
        saved = instance.save()
        return saved
        
    def update(self, commit=True, **kwargs):
        for attr, value in kwargs.items():
            if value != getattr(self, attr):
                setattr(self, attr, value)
        return commit and self.save() or self

    def save(self, commit=True):
        db.session.add(self)
        if commit:
            try:
                db.session.commit()
            except Exception:
                db.session.rollback()
                raise
        return self

    def delete(self, commit=True):
        """Remove the record from the database."""
        db.session.delete(self)
        return commit and db.session.commit()


class Model(CRUDMixin, db.Model):

    __abstract__ = True

class SearchableMixin(object):

    @classmethod
    def create(cls, **kwargs):
        try:
            new = super().create(**kwargs)
            add_to_index(new)
            db.session.commit()
            return new
        except Exception:
            raise

    def update(self, commit=True, **kwargs):
        try:
            super().update(commit, **kwargs)
            add_to_index(self)
            db.session.commit()
            return self
        except Exception:
            raise

    def delete(self, commit=True):
        try:
            super().delete(commit)
            remove_from_index(self)
            db.session.commit()
            return None
        except Exception:
            raise

class User(SearchableMixin, Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))
    address = db.relationship("Address", backref="user", lazy='joined', uselist=False)

    @hybrid_property
    def city(self):
        return self.address.city


class Address(Model):
    id = db.Column(db.Integer, primary_key=True)
    city = db.Column(db.String(50))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))


  [1]: https://stackoverflow.com/questions/62722027/sql-alchemy-wont-drop-tables-at-end-of-test-due-to-metadata-lock-on-db