SQLAlchemy - How to correctly connect two sets of data?

Viewed 50

I am hoping for some guidance about what I believe is going to be a common pattern in SQLAlchemy for Python. However, I have so far failed to find a simple explanation for someone new to SQLAlchemy.

I have the follow objects:

  • Customers
  • Orders
  • Products

I am building a Python FastAPI application and I want to be able to create customers, and products individually. And subsequently, I want to then be able to create an order for a customer that can contain 1 or more products. A customer will be able to have multiple orders also.

Here are my SQLAlchemy models:

order_products = Table('order_products', Base.metadata,
    Column('order_id', ForeignKey('orders.id'), primary_key=True),
    Column('product_id', ForeignKey('products.id'), primary_key=True)
)

class Customer(Base):
    __tablename__ = "customers"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    address = Column(String)
    phonenumber = Column(String)
    email = Column(String, unique=True, index=True)
    is_active = Column(Boolean, default=True)

    orders = relationship("Order", back_populates="customers")

class Order(Base):
    __tablename__ = "orders"

    id = Column(Integer, primary_key=True, index=True)
    ordernumber = Column(String, index=True)
    customer_id = Column(Integer, ForeignKey("customers.id"))

    customers = relationship("Customer", back_populates="orders")
    products = relationship("Product", secondary="order_products", back_populates="orders")

class Product(Base):
    __tablename__ = "products"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    size = Column(Integer)
    order_id = Column(Integer, ForeignKey("orders.id"))

    orders = relationship("Order", secondary="order_products", back_populates="products")

And here are my CRUD operations:

def create_customer(db: Session, customer: customer.CustomerCreate):
    db_customer = models.Customer(name = customer.name, address = customer.address, email=customer.email, phonenumber=customer.phonenumber)
    db.add(db_customer)
    db.commit()
    db.refresh(db_customer)
    return db_customer

def create_product(db: Session, product: product.Productreate):
    db_product = models.Product(name = product.name, size = product.size)
    db.add(db_product)
    db.commit()
    db.refresh(db_product)
    return db_product

def create_order(db: Session, order: order.OrderCreate, cust_id: int):
    db_order = models.Order(**order.dict(), customer_id=cust_id)
    db.add(db_order)
    db.commit()
    db.refresh(db_order)
    return db_order

def update_order_with_product(db: Session, order: order.Order):

    db_order = db.query(models.Order).filter(models.Order.id==1).first()
    if db_order is None:
        return None

    db_product = db.query(models.Order).filter(models.Product.id==1).first()
    if db_order is None:
        return None

    db_order.products.append(db_product)

    db.add(db_order)
    db.commit()
    db.refresh(db_order)
    return db_order

All of the CRUD operations work apart from update_order_with_product which gives me this error:

child_impl = child_state.manager[key].impl KeyError: 'orders'

  1. I'm not sure if I am taking the correct approach to the pattern needed to define the relationships between my models. If not, can someone point me in the right direction of some good examples for a beginner?

  2. If my pattern is valid then there must be an issue with my CRUD operation trying to create the relationships? Can anyone help with that?

1 Answers

This query could be a problem:

db_product = db.query(models.Order).filter(models.Product.id==1).first()

Should probably be:

db_product = db.query(models.Product).filter(models.Product.id==1).first()

because you want to get a Product instance, not Order.

When you update a record you should not add it to the session (because it has been registered to the session when you queried the record).

def update_order_with_product(db: Session, order: order.Order):

    db_order = db.query(models.Order).filter(models.Order.id==1).first()
    if db_order is None:
        return None

    db_product = db.query(models.Product).filter(models.Product.id==1).first()
    if db_product is None:
        return None

    db_order.products.append(db_product)

    db.commit()
    db.refresh(db_order)
    return db_order
Related