Python 如何在SQLAlchemy中映射具有动态关系的层次结构?

Python 如何在SQLAlchemy中映射具有动态关系的层次结构?,python,orm,sqlalchemy,Python,Orm,Sqlalchemy,我定义的SQLAlchemy模型如下: class SubProject(Base): active = Column(Boolean) class Project(Base): active = Column(Boolean) subprojects = relationship(SubProject, backref=backref('project')) class Customer(Base): active = Column(Boolean) projects

我定义的SQLAlchemy模型如下:

class SubProject(Base):
  active = Column(Boolean)

class Project(Base):
  active = Column(Boolean)
  subprojects = relationship(SubProject, backref=backref('project'))

class Customer(Base):
  active = Column(Boolean)
  projects = relationship(Project, backref=backref('customer'))
我需要获得这两个州之一的客户名单:

  • 所有客户,包括所有项目和所有子项目
  • 仅活动客户、仅活动项目和仅活动子项目

    编辑值得注意的是,应包括所有没有项目的活跃客户, 所有没有活动调查的活动项目都应包括在本报告中


  • 这在带有连接的SQL中是微不足道的,但是我不知道如何使用SQLAlchemy ORM来实现它。这里的解决方案是什么?

    如果我理解正确,您需要让所有非活动对象对查询不可见。以下类将筛选出属性设置为
    False
    active
    的所有模型对象,包括通过关系访问的模型对象:

    from sqlalchemy.orm import Query
    from sqlalchemy.orm.util import _class_to_mapper
    
    
    class QueryActive(Query):
    
        def __init__(self, entities, *args, **kwargs):
            Query.__init__(self, entities, *args, **kwargs)
            query = self
            for entity in entities:
                if hasattr(entity, 'parententity'):
                    entity = entity.parententity
                cls = _class_to_mapper(entity).class_
                if hasattr(cls, 'active'):
                    query = query.filter(cls.active==True)
            self._criterion = query._criterion
    
        def get(self, ident):
            # Use default implementation when there is no condition
            if not self._criterion:
                return Query.get(self, ident)
            # Copied from Query implementation with some changes.
            if hasattr(ident, '__composite_values__'):
                ident = ident.__composite_values__()
            mapper = self._only_mapper_zero(
                        "get() can only be used against a single mapped class.")
            key = mapper.identity_key_from_primary_key(ident)
            if ident is None:
                if key is not None:
                    ident = key[1]
            else:
                from sqlalchemy import util
                ident = util.to_list(ident)
            if ident is not None:
                columns = list(mapper.primary_key)
                if len(columns)!=len(ident):
                    raise TypeError("Number of values doesn't match number "
                                    'of columns in primary key')
                params = {}
                for column, value in zip(columns, ident):
                    params[column.key] = value
                return self.filter_by(**params).first()
    
    要使用它,您必须创建一个单独的会话对象:

    session_active = sessionmaker(bind=engine, query_cls=QueryActive)()
    

    这种方法有局限性,不适用于某些复杂的查询,但适用于大多数项目。

    如果我没弄错的话,您需要让所有非活动对象对查询不可见。以下类将筛选出属性设置为
    False
    active
    的所有模型对象,包括通过关系访问的模型对象:

    from sqlalchemy.orm import Query
    from sqlalchemy.orm.util import _class_to_mapper
    
    
    class QueryActive(Query):
    
        def __init__(self, entities, *args, **kwargs):
            Query.__init__(self, entities, *args, **kwargs)
            query = self
            for entity in entities:
                if hasattr(entity, 'parententity'):
                    entity = entity.parententity
                cls = _class_to_mapper(entity).class_
                if hasattr(cls, 'active'):
                    query = query.filter(cls.active==True)
            self._criterion = query._criterion
    
        def get(self, ident):
            # Use default implementation when there is no condition
            if not self._criterion:
                return Query.get(self, ident)
            # Copied from Query implementation with some changes.
            if hasattr(ident, '__composite_values__'):
                ident = ident.__composite_values__()
            mapper = self._only_mapper_zero(
                        "get() can only be used against a single mapped class.")
            key = mapper.identity_key_from_primary_key(ident)
            if ident is None:
                if key is not None:
                    ident = key[1]
            else:
                from sqlalchemy import util
                ident = util.to_list(ident)
            if ident is not None:
                columns = list(mapper.primary_key)
                if len(columns)!=len(ident):
                    raise TypeError("Number of values doesn't match number "
                                    'of columns in primary key')
                params = {}
                for column, value in zip(columns, ident):
                    params[column.key] = value
                return self.filter_by(**params).first()
    
    要使用它,您必须创建一个单独的会话对象:

    session_active = sessionmaker(bind=engine, query_cls=QueryActive)()
    

    这种方法有局限性,不适用于某些复杂的查询,但适用于大多数项目。

    要补充Denis的答案,可以使用enable_断言(False)。据我所知,这是对SQLAlchemy为正常操作添加的查询的额外检查。对于更复杂的情况,您可以禁用它。

    为了补充Denis的答案,您可以使用enable_断言(False)。据我所知,这是对SQLAlchemy为正常操作添加的查询的额外检查。对于更复杂的情况,您可以禁用它。

    这是有希望的,但会导致我的应用程序中出现一系列异常,如:
    InvalidRequestError:Query.get()在具有现有条件的查询中被调用。
    是的,这根本不起作用;它最终污染了我应用程序中的所有其他查询。你能提供一个失败的最小测试用例吗?您的错误消息就像您在没有get()方法的情况下复制了QueryActive类的一部分。这很有希望,但在我的应用程序中会导致一系列异常,如:
    InvalidRequestError:Query.get()在具有现有条件的查询中被调用。
    是的,这根本不起作用;它最终污染了我应用程序中的所有其他查询。你能提供一个失败的最小测试用例吗?您的错误消息就像您在没有get()方法的情况下复制了QueryActive类的一部分。