pythonflasksqlalchemyflask-sqlalchemypython-dataclasses

SQLAlchemy and proxy_association giving use default_factory error


So I am doing some learning, and I have followed several tutorials online for Flask-SQLAlchemy and dataclasses in Python. I have my app set up as bog standard as I can get:

__init__.py

from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://log:pass@localhost/db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
CORS(app)
db = SQLAlchemy(app)

I got up to a point where I am using the Object Association Pattern for Many-To-Many relationships, and that worked fine, returning what I need. I now want to pull an extra column from the middle table in that relationship, x. That led me to using association_proxy. I seem to have followed the tutorials correctly that I can find, but when I start up flask, I keep getting the following:

ValueError: mutable default <class 'sqlalchemy.ext.associationproxy.AmbiguousAssociationProxyInstance'> for field x is not allowed: use default_factory

models.py

from typing import List
from dataclasses import dataclass
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import Mapped

from sqlalchemy.ext.associationproxy import association_proxy

from . import db


@dataclass
class Main(db.Model):
    __tablename__="main"
    id: int = db.Column(db.Integer, primary_key=True)
    ones: Mapped[List[One]] = db.relationship("One", secondary="manys", overlaps="main")

@dataclass
class Many(db.Model):
    __tablename__ = "manys"

    main_id = db.Column(db.Integer, db.ForeignKey("main.id"), primary_key=True)
    one_id = db.Column(db.Integer, db.ForeignKey("one.id"), primary_key=True)
    x = db.Column(db.Boolean, nullable=False, default=False)

@dataclass
class One(db.Model):
    __tablename__ = "ones"

    id: int = db.Column(db.Integer, primary_key=True)

    name: str = db.Column(db.String, nullable=False)
    other_id: int = db.Column(db.Integer, nullable=False)

    many_link = db.relationship("Many", backref="ones")
    many_x: Mapped[bool] = association_proxy("many_link", "x")


Am I doing anything explicitly wrong? Am I missing something? If I add in default_factory=None to my proxy_association, I get

sqlalchemy.exc.ArgumentError: Attribute 'x' on class <class 'api.model.One'> includes dataclasses argument(s): 'default_factory' but class does not specify SQLAlchemy native dataclass configuration.


Solution

  • Solved by the following. Retains the ability to use Main.query.all() functionality. Thanks for the pointer, @snakecharmerb.

    __init__.py

    #Model Base Class
    class Base(DeclarativeBase, MappedAsDataclass):
        pass
    
    
    app = Flask(__name__)
    app.json = BetterJsonProvider(app)
    app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://login:passwd@localhost/db"
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    CORS(app)
    
    db = SQLAlchemy(app, model_class=Base)
    

    "Dataclasses" now look like this:

    class Main(db.Model):
        __tablename__="main"
        id: int = db.Column(db.Integer, primary_key=True)
        ones: Mapped[List[One]] = db.relationship("One", secondary="manys", overlaps="many,one")
    
    class Many(db.Model):
        __tablename__ = "manys"
    
        main_id = db.Column(db.Integer, db.ForeignKey("main.id"), primary_key=True)
        one_id = db.Column(db.Integer, db.ForeignKey("one.id"), primary_key=True)
        x = db.Column(db.Boolean, nullable=False, default=False)
    
    class One(db.Model):
        __tablename__ = "ones"
    
        id: int = db.Column(db.Integer, primary_key=True)
    
        name: str = db.Column(db.String, nullable=False)
        other_id: int = db.Column(db.Integer, nullable=False)
    
        many_link = db.relationship("Many", backref="ones", uselist=False)
        many_x: Mapped[bool] = association_proxy("many_link", "x", default_factory=bool)