David J.
David J.

Reputation: 1913

sqlalchemy model creation failing due to missing required attribute - but it's not missing

I have an sql alchemy model that references another model as a foreign key.

class BaseMixin(object):
    @classmethod
    def create(cls, **kw):
        obj = cls(**kw)
        db.session.add(obj)
        db.session.commit()

    @classmethod
    def find(cls, id):
        obj = db.session.query(cls).get(id)
        return obj

class Node(db.Model, BaseMixin):
    id = db.Column(db.Integer, primary_key=True)
    label = db.Column(db.String(120), unique=True, nullable=False)
    firsts = db.relationship('First', backref='node', lazy=True)

    def __repr__(self):
        return f'<Node {self.label} {self.id}>'

    def __init__(self, label):
        self.label = label
        

class First(db.Model, BaseMixin):
    id = db.Column(db.Integer, primary_key=True)
    node_id = db.Column(db.Integer, db.ForeignKey('node.id'), nullable=False)

    def __init__(self, label):

        if label is None:
            raise Exception("A first must have a label")

        node = db.session.query(Node).filter_by(label=label).first()

        if node is None:
            node = Node(label=label)
            node.create()

        self.node_id = node.id

Elsewhere in my code, I try to create a first with a brand new label, which seems like it should create a new node in the process and use it as a foreign key:

first = First(label=context.label)
first.create()

But I get the following error:

   File "models.py", line 12, in create
      obj = cls(**kw)
  TypeError: __init__() missing 1 required positional argument: 'label'

It seems to me that I am passing label to node when trying to create. So why am I getting this error message?

Upvotes: 0

Views: 244

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55884

There are a few issues here.

Firstly, the code initialises entities and then calls create on the instance. This would result in two entities being initialised, since create creates a new entity. create is a classmethod, so it can be called on the class directly.

Secondly, the label argument is not being passed to create where it is called, so you get the error when create calls cls(**kw).

Instead of doing this

first = First(label=some_label)
first.create()

do this

first = First.create(label=some_label)

or just

first = First(label=some_label)
db.session.add(first)

(note that create is also called in First.__init__).

Thirdly - and this more of a preference - I'd avoid calling commit in the create method. Commit once, at the end of the request. This is more efficient and avoids situations where some objects get committed but others don't due to an exception being raised part way through a request.

Upvotes: 1

Related Questions