SQLAlchemy: Getting a single object from joining multiple tables

前端 未结 3 657
说谎
说谎 2021-01-01 01:32

DB - SQLAlchemy setting

Consider a classic setting of two tables - user and api_key, represented by SQLAlchemy objects as:



        
3条回答
  •  情话喂你
    2021-01-01 02:33

    I'm surprised how little discussion there is on this... I need to join two tables and return a single object which has all columns from both tables. I'd expect that is a pretty common usecase - anyways here's the hack I made after hours of looking and failing to find a nice way

    from sqlalchemy import inspect
    def obj_to_dict(obj):
        return {c.key: getattr(obj, c.key) for c in inspect(obj).mapper.column_attrs}
    def flatten_join(tup_list):
        return [{**obj_to_dict(a), **obj_to_dict(b)} for a,b in tup_list]
    

    Here's an example of what it does...

    ########################## SETUP ##########################
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy import Column, Integer, ForeignKey, Text
    from sqlalchemy.ext.declarative import declarative_base
    
    DeclarativeBase = declarative_base()
    
    
    class Base(DeclarativeBase):
        __abstract__ = True
    
        def __repr__(self) -> str:
            items = []
            for key in self.__table__._columns._data.keys():
                val = self.__getattribute__(key)
                items.append(f'{key}={val}')
            key_vals = ' '.join(items)
            name = self.__class__.__name__
            return f"<{name}({key_vals})>"
    
    
    class User(Base):
        __tablename__ = "user"
    
        id = Column(Integer, primary_key=True, index=True)
        username = Column(Text)
    
    
    class Todo(Base):
        __tablename__ = "todo"
    
        id = Column(Integer, primary_key=True, index=True)
        title = Column(Text)
        user_id = Column(Integer, ForeignKey("user.id"))
    
    
    engine = create_engine("sqlite:///:memory:")
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    db = SessionLocal()
    Base.metadata.create_all(bind=engine)
    
    ########################## DEMO ##########################
    
    db.add(User(**{"id": 1, "username": "jefmason"}))
    db.add(Todo(**{"id": 1, "title": "Make coffee", "user_id": 1}))
    db.add(Todo(**{"id": 2, "title": "Learn SQLAlchemy", "user_id": 1}))
    db.commit()
    
    result = db.query(Todo, User).join(User).all()
    
    print(result)
    # > [(, ), (, )]
    
    print(flatten_join(result))
    # > [{'id': 1, 'title': 'Make coffee', 'user_id': 1, 'username': 'jefmason'}, {'id': 1, 'title': 'Learn SQLAlchemy', 'user_id': 1, 'username': 'jefmason'}]
    

提交回复
热议问题