0

I have a following model where a parent can have many children. User can just create the parent or create a parent along with the child at the same time.

class Parent(Base):
  name = Column(String)
  slug = Column(String)
  values = relationship('Child', back_populates='parent')

class Child(Base):
  name = Column(String, unique=True)
  slug = Column(String)
  parent_id = Column(Integer, ForeignKey('parent.id'))
  parent = relationship('Parent', back_populates='values')

I need to create a parent but if values is send then create a child as well. The problem here is to create a Child, parent id is needed. If I create a parent and then get the id and while creating a child if there is any error like name uniquess for same parent id then there will be broken parent object. How can I create both parent and child once there is no issue in both table?

I am using encode/database for async database operation so I have no idea of doing such

parent = Parent(name='Parent name')
child = Child(name='Child name', slug='child-name')
session.add_all([parent, child])
session.commit()

when using encode database library https://github.com/encode/databases This is what I am trying

async def check_values_are_unique(db, child_value, parent):
    # get the list of slugs of particular attribute.
    # alternative of django values_list in sqlalchemy
    existing_values = await db.execute(select(parent.values.slug).distinct()).all()
    # some check is done here like Value already exists within this parent


async def create_parent(db, data):
  # data -> Parent(name='Parent name', values=[Child(name='Child name', value='child name')])
  values_input = data.values
  parent_qs = Parent.__table__.select().where(
        Parent.__table__.c.name == data.name
    )
  parent_exists = await db.fetch_one(query=parent_qs)
  if parent_exists:
      return Error(
          code="PARENT_ALREADY_EXIST",
          message=f"Parent with name {data.name} already exist",
      )
  else:
      # create an instance of attribute
      parent_input = data.__dict__
      del parent_input["values"]
      # create an instance 
      parent = Parent.__table__.insert().values(**parent_input)
      for val in values_input:
          setattr(val, "slug", slugify(val.name))
          child_value = Child.__table__.insert().values(
              **val.__dict__, parent=parent
          )
      check_values_are_unique(db, values_input, parent)

UPDATED CODE

async def create_parent(db, data):
  # data -> Parent(name='Parent name', values=[Child(name='Child name', value='child name')])
  values_input = data.values
  parent_qs = Parent.__table__.select().where(
        Parent.__table__.c.name == data.name
    )
  parent_exists = await db.fetch_one(query=parent_qs)
  if parent_exists:
      return Error(
          code="PARENT_ALREADY_EXIST",
          message=f"Parent with name {data.name} already exist",
      )
  else:
      # create an instance of attribute
      parent_input = data.__dict__
      del parent_input["values"]
      # create an instance 
      parent = Parent.__table__.insert().values(**parent_input)
      for val in values_input:
          setattr(val, "slug", slugify(val.name))
          child_value = Child.__table__.insert().values(
              **val.__dict__, parent=parent
          )
      async with db.transaction():
            # this will create a parent instance but wont get save to database
            parent = Parent(**parent_input)
            for val in values_input:
                setattr(val, "slug", slugify(val.name))
                child_value = Child(**val.__dict__, parent=parent)
                # child.values.append(child_value)
            print('now save')
            # NOW SAVE PARENT AND CHILD IN DB
            print("parent", parent.__dict__) # it has values as well
            # parent {'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x7f51abda5430>, 'name': 'parent name', 'slug': 'parent-name', 'values': [<app.models.parent.parent.Child object at 0x7f51abda5520>, <app.models.parent.parent.Child object at 0x7f51abda5520>]}
            parent_id = await db.execute(
                Parent.__table__.insert().values(
                    name=parent.name,
                    slug=parent.slug
                )
            )
            print("parent_id", parent_id)
            response_payload = {
                **data.__dict__,
                "id": parent_id,
                "choices": values_input, # no idea to get list of values with id 
            }
            print("response payload", response_payload)

            return ParentPayload(**response_payload)

I could save Parent object but not child and you can see in the comment for print ('parent', parent.__dict__). It has list of values as well.

milan
  • 2,409
  • 2
  • 34
  • 71
  • You can first create the parent, retrieve the id of the parent and use it to create the child. All of it within a transaction https://www.encode.io/databases/connections_and_transactions/#transactions – lsabi Sep 20 '21 at 06:49
  • @lsabi Thank you for your comment. I could not understand how to use from that documentation. If its okay to you, can you put sample code in your answer, please? – milan Sep 20 '21 at 09:18

1 Answers1

0

I don't have the setup to test it right now, but this should give you a guideline of how it should be done.

Here, the database object is the connected instance of the database. The operations that are performed within this context will be part of the same transaction.

else:
      # create an instance of attribute
      parent_input = data.__dict__
      del parent_input["values"]
      async with database.transaction():
          # create parent instance 
          parent = Parent.__table__.insert().values(**parent_input)
          for val in values_input:
              setattr(val, "slug", slugify(val.name))
              child_value = Child.__table__.insert().values(
                  **val.__dict__, parent=parent
              )
      check_values_are_unique(db, values_input, parent)
lsabi
  • 3,641
  • 1
  • 14
  • 26
  • I am getting `"message": "SQL expression element expected, got .",` issue on line `child_value = Child.__table__.insert().values( **val.__dict__, parent=parent )`. – milan Sep 20 '21 at 11:54
  • Does your code work? This error means that your code is expecting a SQL expression, not an object. Probably it is due to parent=parent. Maybe using parent_id=parent.id or something else should help – lsabi Sep 20 '21 at 12:49
  • if I do parent = Parent(**parent_input) rather then it works. – milan Sep 20 '21 at 12:53
  • Does it insert the parent in the database? – lsabi Sep 20 '21 at 14:54
  • I am getting issue on other line `existing_values = await db.execute(select(parent.values.slug).distinct()).all()` to get the list/tuple of slugs. Once I fix this I will try inserting and will let you know. – milan Sep 21 '21 at 09:00
  • ```parent_id = await db.execute( Parent.__table__.insert().values( name=parent.name, slug=parent.slug ) ) ``` creates a parent object but not child. If I pass values = parent.values then I get 'unused column values'. – milan Sep 21 '21 at 11:58
  • You cannot create both parent and child at once. You have to first create the parent and then the child. This will help you understand better why https://www.ahmed-ibrahim.com/relational-database-transaction-acid-in-dbms/ – lsabi Sep 21 '21 at 13:16
  • Thanks for the link. I will read it carefully. Before reading the article you suggested, I will make an assumption that I will have to do something like ```parent_id = await db.execute(Parent.__table__.insert().values(name=parent.name, slug=parent.slug))``` and then ```child = await db.exeucte(Child.__table__.insert().values(name=child.name, slug=child.slug, parent=parent_id))```. Am I right? – milan Sep 22 '21 at 06:51
  • If its the way then I think if you put those things in the answer then I should mark it as answered. – milan Sep 22 '21 at 06:53
  • Yes, it is what I meant. Does it work as expected on your code? – lsabi Sep 22 '21 at 07:09
  • Parent got created in the database but I am sorry I am so confused on creating child object as it will have multiple values. I have updated my code. Can you once look at it, please? Also can you look at parent.__dict__ where there is list of values that belongs to child. – milan Sep 22 '21 at 10:28
  • You just copied and pasted my code....As I said, it needs to be checked since I haven't worked with that particular library. Though, the error is quite simple: you need to insert the parent and child creation in a transaction. You'll first create the parent and then add the children using the parent's reference – lsabi Sep 22 '21 at 10:36