Я пытаюсь создать новый объект из q2 , который завершается неудачно, потому что класс Question ожидает, что параметры будут словарем параметров, и вместо этого он получает слово dict.

Таким образом, распаковка явно не удалась с вложенной моделью.

Каков наилучший способ справиться с этим? Есть ли что-то, что эквивалентно элегантности ** dict для вложенной модели?

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)
1
Searle 28 Авг 2017 в 00:10

3 ответа

Лучший ответ

Мне понравился ответ, предоставленный @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, а затем преобразует их в соответствующий класс отношений.

Если кто-нибудь знает, как настроить его так, чтобы он мог работать на всех моделях, а не указывать их, я был бы признателен.

1
Searle 29 Авг 2017 в 19:17

Учитывая, что вам нужно создавать объект 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
Abdou 27 Авг 2017 в 23:56

Для объектов SQLAlchemy вы можете просто использовать Model.__dict__

0
R Yakovlev 27 Авг 2017 в 21:25