SQLAlchemy嵌套模型创建单行

时间:2017-08-27 21:10:39

标签: python sqlalchemy nested

我正在寻找从 q2 创建一个新对象,该对象失败,因为Question类期望选项是Options的字典,而是接收dicts的字典。

因此,使用嵌套模型显然无法解压缩。

处理此问题的最佳方法是什么?是否有某种东西相当于嵌套模型的**字典的优雅?

main.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import models.base

from models.question import Question
from models.option import Option


engine = create_engine('sqlite:///:memory:')

models.base.Base.metadata.create_all(engine, checkfirst=True)
Session = sessionmaker(bind=engine)
session = Session()


def create_question(q):

    # The following hard coding works:
    # q = Question(text='test text',
    #                     frequency='test frequency',
    #                     options=[Option(text='test option')]
    #                     )

    question = Question(**q)
    session.add(question)
    session.commit()

q1 = {
    'text': 'test text',
    'frequency': 'test frequency'
}

q2 = {
    'text': 'test text',
    'frequency': 'test frequency',
    'options': [
        {'text': 'test option 123'},
    ]
}

create_question(q1)
# create_question(q2) FAILS

base.py

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

question.py

from sqlalchemy import *
from sqlalchemy.orm import relationship
from .base import Base


class Question(Base):

    __tablename__ = 'questions'

    id = Column(Integer, primary_key=True)

    text = Column(String(120), nullable=False)
    frequency = Column(String(20), nullable=False)
    active = Column(Boolean(), default=True, nullable=False)

    options = relationship('Option', back_populates='question')

    def __repr__(self):
        return "<Question(id={0}, text={1}, frequency={2}, active={3})>".format(self.id, self.text, self.frequency, self.active)

option.py

from sqlalchemy import *
from sqlalchemy.orm import relationship
from .base import Base


class Option(Base):

    __tablename__ = 'options'

    id = Column(Integer, primary_key=True)

    question_id = Column(Integer, ForeignKey('questions.id'))
    text = Column(String(20), nullable=False)

    question = relationship('Question', back_populates='options')

    def __repr__(self):
        return "<Option(id={0}, question_id={1}, text={2})>".format(self.id, self.question_id, self.text)

4 个答案:

答案 0 :(得分:1)

鉴于每次在传递给Option函数的字典中有options键时都需要创建create_question对象,您应该先使用字典理解来创建选项将结果传递给Question实例化器。我会按如下方式重写函数:

def create_question(q):

    # The following hard coding works:
    # q = Question(text='test text',
    #                     frequency='test frequency',
    #                     options=[Option(text='test option')]
    #                     )
    q = dict((k, [Option(**x) for x in v]) if k == 'options' else (k,v) for k,v in q.items())
    print(q)
    question = Question(**q)
    session.add(question)
    session.commit()

字典理解部分基本上检查给定字典中是否存在options键;如果有,则使用值创建Option个对象。否则,它会正常进行。

以上功能产生了以下内容:

# {'text': 'test text', 'frequency': 'test frequency'}
# {'text': 'test text', 'frequency': 'test frequency', 'options': [<Option(id=None, question_id=None, text=test option 123)>]}

我希望这会有所帮助。

答案 1 :(得分:1)

我喜欢@Abdou提供的答案,但想看看我是否能够让它更通用。

我最终提出了以下内容,它应该处理任何嵌套模型。

@event.listens_for(Question, 'init')
@event.listens_for(Option, 'init')
def received_init(target, args, kwargs):

    for rel in inspect(target.__class__).relationships:

        rel_cls = rel.mapper.class_

        if rel.key in kwargs:
            kwargs[rel.key] = [rel_cls(**c) for c in kwargs[rel.key]]

侦听任何指定模型的init事件,检查与传入的kwargs匹配的关系,然后将这些关系转换为关系的匹配类。

如果有人知道如何设置它以便它可以在所有模型上工作而不是指定它们,我将不胜感激。

答案 2 :(得分:0)

对于SQLAlchemy对象,您只需使用Model.__dict__

即可

答案 3 :(得分:0)

以@Searle 的回答为基础,这避免了需要在装饰器中直接列出所有模型,并且还提供对 when uselist=False 的处理(例如 1:1、many:1 关系):

from sqlalchemy import event
from sqlalchemy.orm import Mapper

@event.listens_for(Mapper, 'init')
def received_init(target, args, kwargs):
    """Allow initializing nested relationships with dict only"""
    for rel in db.inspect(target).mapper.relationships:
        if rel.key in kwargs:
            if rel.uselist:
                kwargs[rel.key] = [rel.mapper.class_(**c) for c in kwargs[rel.key]]
            else:
                kwargs[rel.key] = rel.mapper.class_(**kwargs[rel.key])

可能的进一步改进:

  • 添加 if kwargs[rel.key] 是模型实例的处理(现在,如果您为关系传递模型实例而不是字典,则会失败)
  • 允许将关系指定为无(现在需要空列表或字典)

来源:SQLAlchemy "event.listen" for all models