sqlalchemy how to bind metadata to the table - flask-sqlalchemy

I have tried the solution mentioned in sqlalchemy create tables and also referred to flask documentation http://flask-sqlalchemy.pocoo.org/2.1/contexts/. I am facing problems creating the database tables using create_all(). Here is my code.
>>> from flask import Flask
>>> app = Flask('myflaskapp')
>>> from flask_sqlalchemy import SQLAlchemy
>>> app.url_map.strict_slashes = False
>>> app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql+psycopg2://my_dbuser:my_dbuser_password#localhost/my_dev_db'
>>> app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
>>> from myflaskapp.models import AdRequest, AdResult
>>> AdRequest.__table__
Table('ad_request', MetaData(bind=None), Column('id', Integer(), table=<ad_request>, primary_key=True, nullable=False), Column('status', String(length=10), table=<ad_request>, default=ColumnDefault('NEW')), Column('status_msg', String(length=100), table=<ad_request>, default=ColumnDefault('')), Column('query_str', String(length=100), table=<ad_request>), schema=None)
>>> db = SQLAlchemy(app)
>>> db.engine
Engine(postgresql+psycopg2://my_dbuser:***#localhost/my_dev_db)
>>> db.metadata.create_all(db.engine)
>>> db.engine
Engine(postgresql+psycopg2://my_dbuser:***#localhost/my_dev_db)
>>> db
<SQLAlchemy engine=postgresql+psycopg2://my_dbuser:***#localhost/my_dev_db>
>>> db.session.commit()
On the postgres console a "\dt" does not list the new table ad_request that should get created as a result of running create_all.
Furthermore, I added debug as follows app['SQLALCHEMY_ECHO']=True and I get the following output which I cannot decipher.
>>> app.config['SQLALCHEMY_ECHO']=True
>>> from models import *
>>> with app.app_context():
... db.create_all()
...
2017-07-17 10:24:09,766 INFO sqlalchemy.engine.base.Engine select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where pg_catalog.pg_table_is_visible(c.oid) and relname=%(name)s
2017-07-17 10:24:09,766 INFO sqlalchemy.engine.base.Engine {'name': 'ad_request'}
2017-07-17 10:24:09,768 INFO sqlalchemy.engine.base.Engine select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where pg_catalog.pg_table_is_visible(c.oid) and relname=%(name)s
2017-07-17 10:24:09,769 INFO sqlalchemy.engine.base.Engine {'name': 'ad_result'}
2017-07-17 10:24:09,772 INFO sqlalchemy.engine.base.Engine
CREATE TABLE ad_request (
id SERIAL NOT NULL,
status VARCHAR(10),
status_msg VARCHAR(100),
query_str VARCHAR(100),
PRIMARY KEY (id)
)
2017-07-17 10:24:09,772 INFO sqlalchemy.engine.base.Engine {}
2017-07-17 10:24:09,779 INFO sqlalchemy.engine.base.Engine COMMIT
2017-07-17 10:24:09,780 INFO sqlalchemy.engine.base.Engine
CREATE TABLE ad_result (
id SERIAL NOT NULL,
uuid_s VARCHAR(50),
score FLOAT,
ad_request_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(ad_request_id) REFERENCES ad_request (id)
)
2017-07-17 10:24:09,780 INFO sqlalchemy.engine.base.Engine {}
2017-07-17 10:24:09,784 INFO sqlalchemy.engine.base.Engine COMMIT
I noticed that AdRequest.table shows Table('ad_request', MetaData(bind=None), ...
How can I make AdRequest bind to the db.metadata ?
Following is my models.py
# models.py
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow()
class AdRequest(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
status = db.Column(db.String(10), default="NEW")
status_msg = db.Column(db.String(100), default="")
query_str = db.Column(db.String(100))
def __init__(self, query_str):
self.query_str = query_str
def __repr__(self):
return '<AdRequest %r, %r, %r, %r>' % (self.id, self.query_str, self.status, self.status_msg)
def as_dict(self):
return {'id': self.id,
'query_str': self.query_str,
'status': self.status,
'status_msg': self.status_msg,
}
class AdResult(db.Model):
id = db.Column(db.Integer, primary_key=True)
uuid_s = db.Column(db.String(50))
score = db.Column(db.Float)
ad_request_id = db.Column(db.Integer, db.ForeignKey('ad_request.id'))
def __init__(self, uuid_s, score, ad_request_id):
self.uuid_s = uuid_s
self.score = score
self.ad_request_id = ad_request_id
def __repr__(self):
return '<AdResult %r, %r, %s>' % (self.id, self.uuid_s, self.ad_request_id)
class AdRequestSchema(ma.Schema):
class Meta:
# Fields to expose
fields = ('id', 'status', 'status_msg', 'query_str')

Order matters when it comes to SQLAlchemy. You have to use the same SQLAlchemy instance when you declare your models and when you use them. In example you provided, you create your database twice - once in models file and once in Python console.
Just create a separate file called database.py with two lines:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Then in your models import it and use as base for your models:
from database import db
class AdRequest(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# rest of your models file
And create your app with:
from flask import Flask
from database import db
from models import AdRequest, AdResult
app = Flask('myflaskapp')
app.url_map.strict_slashes = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
with app.app_context():
db.init_app(app) # this is important!
db.create_all()
Please let me know if it helped.
Edit: you can check if my code works by running it and checking out if test.db file was created. If the file is there but you still have your problem, maybe it is PostgreSQL specific issue.

Related

delete a record in flask sql_alchemy [duplicate]

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

SQLAlchemy IntegrityError Relationship using object already in the database

This is my model, made with classical mapping, classes A and B are working accordingly
import sqlalchemy as sa
from sqlalchemy.orm import mapper, relationship
from domain.a import A
from domain.b import B
from app_extentions import metadata
a_table = sa.Table(
'a', metadata,
sa.Column('description', sa.String(30), primary_key=True), # I think this is important
sa.Column('value_x', sa.Boolean()),
)
b_table = sa.Table(
'b', metadata,
sa.Column('id', sa.BigInteger, primary_key=True, autoincrement=True),
sa.Column('description', sa.String(50), sa.ForeignKey(a_table.c.description), nullable=False),
sa.Column('value_y', sa.String(20), nullable=True),
)
mapper(A, a_table)
mapper(B, b_table, properties={
'rel': relationship(
A, primaryjoin=(a_table.c.description == b_table.c.description)
),
})
When I do this using pytest
obj1:A = retrieve_A_object() # A is already in the DB, I get it
obj2:B = create_B_object() # this is created now, it is brand new
obj2.rel = obj1
session = get_session()
session.add(obj2)
session.commit()
SQLAlchemy raises an error
def do_execute(self, cursor, statement, parameters, context=None):
> cursor.execute(statement, parameters)
E sqlalchemy.exc.IntegrityError: (psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "a_pkey"
E DETAIL: Key (description)=('MY DESCRIPTION') already exists.
I know that it's already in the db, I want to save B object
How can i solve this? Why is this happening?

Nested fields with mashmallow_sqlalchemy and flask_marshmallow

I am trying to get a nested serialized output, but all I get is the 'many' side of the many-to-one's table's primary key.
Expected output:
[{'part_numbers':
{'part_number': '23103048', 'description': 'blue product'},
'machines': 2,
'percent_complete': 5.0,
'serial_number': '001',
'timestamp_created': 2020-03-12T15:4:23.135098},
{'part_numbers':
{'part_number': '44444009', 'description': 'red product'},
'machines': 1,
'percent_complete': 60.0,
'serial_number': '002',
'timestamp_created': '2020-03-12T15:44:23.135098'}]
Actual output:
[{'id': 3,
'machines': 2,
'percent_complete': 5.0,
'serial_number': '001',
'timestamp_created': 2020-03-12T15:4:23.135098},
{'id': 1,
'machines': 1,
'percent_complete': 60.0,
'serial_number': '0002',
'timestamp_created': '2020-03-12T15:44:23.135098'}]
These are my files. I have tried adding part_numbers = ma.Nested(PartNumbers) to the Machine schema inside models.py, but it didn't change the result!
my_app
|--- my_app
|--- \__init\__.py
|--- models.py
|--- views.py
models.py
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
db = SQLAlchemy()
ma = Marshmallow()
### Models ###
class Machine(db.Model):
id = db.Column(db.Integer, primary_key=True)
serial_number = db.Column(db.Text, unique=True)
percent_complete = db.Column(db.Float)
part_number_id = db.Column(db.Integer, db.ForeignKey('PartNumbers.id'))
timestamp_created = db.Column(db.DateTime, default=datetime.utcnow)
class PartNumbers(db.Model):
__tablename__ = 'PartNumbers'
id = db.Column(db.Integer, primary_key=True)
part_number = db.Column(db.Text, unique=True)
description = db.Column(db.Text)
machines = db.relationship('Machines', backref='machines', lazy='dynamic')
### Schemas ###
class PartNumbersSchema(SQLAlchemyAutoSchema):
class Meta:
model = PartNumbers
include_fk = True
class MachineSchema(SQLAlchemyAutoSchema):
class Meta:
model = Machines
include_relationships = True
__init__.py
from flask import Flask
from .models import db, ma
from .views import my_app
app = Flask(__name__)
db.init_app(app)
ma.init_app(app)
app.register_blueprint(my_app)
views.py
from flask import Blueprint
from .models import db, Machines, MachineSchema, PartNumbers
my_app = Blueprint("my_app", __name__)
machines_schema = MachineSchema(many=True)
#my_app.route('/')
def home()
machines = db.session.query(Machines).all()
return machines_schema.dump(machines)
Define part_number in the Machine class:
class Machine(db.Model):
id = db.Column(db.Integer, primary_key=True)
serial_number = db.Column(db.Text, unique=True)
percent_complete = db.Column(db.Float)
part_number_id = db.Column(db.Integer, db.ForeignKey('PartNumbers.id'))
timestamp_created = db.Column(db.DateTime, default=datetime.utcnow)
part_numbers = db.relationship('PartNumbers')
You have to define part_numbers in the MachineSchema explicitly:
from marshmallow_sqlalchemy import fields
class MachineSchema(SQLAlchemyAutoSchema):
class Meta:
model = Machines
part_numbers = fields.Nested(PartNumbersSchema)
I assumed part_numbers is a single object (1:1 relationship). Otherwise, you have to add many=True to part_numbers.
More details: https://marshmallow.readthedocs.io/en/stable/nesting.html
[edited to incorporate correction in comment]

import and mapping csv to sqlalchemy dynamically

I am creating database using sqlalchemy in flask app and filling the database with existing CSV with selected columns from it so I use pandas here is my classes creation
I need to add company objects and commit them in dynamic way , but that way does not work , the csv file is not small about 20,000 record I can not add them manually ,so any suggestions to add them in dynamic way?
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from flask import jsonify
Base = declarative_base()
class Company(Base):
__tablename__ = 'forbesglobal2000_2016'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
profits = Column(String(250), nullable=False)
marketValue = Column(String(250), nullable=False)
revenue = Column(String(250), nullable=False)
industry = Column(String(250), nullable=False)
class SIC(Base):
__tablename__ = "SIC"
id = Column(Integer, primary_key=True)
SIC = Column(Integer, nullable=False)
Industry_name = Column(String(250),ForeignKey('forbesglobal2000_2016.industry'))
Indusrty = relationship(Company)
# configuration part
engine = create_engine('sqlite:///CompainesData.db')
Base.metadata.create_all(engine)
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from database_setup import *
import pandas as pd
# opening connection with database
engine = create_engine('sqlite:///CompainesData.db')
Base.metadata.bind = engine
# Clear database
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
DBSession = sessionmaker(bind=engine)
session = DBSession()
df = pd.read_csv("forbesglobal2000-2016.csv")
df1 = pd.read_csv("SIC.csv")
# market valuation, revenue, profits and industry
profit_column = df.profits
name_column = df.name
industry_column = df.industry
revenue_column = df.revenue
marketvalue_column = df.marketValue
industry_column_f = df1.Description
SIC_column = df1.SICCode
company = []
i = 1
while i < name_column.__len__():
company[i] = Company(name = name_column[i] , industry=industry_column[i], marketValue = marketvalue_column[i] , profits = profit_column[i] ,
revenue = revenue_column[i] )
i = i +1
for i in company:
session.add(i)
session.commit()
# printing test
com = session.query(Company).all()
for f in com:
print(f.name)
print(f.industry)
print(f.profits)
print(f.revenue)
print(f.marketValue)
If you want to load data from csv files to database just use df.to_sql() function it allows you to do that. For example :
df.to_sql(con=engine, name=airlines.__tablename__, if_exists='replace',index=False)
Pay attention to index=False, it's used to ignore pandas id column.
I think the index will start at 0 and not 1:
i = 1
should be
i = 0
can you try that?

Sqlalchemy: Propagation of updates across multiple (linked) relationships

I show here an (artificial) example of three linked tables: ParentA, ChildA, and ChildAA. ChildA is related to the primary key (PK) of ParentA via foreign key, and ChildAA relates to the same key in ChildA. In this way ChildAA links to the primary key of the ParentA. I would expect that when I make a change to the ParentA PK this change propagates back to the corresponding ChildAA's attribute, but it doesn't.
Thanks in advance!
(I apologize if this has been answered or documented before, I really couldn't find anything.)
The Code:
from sqlalchemy import *
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class ParentA(Base):
__tablename__ = 'tbl_parentA'
pid = Column(Integer, primary_key=True)
childA = orm.relationship("ChildA", passive_updates=False, backref='parentA')
class ChildA(Base):
__tablename__ = 'tbl_childA'
attrib1 = Column(String, nullable=True)
parentA_id = Column(Integer, ForeignKey(ParentA.pid), primary_key=True)
childAA = orm.relationship("ChildAA", passive_updates=False, backref="childA")
# This class is related to Parents through ChildA
class ChildAA(Base):
__tablename__ = 'tbl_childAA'
cid = Column(Integer, primary_key=True)
attrib1 = Column(String, nullable=True)
parentA_id = Column(Integer, ForeignKey(ChildA.parentA_id))
def clear_db(db):
tmp = db.echo
db.echo = False
metadata = MetaData(bind=db)
metadata.reflect(db)
for table in reversed(metadata.sorted_tables):
table.drop(db)
metadata.clear()
db.echo = tmp
if __name__ == '__main__':
# SQLite Connection
db = create_engine('sqlite:///linked_updates.db')
# db.echo = True
# Initalize Objects
pa1 = ParentA()
ca1 = ChildA(attrib1='ca1 str')
caa1= ChildAA(attrib1='caa1 str')
# Assign a parent to ChildA
ca1.parentA = pa1
# Assign a parent to ChildAA
caa1.childA = ca1
# Initialize clean DB & session
clear_db(db)
Base.metadata.create_all(db)
session = orm.create_session(db)
# Write to DB
session.add_all([pa1, ca1, caa1])
session.flush()
print 'After flush, we have: ', caa1.parentA_id, '==', caa1.childA.parentA_id
# Induce change, check propagation
pa1.pid = 2
session.flush()
print 'I expect: ', caa1.parentA_id, '==', caa1.childA.parentA_id
print 'END'