Marshmallow returning empty JSON after dumping - python

I am trying to implement a restful API with Flask using flask-restful, flask-sqlalchemy and flask-marshmallow. I am aware there are many questions about this issue but I still can't figure it out.
Solutions like using strict=True in marshmallow schemas or adding .data to user_schema.dump(result) don't let me see my result in the response body.
Here is my code :
# User.py file
from flask import jsonify, request
from flask_restful import Resource
from src import db
from src.models.user import User
from src.validation import UserSchema
class Users(Resource):
def get(self):
try:
data = request.get_json()
user = User.query.filter_by(email=data['email']).first()
user_schema = UserSchema()
result = user_schema.dump(user)
return jsonify(result)
except:
return jsonify({'error_message': 'User not found'})
def post(self):
try:
data = request.get_json()
user = User(data["firstName"], data["lastName"],
data["email"], data["password"])
db.session.add(user)
db.session.commit()
user_schema = UserSchema()
result = user_schema.dump(user)
return result
except:
return jsonify({'error_message': 'User already exists in database'})
The init.py file :
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["SQLALCHEMY_ECHO"] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql:///gauss"
ma = Marshmallow(app)
api = Api(app)
db = SQLAlchemy(app)
from src import routes
The validation.py file :
from src import ma
from src.models.user import User
class UserSchema(ma.SQLAlchemySchema):
class Meta:
model = User
And the User.py schema for the database :
from src import db
from datetime import datetime
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
firstName = db.Column(db.String(20))
lastName = db.Column(db.String(20))
email = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(), nullable=False)
createdAt = db.Column(db.DateTime, default=datetime.utcnow)
def __init__(self, firstName, lastName, email, password):
self.firstName = firstName
self.lastName = lastName
self.email = email
self.password = password
I've been looking for solutions for a few days now and it seems that marshmallow returning empty json occures mostly when querying with the .all() method for instance, because marshmallow .dump() method misses the Many=True configuration.
I'm probably missing something but I can't wrap my head around it. Any help appreciated !

i had the same problem, the solution to my problem was to change UserSchema(ma.SQLAlchemySchema): to UserSchema(ma.SQLAlchemyAutoSchema):

Related

Flask separate models into multiple files

I'm setting up a new Flask app, where I want to have all of my SQLAlchemy models in separate files. The overall structure should end up looking like this:
flask-app
|
|--api
| |--user.py
| |--example.py
|
|--controllers
| |--user.py
| |--example.py
|
|--models
| |--user.py
| |--example.py
|
|-main.py
|-modelref.py
|-config.py
In terms of code here's what i have so far:
main.py:
import os
from flask import Flask, send_from_directory
from flask_bcrypt import Bcrypt
from flask_cors import CORS
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
import modelref
from apiref import load_api
app = Flask(__name__, static_url_path='', static_folder='./../react/public')
app.config.from_object(os.getenv("APP_SETTINGS", "config.Development"))
CORS(app)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
bcrypt = Bcrypt(app)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder,'index.html')
load_api(app)
The modelref import is a shorthand of sorts. In the modelref.py file I import every model that I have in the models folder like so:
modelref.py:
from models.user import User
from models.example import Example
Then the model itself is something along the lines of:
models/user.py:
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
first_name = db.Column(db.String(128), nullable=False)
last_name = db.Column(db.String(128), nullable=False)
registered_on = db.Column(db.DateTime, nullable=False)
def __init__(self, username, email, password, first_name, last_name):
self.username = username
self.email = email
self.password = bcrypt.generate_password_hash(password, flask.current_app.config.get('BCRYPT_LOG_ROUNDS')).decode()
self.first_name = first_name
self.last_name = last_name
self.registered_on = datetime.datetime.now()
def save(self):
db.session.add(self)
db.session.commit()
The model gets manipulated in a controller class defined here:
controllers/user.py:
class UserController:
#staticmethod
def register(username: str, password: str, email: str, first_name: str, last_name: str) -> dict:
result: dict = {}
try:
user = User(
username=username,
password=password,
email=email,
first_name=first_name,
last_name=last_name
)
user.save()
except IntegrityError:
User.rollback()
raise ResourceExists("user already exists")
return login(user.username, user.password);
#staticmethod
def login(username: str, password: str):
user = User.query.filter_by(username).first()
if user:
if bcrypt.check_password_hash(user.password, password):
token = user.encode_auth_token(user.id)
return {
"username": user.username,
"first_name": user.first_name,
"last_name": user.last_name,
"token": token
}
else:
abort(400, "Invalid Password")
else:
abort(404, "User Not Found")
The controller is accessed in an API resource defined using pluggable views. The views and routes are registered with the app in the load_app(app) method. I'm not sharing that whole pipeline, because I believe it is out of scope, but if anyone believes it's not, than I will add it into the post.
So now here's the problem. If I try to access the model in the code or run flask db migrate, I get an error that db is not defined.
A solution I've seen is to add a db=SQLAlchemy line in the model, but then the flask db migrate doesn't find this model, likely because the mentioned line creates a new SQLAlchemy instance, so it's essentially unaware of the models existence.
With this setup I'm unable to import the db object from main since that causes a circular import due to the model being imported in the controller.
Is there a way I can pass a reference to db to the model without an import, or make the model search for the "current" instance of 'db'. Or is there any way to reorganize the main code so that I can maintain the general structure, but be able to run migrations?
It turns out that in a case such as mine, the best option is to apply the application factory pattern. I now have an app.py module, where I initialize all the modules I need in a create_app method like so:
app.py:
import os
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
db = SQLAlchemy()
migrate = Migrate()
bcrypt = Bcrypt()
def create_app():
app = Flask(__name__, static_url_path='', static_folder='./../react/public')
app.config.from_object(os.getenv("APP_SETTINGS", "config.Development"))
import modelref
db.init_app(app)
migrate.init_app(app, db)
bcrypt.init_app(app)
return app
And them my main.py file simply invokes the create_app method and then sets up routes and API resources like so:
main.py:
from flask import send_from_directory
from flask_cors import CORS
from apiref import load_api
from app import create_app
app = create_app()
CORS(app)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder,'index.html')
load_api(app)
Running import modelref within the create_model method makes all the models imported in modelref visible to the app and the flask db commands.

Flask-SQLAlchemy AttributeError: can't set attribute [duplicate]

This question already has answers here:
AttributeError: can't set attribute when connecting to sqlite database with flask-sqlalchemy
(6 answers)
Closed 1 year ago.
I try to create a database and put an article table in routes.py's /articles/<article_name> route but it gives me AttributeError: can't set attribute. I looked up other sources for a solution but they seemed irrelevant. Error occurs when create_all(), add(), commit() are used.
models.py
from files import db
from datetime import datetime
class Article(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False,unique=True)
article_content = db.Column(db.Text)
date = db.Column(db.DateTime,default=datetime.utcnow)
abstract = db.Column(db.String(150))
comments = db.relationship("Comment", backref="article", lazy = True)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_name = db.Column(db.String(30), unique=True, nullable=False)
password = db.Column(db.String(30), nullable=False)
comments = db.relationship("Comment", backref="user", lazy = True)
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_comment = db.Column(db.String(200), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
article_id = db.Column(db.Integer, db.ForeignKey("article.id"))
init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = ''
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
from files import routes
routes.py
from files import app, db
from flask import render_template
from files.models import Article
from files.forms import RegisterForm, LoginForm
#app.route('/')
#app.route('/main')
#app.route('/home')
def home():
return render_template("home.html")
#app.route('/articles/<article_name>') # dynamic route
def article(article_name):
db.create_all()
article_ = Article(title = article_name, article_content = "article", abstract = "ndng")
db.session.add(article_)
db.session.commit()
return render_template("article.html", article_name=article_name)
#app.route('/articles')
def articles():
return render_template("articles.html")
#app.route('/register')
def register():
form = RegisterForm()
return render_template("register.html", form = form)
#app.route('/login')
def login():
form = LoginForm()
return render_template("login.html", form = form)
I recently delt with that same error message. It is actually due to an upgrade in SQLAlchemy, which gets installed as a dependency of flask-sqlalchemy.
You can find the question I posted on stackoverflow for the issue here:
The problem gets solved by uninstalling SQLAlchemy==1.4.0 and installing the previous version SQLAlchemy==1.3.23.
Try doing that and see if it helps.
Just Downgrade the SQLAlchemy .
For some reason the latest version is not working
pip install 'SQLAlchemy<1.4.0'
Downgrading SQLAlchemy to anything lower than 1.4.0 (1.3.8 in my case) solves the problem.

Schedule SQLAlchemy to clear all rows from a table

I'm trying to create a function that can be scheduled to delete all rows within an SQLAlchemy model.
I'm trying to use apscheduler to accomplish this task. But I keep getting an error that says:
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'flask_sqlalchemy.model.DefaultMeta' is
not mapped; was a class (app.models.User) supplied where an instance was required?
Am I missing something?
Here is my app/__init__.py:
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from config import Config
app = Flask(__name__)
db = SQLAlchemy()
login = LoginManager()
app.config.from_object(Config)
db.init_app(app)
login.init_app(app)
login.login_view = 'login'
from app import routes, models
and here is my manage.py:
from apscheduler.schedulers.background import BackgroundScheduler
from app import app, db
from flask_migrate import Migrate
from flask_script import Manager
from app.models import User
manager = Manager(app)
migrate = Migrate(app, db)
def clear_data():
db.session.delete(User)
print("Deleted User table!")
#manager.command
def run():
scheduler = BackgroundScheduler()
scheduler.add_job(clear_data, trigger='interval', seconds=5)
scheduler.start()
app.run(debug=True)
if __name__ == '__main__':
manager.run()
Also, here's my model:
from app import db, login
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
api_token = db.Column(db.String(50), unique=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<models.py {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
As your error indicated, it is expecting an instances of an object, and you instead passed it a class. I think the issue is the first line in the clear_data function:
db.session.delete(User)
It was expecting an instances of a User record to delete, and doesn't know how to delete the whole table using just the model.
Check out this answer on how to delete all rows in a table. There are a few ways to do this, but this may be the least change for you:
db.session.query(User).delete()
In this case you are adding the step of SELECTing all the records in the table User maps to, then deleting them.
P.S.: as mentioned in the linked answer, you need to .commit() your session, otherwise it won't stick, and will rollback after you close the connection.
db.session.commit()
Code snippet:
db.session.query(model_name).delete()
db.session.commit()

SQL Alchemy filtering

I am trying to filter data by username in Python and sql alchemy but somehow the filter is not working., i am able to filter by ID and add users., in below code, i just showed the method for filter by username. not sure what wrong i am doing
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
import os
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,
'datas.sqlite')
db = SQLAlchemy(app)
ma = Marshmallow(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
class UserSchema(ma.Schema):
class Meta:
# Fields to expose
fields = ('username', 'email')
user_schema = UserSchema()
users_schema = UserSchema(many=True)
#endpointtogetuserdetailbyusername
#app.route("/username/<username>",methods=["GET"])
def user_detailbyusername(username):
user=User.query.filter(User.username=='Tim')
# i tried user=User.query.filter_by(User.username=='Tim') but no luck
return user_schema.jsonify(user)
if __name__ == '__main__':
app.run(debug=True)
I think you should add some method to really get back the records, something like User.query.filter(User.username=='Tim').all() or User.query.filter(User.username=='Tim').first(), as can be seen in the examples at http://flask-sqlalchemy.pocoo.org/2.3/queries/#querying-records

update state with flask sqlalchemy with postgres will not commit to database

I have read quite a bit of documentation and I can't see what is wrong with these lines
update_this = User.query.filter_by(email=email).first()
update_this.emailconfirmed = True
db.session.commit()
...and yet when I deploy the boolean column 'emailconfirmed' never is update to True. I have confirmed with print statements that update_this.emailconfirmed is False at the exact point in the code shown above... I just can't seem to update that value. Does anybody know what tests I can do, what imports I should check etc. etc.
Right now this is the top of my main .py file where the above code appears
from flask import Flask, render_template, request, session, redirect, url_for, make_response
# the following line imports from models.py
from models import db, User
# the following line imports SignupForm from forms.py
from forms import SignupForm, LoginForm
from flask_mail import Mail, Message
from itsdangerous import URLSafeTimedSerializer
# Production (causes Heroku to redirect to SSL)
from flask_sslify import SSLify
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
sslify = SSLify(app)
sslify = SSLify(app, subdomains=True)
app.config.from_pyfile('config_file.cfg')
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
db = SQLAlchemy(app)
mail = Mail(app)
ts = URLSafeTimedSerializer(app.config['SECRET_KEY'], salt=app.config['SALT'])
and this is my models.py file
from flask_sqlalchemy import SQLAlchemy
from werkzeug import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(100))
lastname = db.Column(db.String(100))
role = db.Column(db.String(20))
roleapproved = db.Column(db.Boolean)
school = db.Column(db.String(100))
email = db.Column(db.String(120), unique=True)
emailconfirmed = db.Column(db.Boolean)
pwdhash = db.Column(db.String(100))
def __init__(self, firstname, lastname, role, school, email, password):
self.firstname = firstname.title()
self.lastname = lastname.title()
self.role = role.lower()
if role.lower() == 'student':
self.roleapproved = True
if role.lower() == 'teacher':
self.roleapproved = False
self.school = school.title()
self.email = email.lower()
self.set_password(password)
self.emailconfirmed = False
def set_password(self, password):
self.pwdhash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.pwdhash, password)
def __repr__(self):
return '<User {0}>'.format(self.email)
Any help on doing the update I mentioned above would be greatly appreciated!!
Ideally you want to maintain a single session throughout your application lifecycle. This way it makes it easy to reason about and you avoid binding sessions to individual models.
Thanks #Ilja Everila
In main.py instead of initializing SQLAlchemy
you should write,
db.init_app(app)
Define a save instance method for your User model.
def save(self):
"""Saves model object instance
"""
db.session.add(self)
db.session.commit()
You can call this method to save the instance as
update_this.save()
Another way to update the entity is to get the specific object session before committing
from sqlachemy.orm import session
...
session = session.object_session(update_this)
session.commit()

Categories

Resources