marshmallow-sqlalchemy icon indicating copy to clipboard operation
marshmallow-sqlalchemy copied to clipboard

`'list' object has no attribute '_sa_adapter'`

Open zeke8402 opened this issue 5 years ago • 3 comments

Hey everyone!

I'm trying to persist a schema that has a one-to-many relationship. For example, I am persisting a 'product' that can have many 'categories'

I have defined the Schemas like so

from project import ma # global ma object 

class CategorySchema(SQLAlchemyAutoSchema):
  class Meta:
    model = Category
    fields = ('id', 'title')
    include_fk = True
    load_instance = True

  id = ma.auto_field('id', hide=True)
  title = ma.auto_field()

class ProductSchema(SQLAlchemyAutoSchema):
  class Meta:
    model = Product
    fields = ('id', 'title', 'categories')
    include_fk = True
    load_instance = True
    include_relationships = True
    ordered = True
  
  id = ma.auto_field('id', hide=True) # my own custom thing, hide isn't a feature of this lib
  title = ma.auto_field()
  categories = ma.List(ma.Nested(ProductCategorySchema))

Editing an item seems to work totally fine. (context: request_data is simply a dict with all required fields from the schema)

schema = ProductSchema()
existing_item = db.session.query(Product).filter(Product.id == item_id).first()
if existing_item is not None:
  updated_item = schema.load(request_data, session=db.session, instance=existing_item)
  db.session.commit()

Running the above code when changing how many categories are associated with the product works flawlessly. But when I try to create an object...

schema = ProductSchema()
new_item = schema.load(request_data, session=db.session, partial=True)
db.session.add(new_item)
db.session.commit()

It returns the following error: 'list' object has no attribute '_sa_adapter'

Further investigation revealed that when loading an existing instance, the categories are inside of an InstrumentedList, whereas attempting to create a product with many, existing categories, creates a regular List, instead. I think that is why this error message is happening, but I'm not sure why loading a new, transient object would result in a different list type for the categories. Perhaps it is because marshmallow-sqlalchemy is anticipating that these are new categories that I would want to persist? But I haven't found anything in the documentation supporting that theory.

I've attempted to modify the ProductSchema as well, and change the categories to categories = ma.Nested(CategorySchema, many=True), and I've also tried categories = fields.Related(CategorySchema), to no avail. Any help would be greatly appreciated!

zeke8402 avatar Sep 21 '20 19:09 zeke8402

This is a lot to chew on, so I thought I'd give a simpler explanation since I've had time to think through this. It seems like editing an item with a one-to-many relation persists all relations as expected.

updated_item = schema.load(request_data, session=db.session, instance=existing_item)

persist this works. But when I'm trying to create a new record, i.e...

new_item = schema.load(request_data, session=db.session, partial=True)

I get this error about an _sa_adapter. Is there any details I may be missing on persisting one-to-many relationships when creating a new item from a schema?

zeke8402 avatar Sep 24 '20 15:09 zeke8402

@zeke8402 I have been using this pattern for quite some time and havent found any issues. Which version of the library are you using ? Do you have a categories relationship in the Product sqlalchemy model ? Could you show us your sqla models ? What is the request_data you are using ?

Note - i dont use partial=True, ib my case we create it from scratch

AbdealiLoKo avatar Sep 24 '20 16:09 AbdealiLoKo

Perhaps it does relate, as I have a pivot table for the many-to-many relation

product_product_categories = db.Table(
    'product_product_categories',
    db.Column(
        'product_id',
        db.Integer,
        db.ForeignKey(
            'product_products.id',
            ondelete='CASCADE'
        ),
        primary_key=True,
        nullable=False,
        index=True
    ),
    db.Column(
        'category_id',
        db.Integer,
        db.ForeignKey(
            'product_categories.id',
            ondelete='CASCADE'
        ),
        primary_key=True,
        nullable=False,
        index=True
    )
)

Again marshmallow-sqlalchemy has no issues updating this relationship, it's only when creating a new product does this happen.

zeke8402 avatar Sep 29 '20 22:09 zeke8402