My database looks like this:
CREATE TYPE APP_ROLE AS ENUM ('admin', 'user');
CREATE TABLE IF NOT EXISTS users (
user_id SERIAL PRIMARY KEY,
login VARCHAR ( 50 ) UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role APP_ROLE NOT NULL
);
I wrote a simple Flask/SQLAlchemy/Python code to test inserting to db. However, it seems that my object is added only once, because no matter how many time I run the script (python3 testdb.py) it shows that there's only one user in db. What Im doing wrong?
#!/usr/bin/env python3
import os
from flask import Flask, render_template, request, url_for, redirect
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import inspect
from sqlalchemy import create_engine
from sqlalchemy import Enum
from werkzeug.security import generate_password_hash
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
DEBUG = False
TESTING = False
CSRF_ENABLED = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = "postgresql://brian:1234#127.0.0.1:5432/example_db"
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
class Users(db.Model):
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(50), unique=True)
password_hash = db.Column(db.String(128))
role = db.Column(Enum("admin", "user", name="app_role", create_type=False))
def __init__(self, login, password_hash, role):
self.login = login
self.password_hash = password_hash
self.role = role
#property
def password(self):
raise AttributeError('Password is not a readable attribute')
#password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def to_json(self):
pass
def is_administrator(self):
pass
if __name__ == '__main__':
u1 = Users('brian', '2345643245', 'admin')
db.create_all()
db.session.add(u1)
db.session.add(u1)
db.session.add(u1)
db.session.flush()
users1 = Users.query.all()
for user in users1:
print(user.login, user.password_hash, user.role)
You are entering 3 times a record that has the exact same value for a field defined as UNIQUE. This is not allowed to do it at the database level, try to enter 3 different users with 3 different values for the login field.
Related
I'm trying to make a pair of tables with a many-many relationship. Here's my code to set up the tables:
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import IntegrityError
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
recipe_ingredient = db.Table('recipe_ingredient',
db.Column('recipe_name', db.String(64), db.ForeignKey('recipe.name')),
db.Column('ingredient_name', db.String(64), db.ForeignKey('ingredient.name'))
)
class Recipe(db.Model):
name = db.Column(db.String(64), primary_key = True)
ingredients_for = db.relationship('Ingredient', secondary='recipe_ingredient', backref='recipes_for')
def __repr__(self):
return f'<Recipe: {self.name}>'
class Ingredient(db.Model):
name = db.Column(db.String(64), primary_key = True)
def __repr__(self):
return f'<Ingredient: {self.name}>'
Now, here's some test code to try and add data to the tables:
db.create_all()
r0 = Recipe(name='eggsandwich')
db.session.merge(r0)
r1 = Recipe(name='tomatoegg')
db.session.merge(r1)
i0 = Ingredient(name='egg')
db.session.merge(i0)
i1 = Ingredient(name='bread')
db.session.merge(i1)
i2 = Ingredient(name='tomato')
db.session.merge(i2)
db.session.commit()
r0.ingredients_for.append(i0)
r0.ingredients_for.append(i1)
r1.ingredients_for.append(i0)
r1.ingredients_for.append(i2)
db.session.commit()
print([i.name for i in i.ingredients_for])
When I run this, it prints "['eggsandwich', 'tomatoegg']", as expected. However, in DB Browser, the recipe_ingredient table is empty:
Why isn't this table populating? How do I get the relationships to show up there?
I tried adding a second table to my website database. First one works and is where I collect inputted e-mails. I wanted a second table that connects inputted instagram handles. So I made this code for my app.py file:
from flask import Flask, jsonify, request, send_from_directory
from flask_sqlalchemy import SQLAlchemy
import datetime
from flask_marshmallow import Marshmallow
from flask_cors import CORS
app = Flask(__name__, static_url_path='', static_folder='frontend/build')
CORS(app)
app.config['SQLALCHEMY_DATABASE_URI']='mysql://***:***#localhost/pobble_data'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']= False
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Emails(db.Model):
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(120))
def __init__(self, email):
self.email = email
class EmailsSchema(ma.Schema):
class Meta:
fields = ('id','email')
class IGHandles(db.Model):
id = db.Column(db.Integer, primary_key = True)
ighandle = db.Column(db.String(120))
def __init__(self, ighandle):
self.ighandle = ighandle
class IGHandlesSchema(ma.Schema):
class Meta:
fields = ('id','ighandles')
email_schema = EmailsSchema()
emails_schema = EmailsSchema(many=True)
ighandle_schema = IGHandlesSchema()
ighandles_schema = IGHandlesSchema(many=True)
#app.route("/", defaults={'path':''})
def serve(path):
return send_from_directory(app.static_folder, 'index.html')
#app.route('/get', methods = ['GET'])
def get_emails():
all_emails = Emails.query.all()
results = emails_schema.dump(all_emails)
return jsonify(results)
def get_ighandles():
all_ighandles = IGHandles.query.all()
results = ighandles_schema.dump(all_ighandles)
#app.route('/get/<id>', methods = ['GET'])
def post_emails(id):
results = Emails.query.get(id)
return email_schema.jsonify(results)
def post_ighandles(id):
results = IGHandles.query.get(id)
return ighandle_schema.jsonify(results)
#app.route('/add', methods = ['POST'])
def add_email2():
email = request.json['email']
entry = Emails(email)
db.session.add(entry)
db.session.commit()
return email_schema.jsonify(entry)
def add_ighandles():
ighandle = request.json['ighandle']
entry = IGHandles(ighandle)
db.session.add(entry)
db.session.commit()
if __name__ == "__main__":
app.run(debug=True)
I saved all of this and went to my python interpreter and ran
from app import db and
db.create_all()
and I get two tables: emails and ig_handles
I expected the following two tables because they are the class names: Emails and IGHandles.
In fact I searched all of my code and don't see ig_handles anywhere so am not sure where it gets that from. I think it's some sort of cached name I might have used when I was experimenting with making the tables earlier.
I've tried running db.session.expire_all() and that doesn't help.
I'm trying to build a REST API application in Python 3.4, using Flask, SQLAlchemy and Marshmallow.
In my model I have a User class with a one to many relationship with a MailAddress class.
If I run a GET request, I manage to read data from the DB, and the data is correctly returned as a JSON string.
Instead, if I run a POST request with the JSON serialization of my User object with some MailAddresses, I get this error:
File "X:\test\...\site-packages\sqlalchemy\orm\collections.py", line 785, in bulk_replace
constants = existing_idset.intersection(values or ())
File "X:\test\...\site-packages\sqlalchemy\util\_collections.py", line 612, in intersection
result._members.update(self._working_set(members).intersection(other))
TypeError: unhashable type: 'dict'
I've tried adding a __hash__ function to my model classes (as suggested at sqlalchemy: TypeError: unhashable type creating instance, sqlalchemy) but that didn't help.
Here is a complete code example that shows this problem:
from flask import Flask, request
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow import fields
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
class Config(object):
SQLALCHEMY_DATABASE_URI = '<CONNECTION STRING HERE>'
SQLALCHEMY_TRACK_MODIFICATIONS = False
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
ma = Marshmallow(app)
# Model
class MailAddress(db.Model):
__tablename__ = 'mail_addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
mail_type = Column(String(200), nullable=False)
mail = Column(String(200), nullable=False)
def __init__(self, mail, mail_type):
self.mail = mail
self.mail_type = mail_type
class MailAddressSchema(ma.ModelSchema):
class Meta:
model = MailAddress
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
mail_addresses = relationship('MailAddress', backref='user')
def __init__(self, name, mail_addresses):
self.name = name
self.mail_addresses = mail_addresses
def __hash__(self):
return hash(self.name)
class UserSchema(ma.ModelSchema):
mail_addresses = fields.Nested(MailAddressSchema, many = True, only=('mail', 'mail_type'))
class Meta:
model = User
# Routes
user_schema = UserSchema()
#app.route('/api/v0/user', methods=['GET'])
def user_get():
users = db.session.query(User).all()
return user_schema.jsonify(users, many = True), 200
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance = user_schema.make_instance(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
# Main
if __name__ == '__main__':
app.run('localhost', 5555)
Is there something I'm missing?
Use load instead make_instance
#app.route('/api/v0/user', methods=['POST'])
def user_create():
new_instance, errors = user_schema.load(request.json)
db.session.add(new_instance)
db.session.commit()
return user_schema.jsonify(new_instance), 201
I am able to perform 'select' queries to my Mysql database.
However, the "insert" ones don't change the database, only the python objects. So when I restart the flask app, all the commited(?) editions are gone.
Views:
from flask import Flask, render_template, request, redirect, url_for, flash, Response
from sqlalchemy import exc
from models import *
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'kjhS7usfHGJHDez78'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://admin:admin#127.0.0.1:3306/grenier'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
db.create_all()
#app.route('/ajax/submit_edition', methods=['POST'])
def submit_edition():
if request.method == 'POST':
given_id=1
show = Shows.query.filter_by(id=given_id).first()
show.short_description = "Hello"
try:
db.session.commit()
db.session.flush()
return "ok"
except exc.SQLAlchemyError:
return "Error in commiting the edition"
No particular exception is found. The route always returns "ok".
Models:
from sqlalchemy import Column, ForeignKey
from sqlalchemy.dialects.mysql import LONGTEXT, YEAR
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Shows(db.Model):
__tablename__ = "shows"
id = Column(db.Integer, ForeignKey("programmation.id"), primary_key=True)
date = Column(db.DATETIME)
title = Column(db.VARCHAR(50))
short_description = Column(db.VARCHAR(200))
type = Column(db.VARCHAR(20))
background_image = Column(db.VARCHAR(150))
content = Column(LONGTEXT)
def serialize(self, whatTo):
result = {}
if 'id' in whatTo:
result['id'] = self.id
if 'date' in whatTo:
result['date'] = str(self.date)
if 'title' in whatTo:
result['title'] = self.title
if 'short_description' in whatTo:
result['short_description'] = self.short_description
if 'type' in whatTo:
result['type'] = self.type
if 'background_image' in whatTo:
result['background_image'] = self.background_image
if 'content' in whatTo:
result['content'] = self.content
return result
class Programmation(db.Model):
__tablename__ = "programmation"
id = Column(db.Integer, primary_key=True)
semester = Column(db.Integer)
year = Column(YEAR)
When I look at the logs, the sql request is created for the select. But for the insert commit(), there is nothing.
Thank you !
The problem is usage of two different SQLAlchemy instance. When you call db.create_all() method it creates all tables which which inherited from db.Model but in your views you don't have any model inherited from db = SQLAlchemy(app). All your models inherited from other SQLAlchemy instance. To fix this import the db object from views to models module and use it as parent class for inheritance:
#models.py
from views import db
#db = SQLAlchemy() #remove this line
class Show(db.Model):
...
I'm trying to get a server for an app working, but I'm getting an error upon login:
[!] Object '<User at 0x7f12bc185a90>' is already attached to session '2' (this is '3')
It seems the session I'm adding is already on the database. This is the snippet of code that is causing the problem:
#app.route('/login', methods=['POST'])
def login():
u = User.query.filter(User.username == request.form["username"]).first()
if not u or u.password != request.form["password"]:
return error("E1")
s = Session.get_by_user(u)
if s is not None:
db_session.delete(s)
db_session.commit()
print db_session.execute("SELECT * FROM sessions").fetchall()
s = Session(u)
db_session.add(s)
db_session.commit()
return jsonify(s.values)
As you can see, I'm printing the content from the sessions table before trying to add anything, and it is empty! ([])
What else could be causing this?
Here is the 'Session' implementation:
class Session(Base):
__tablename__ = "sessions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), unique=True)
user = relationship(User)
key = Column(String(50), unique=True)
created = Column(DateTime)
def __init__(self, user=None):
self.user = user
self.key = base64.encodestring(os.urandom(24)).strip()
self.created = datetime.now()
def __repr__(self):
return '<Session %r>' % (self.key)
#property
def values(self):
return {"username" : self.user.username,
"key" : self.key,
"created" : str(self.created),
}
#classmethod
def get_by_key(cls, key):
s = cls.query.filter(cls.key == key).first()
#print datetime.now() - s.created
if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
s = None
return s
#classmethod
def get_by_user(cls, user):
s = cls.query.filter(cls.user == user).first()
if s and datetime.now() - s.created > settings.SESSION_LIFETIME:
s.query.delete()
db_session.commit()
s = None
return s
As #marcinkuzminski mentioned, you can't add an object that is already attached to another session. Just pulling in the original session from the object with object_session() is risky, though, if you aren't sure that session originated in the same thread context you're currently operating in. A thread-safe method is to use merge():
local_object = db_session.merge(original_object)
db_session.add(local_object)
db_session.commit()
Object you're trying to modify is already attached to another session.
Maybe you have wrong imports, and db_session is a new instance.
A good workaround to this is to extract the current bound session and use it:
Instead of:
db_session.add(s)
Do:
current_db_sessions = db_session.object_session(s)
current_db_sessions.add(s)
This db session issue will arise if you are having server.py and model.py importing each other
server.py
from flask import Flask
import os
import models as appmod #################### importing models here in server.py<----------
app = Flask(__name__) # L1
app.config.from_object(os.environ['APP_SETTINGS']) # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # L3
database = SQLAlchemy(app) # L4
db = database # L5
#app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
data_rec = appmod.Employee.query.get(id)
db.session.delete(data_rec)
db.session.commit()
return "DELETE"
if __name__ == '__main__':
app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)
models.py
from server import db #################### importing server in models.py here <------------
from sqlalchemy.dialects.mysql import JSON
class Employee(db.Model):
__tablename__ = 'employe_flask'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
datetime = db.Column(db.DateTime)
designation = db.Column(db.String(128))
def __init__(self, name, datetime, designation):
self.name = name
self.datetime = datetime
self.designation = designation
#staticmethod
def delete_rec(data_rec):
db.session.delete(data_rec)#.delete
db.session.commit()
def __repr__(self):
record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
return record
Remove the line L1 to L5 from server.py and place it in common file like settings.py
and import 'app' and 'db' to server.py and import db in models.py
like this files below
server.py
from flask import Flask
import os
import models as appmod
from settings import app, db
#app.route('/item_delete/<id>', methods=['DELETE'])
def remove_method(id = None):
data_rec = appmod.Employee.query.get(id)
db.session.delete(data_rec)
db.session.commit()
return "DELETE"
if __name__ == '__main__':
app.run(port=5000, host='0.0.0.0',debug=True,threaded=True)
settings.py
import os
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__) # L1
app.config.from_object(os.environ['APP_SETTINGS']) # L2
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # L3
database = SQLAlchemy(app) # L4
db = database # L5
models.py
from settings import db
from sqlalchemy.dialects.mysql import JSON
class Employee(db.Model):
__tablename__ = 'employe_flask'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
datetime = db.Column(db.DateTime)
designation = db.Column(db.String(128))
def __init__(self, name, datetime, designation):
self.name = name
self.datetime = datetime
self.designation = designation
#staticmethod
def delete_rec(data_rec):
db.session.delete(data_rec)#.delete
db.session.commit()
def __repr__(self):
record = {"name":self.name,"date":self.datetime.ctime(),"designation":self.designation}.__str__()
return record
This error means the record you are handling is attached to 2 different session(db)!
One of the reasons is that you may define your model with one db = SQLAlchemy(app) and add/insert/modify the database with another!
My solution is UNIFORMING THE DB!
try this:
u = db.session.query(User).filter(User.username == request.form["username"]).first()
Instead of this:
u = User.query.filter(User.username == request.form["username"]).first()
I had this problem too.
I created a test_file.py and added this code:
from app import app
from models import Tovar
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
tovardel = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
db.session.delete(tovardel)
tovar = Tovar.query.filter(Tovar.region == 1 and Tovar.price == 12).first()
print(tovar.description)
and when I ran the code I got this error:
Object '<Tovar at 0x7f09cbf74208>' is already attached to session '1' (this is '2')
PROBLEM SOLVING:
If you have db = SQLAlchemy(app) in, for example, text_file.py, and in app.py, you get this problem all time. You should del db = SQLAlchemy(app), and import db from app from app import db
I faced the same issue. I was defining models in a separate file and I had to call SQLAlchemy twice. That's why there were two different sessions were running.
I solved this by doing following:
In case you are trying to remove an object from db:
Just create the removeObject function inside the model