I currently have some code that takes the first value in the dataframe and either inserts it into MySQL, or updates it (depending if the id is already in the DB or not).
However, I need to be able to create a loop to loop through all the values in the dataframe instead of just one. But I'm not sure how to do it.
Here is my code for just one value:
class Example(db.Model):
__tablename__ = 'sessionAttendances'
_id = db.Column('_id', db.Unicode, primary_key=True)
wondeID = db.Column('wondeID', db.Unicode)
date = db.Column('date', db.Unicode)
timezoneType = db.Column('timezoneType', db.Unicode)
timezone = db.Column('timezone', db.Unicode)
createdAt = db.Column('createdAt', db.Date)
session = db.Column('session', db.Unicode)
updatedAt = db.Column('updatedAt', db.Date)
def __init__(self, _id, wondeID, date, timezoneType, timezone, createdAt, session, updatedAt):
self._id = _id
self.wondeID = wondeID
self.date = date
self.timezoneType = timezoneType
self.timezone = timezone
self.createdAt = createdAt
self.session = session
self.updatedAt = updatedAt
#classmethod
def add_or_update(cls, _id, wondeID, date, timezoneType, timezone, createdAt, session, updatedAt):
entity = cls.query.filter_by(_id=sessionAttendance._id.iloc[0]).first()
if not entity:
entity = cls(sessionAttendance._id.iloc[0], sessionAttendance.wondeID.iloc[0], sessionAttendance.date.iloc[0], sessionAttendance.timezoneType.iloc[0], sessionAttendance.timezone.iloc[0], sessionAttendance.createdAt.iloc[0], sessionAttendance.session.iloc[0], sessionAttendance.updatedAt.iloc[0])
db.session.add(entity)
db.session.commit()
print("Adding Record")
else:
entity.attendanceCode = 'late'
db.session.commit()
print("Updating Record")
return entity
example = Example(sessionAttendance._id.iloc[0], sessionAttendance.wondeID.iloc[0], sessionAttendance.date.iloc[0], sessionAttendance.timezoneType.iloc[0], sessionAttendance.timezone.iloc[0], sessionAttendance.createdAt.iloc[0], sessionAttendance.session.iloc[0], sessionAttendance.updatedAt.iloc[0])
example.add_or_update(sessionAttendance._id.iloc[0], sessionAttendance.wondeID.iloc[0], sessionAttendance.date.iloc[0], sessionAttendance.timezoneType.iloc[0], sessionAttendance.timezone.iloc[0], sessionAttendance.createdAt.iloc[0], sessionAttendance.session.iloc[0], sessionAttendance.updatedAt.iloc[0])
examples = Example.query.all()
for ex in examples:
print (ex.date)
Apparently the MySQL method for bulk upsert is rather slow so maybe looping is your best option.
Here is how you'd do it using .iterrows():
for idx,row in sessionAttendance.iterrows():
example = Example(row._id, row.wondeID, row.date, row.timezoneType,
row.timezone, row.createdAt, row.session, row.updatedAt)
example.add_or_update(row._id, row.wondeID, row.date, row.timezoneType,
row.timezone, row.createdAt, row.sessio, row.updatedAt)
Related
How do I implement functionality where a user can only update an entry when the date_created and date_modified fields of the diary entry are the same?
This is what I have implemented. I have compared the date_created field of the entry model in the db to datetime.date.today().
Data model
class DiaryEntry():
def __init__(self):
self.title = ''
self.body = ''
self.date_modified = None
self.date_created = datetime.date.today()
def save(self, current_user_email):
# insert data into db
query = "INSERT INTO entries (owner_id, title, body, date_created, date_modified) \
VALUES ((SELECT user_id from users where email ='{}'), '{}', '{}', '{}','{}')" \
. format(current_user_email,
self.title,
self.body,
self.date_created,
self.date_modified
)
db.execute(query)
Method
def update_diary_entry(self,entry_id):
query = "select * from entries where entry_id='{}'".format(entry_id)
result = db.execute(query)
entry = result.fetchone()
data = request.get_json()
date_created = entry[4]
if date_created == datetime.date.today():
query = "update entries set title='{}',body='{}' where entry_id='{}'"\
.format(data['title'], data['body'], int(entry_id))
db.execute(query)
return {'message': 'diary entry updated succesfully','date':date_created}, 406
else:
return {'message': 'diary entry can only be updated on the day it was created'}, 406
I am currently getting the second return statement. What could I be doing wrong?
It looks like you have date_created as a string (str) within update_diary_entry(). That will cause an invalid comparison to a Python datetime.date object unless you parse the string into the same type:
>>> import datetime
>>> date_created = '2018-07-29'
>>> date_created == datetime.date.today()
False
>>> datetime.datetime.strptime(date_created, '%Y-%m-%d').date() == datetime.date.today()
True
The classmethod strptime() parses a string that looks like a date into a datetime.datetime object. You need to then grab just the date component from this to enable the comparison that you want. If you have a differently-formatted date-string, see strftime() and strptime() Behavior.
#hybrid_method
# #paginate
def investors(self, **kwargs):
"""All investors for a given Custodian"""
ind_inv_type_id = InvestorType.where(description="Individual").first().id
inv_query = Investor.with_joined(InvestorAddress, InvestmentAddress, CustodianAddress) \
.filter_by(custodians_id=self.id) \
.with_joined(Investment) \
.filter_by(investor_types_id=ind_inv_type_id)
investors = Investor.where(None, False, inv_query, **kwargs)
temp_inv_query = Investor.with_joined(CustodianInvestor, Custodian)\
.filter_by(Custodian.id==self.id)
temp_investors = Investor.where(None, False, temp_inv_query, **kwargs)
return list(set(investors + temp_investors))
# end def investors
# #auth.access_controlled
class InvestorAddress(db.Model, EntityAddressMixin):
# Metadata
__tablename__ = 'investor_addresses'
# Database Columns
investors_id = db.Column(db.ForeignKey("investors.investors_id"),
nullable=False)
investor = db.relationship("Investor", foreign_keys=[investors_id],
backref=db.backref("InvestorAddress"))
# end class InvestorAddress
class InvestmentAddress(db.Model):
"""This model differs from other EntityAddress Models because it links to either an investor_address or an custodian_address."""
# Metadata
__tablename__ = 'investment_addresses'
# Database Columns
address_types_id = db.Column(
db.ForeignKey("address_types.address_types_id"),
nullable=False)
address_type = db.relationship("AddressType",
foreign_keys=[address_types_id],
backref=db.backref("InvestmentAddress"))
investments_id = db.Column(db.ForeignKey("investments.investments_id"),
nullable=False)
investment = db.relationship("Investment",
foreign_keys=[investments_id],
backref=db.backref("InvestmentAddress"))
investor_addresses_id = db.Column(db.ForeignKey(
"investor_addresses.investor_addresses_id"))
investor_address = db.relationship("InvestorAddress",
foreign_keys=[investor_addresses_id],
backref=db.backref("InvestmentAddress"))
custodian_addresses_id = db.Column(db.ForeignKey(
"custodian_addresses.custodian_addresses_id"))
custodian_address = db.relationship("CustodianAddress",
foreign_keys=[custodian_addresses_id],
backref=db.backref("InvestmentAddress")
)
# end class InvestmentAddress
class CustodianAddress(db.Model, EntityAddressMixin):
"""Defines the relationship between a Custodian and their addresses."""
# Metadata
__tablename__ = 'custodian_addresses'
# Database Columns
custodians_id = db.Column(db.ForeignKey(
"custodians.custodians_id"), nullable=False)
custodian = db.relationship("Custodian", foreign_keys=[custodians_id],
backref=db.backref("CustodianAddress"))
# end CustodianAddress
i have an application and this function is supposed to return a list of 'investors' for a given 'Custodian'. Now when it executes i get an error: "sqlalchemy.exc.ArgumentError: mapper option expects string key or list of attributes". The error comes from the 'join' in the 'inv_query'.
I have included my 3 models that im using for the Join.
As described in the documentation provided by you. here
You should provide string arguments(table names) in with_joined. Given you have defined the relationship
Investor.with_joined('investorAddressTable', 'investmentAddressTable, 'custodianAddressTable')
In case you can use session then you can query the ORM classes directly like
session.query(Investor).join(InvestorAddress).join(InvestmentAddress).join(CustodianAddress).all() # will assume you have set the foreign key properly
I've created models for my database:
class Album(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128))
year = db.Column(db.String(4))
tracklist = db.relationship('Track', secondary=tracklist,
backref=db.backref('albums',
lazy='dynamic'), lazy='dynamic')
class Track(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128))
class Artist(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
releases = db.relationship('Track', secondary=releases,
backref=db.backref('artists',
lazy='dynamic'), lazy='dynamic')
They are many-to-many related Album <--> Track <--> Artist
Next, I have this form:
class SearchForm(FlaskForm):
search_by_album = StringField('Album', validators=[Optional()])
search_by_artist = StringField('Artist', validators=[Optional()])
search_track = StringField('Track', validators=[Optional()])
year = StringField('Year', validators=[Optional(), Length(max=4)])
My idea is to give the user freedom in filling desired combination of forms (but at least one is required), so I've got this function, which recieves SearchForm().data (an immutable dict 'field_name': 'data'):
def construct_query(form):
query = db.session.query(*[field.label.text for field in form if field.data and field.name != 'csrf_token'])
if form.search_by_album.data:
query = query.filter(Album.title == form.search_by_album.data)
if form.search_by_artist.data:
query = query.filter(Artist.name == form.search_by_artist.data)
if form.search_track.data:
query = query.filter(Track.title == form.search_track.data)
if form.year.data:
query = query.filter(Album.year == form.year.data)
result = query.all()
return result
My question is if there is a more abstract way of adding filters in the function above? If one day I decide to add more columns to my tables (or even create new tables), I will have to add more monstrous ifs to constrcut_query(), which will eventually grow enormous. Or such an abstractions is not a pythonic way because "Explicit is better than implicit"?
PS
I know about forms from models, but I don't think that they are my case
One way would be associating the filter-attribute with the fields at some place, e.g. as a class attribute on the form itself:
class SearchForm(FlaskForm):
search_by_album = StringField('Album', validators=[Optional()])
search_by_artist = StringField('Artist', validators=[Optional()])
search_track = StringField('Track', validators=[Optional()])
year = StringField('Year', validators=[Optional(), Length(max=4)])
# map form fields to database fields/attributes
field_to_attr = {search_by_album: Album.title,
search_by_artist: Artist.name,
search_track: Track.title,
year: Album.year}
When building the query, you could then build the where clause in a pretty comfortable way:
def construct_query(form):
query = db.session.query(*[field.label.text for field in form if field.data and field.name != 'csrf_token'])
for field in form:
if field.data:
query = query.filter(form.field_to_attr[field] == field.data)
# or:
# for field, attr in form.field_to_attr.items():
# if field.data:
# query = query.filter(attr == field.data)
result = query.all()
return result
Adding new fields and attributes to filter on would then only translate to the creating the field and its mapping to an attribute.
I have a very simple User class definition:
class User(Base):
implements(interfaces.IUser)
__tablename__ = 'users'
#Fields description
id = Column(Integer, primary_key=True)
client_id = Column(Integer, ForeignKey('w2_client.id'))
client = relationship("Client", backref=backref('users', order_by=id))
I want to generate automatically a GUI to edit the object User (and other type of class). So I need to get all the meta data of the table, for example, I can do:
for c in User.__table__.columns:
print c.name, c.type, c.nullable, c.primary_key, c.foreign_keys
But I can not get any information about the relationship "client", the c.foreign_keys just shows me the table related to the foreign_keys but not the attribute "client" I've defined.
Please let me know if my question is not clear
It's true that is not readily available. I had to come up with my own function after some reverse-engineering.
Here is the metadata that I use. I little different than what you are are looking for, but perhaps you can use it.
# structure returned by get_metadata function.
MetaDataTuple = collections.namedtuple("MetaDataTuple",
"coltype, colname, default, m2m, nullable, uselist, collection")
def get_metadata_iterator(class_):
for prop in class_mapper(class_).iterate_properties:
name = prop.key
if name.startswith("_") or name == "id" or name.endswith("_id"):
continue
md = _get_column_metadata(prop)
if md is None:
continue
yield md
def get_column_metadata(class_, colname):
prop = class_mapper(class_).get_property(colname)
md = _get_column_metadata(prop)
if md is None:
raise ValueError("Not a column name: %r." % (colname,))
return md
def _get_column_metadata(prop):
name = prop.key
m2m = False
default = None
nullable = None
uselist = False
collection = None
proptype = type(prop)
if proptype is ColumnProperty:
coltype = type(prop.columns[0].type).__name__
try:
default = prop.columns[0].default
except AttributeError:
default = None
else:
if default is not None:
default = default.arg(None)
nullable = prop.columns[0].nullable
elif proptype is RelationshipProperty:
coltype = RelationshipProperty.__name__
m2m = prop.secondary is not None
nullable = prop.local_side[0].nullable
uselist = prop.uselist
if prop.collection_class is not None:
collection = type(prop.collection_class()).__name__
else:
collection = "list"
else:
return None
return MetaDataTuple(coltype, str(name), default, m2m, nullable, uselist, collection)
def get_metadata(class_):
"""Returns a list of MetaDataTuple structures.
"""
return list(get_metadata_iterator(class_))
def get_metadata_map(class_):
rv = {}
for metadata in get_metadata_iterator(class_):
rv[metadata.colname] = metadata
return rv
But it doesn't have the primary key. I use a separate function for that.
mapper = class_mapper(ORMClass)
pkname = str(mapper.primary_key[0].name)
Perhaps I should put the primary key name in the metadata.
How can I update a row's information?
For example I'd like to alter the name column of the row that has the id 5.
Retrieve an object using the tutorial shown in the Flask-SQLAlchemy documentation. Once you have the entity that you want to change, change the entity itself. Then, db.session.commit().
For example:
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
db.session.commit()
user = User.query.get(5)
user.name = 'New Name'
db.session.commit()
Flask-SQLAlchemy is based on SQLAlchemy, so be sure to check out the SQLAlchemy Docs as well.
There is a method update on BaseQuery object in SQLAlchemy, which is returned by filter_by.
num_rows_updated = User.query.filter_by(username='admin').update(dict(email='my_new_email#example.com')))
db.session.commit()
The advantage of using update over changing the entity comes when there are many objects to be updated.
If you want to give add_user permission to all the admins,
rows_changed = User.query.filter_by(role='admin').update(dict(permission='add_user'))
db.session.commit()
Notice that filter_by takes keyword arguments (use only one =) as opposed to filter which takes an expression.
This does not work if you modify a pickled attribute of the model. Pickled attributes should be replaced in order to trigger updates:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from pprint import pprint
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqllite:////tmp/users.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.PickleType())
def __init__(self, name, data):
self.name = name
self.data = data
def __repr__(self):
return '<User %r>' % self.username
db.create_all()
# Create a user.
bob = User('Bob', {})
db.session.add(bob)
db.session.commit()
# Retrieve the row by its name.
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Modifying data is ignored.
bob.data['foo'] = 123
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Replacing data is respected.
bob.data = {'bar': 321}
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
# Modifying data is ignored.
bob.data['moo'] = 789
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
Just assigning the value and committing them will work for all the data types but JSON and Pickled attributes. Since pickled type is explained above I'll note down a slightly different but easy way to update JSONs.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.JSON)
def __init__(self, name, data):
self.name = name
self.data = data
Let's say the model is like above.
user = User("Jon Dove", {"country":"Sri Lanka"})
db.session.add(user)
db.session.flush()
db.session.commit()
This will add the user into the MySQL database with data {"country":"Sri Lanka"}
Modifying data will be ignored. My code that didn't work is as follows.
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
db.session.merge(user)
db.session.flush()
db.session.commit()
Instead of going through the painful work of copying the JSON to a new dict (not assigning it to a new variable as above), which should have worked I found a simple way to do that. There is a way to flag the system that JSONs have changed.
Following is the working code.
from sqlalchemy.orm.attributes import flag_modified
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
flag_modified(user, "data")
db.session.merge(user)
db.session.flush()
db.session.commit()
This worked like a charm.
There is another method proposed along with this method here
Hope I've helped some one.
Models.py define the serializers
def default(o):
if isinstance(o, (date, datetime)):
return o.isoformat()
def get_model_columns(instance,exclude=[]):
columns=instance.__table__.columns.keys()
columns=list(set(columns)-set(exclude))
return columns
class User(db.Model):
__tablename__='user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
.......
####
def serializers(self):
cols = get_model_columns(self)
dict_val = {}
for c in cols:
dict_val[c] = getattr(self, c)
return json.loads(json.dumps(dict_val,default=default))
In RestApi, We can update the record dynamically by passing the json data into update query:
class UpdateUserDetails(Resource):
#auth_token_required
def post(self):
json_data = request.get_json()
user_id = current_user.id
try:
instance = User.query.filter(User.id==user_id)
data=instance.update(dict(json_data))
db.session.commit()
updateddata=instance.first()
msg={"msg":"User details updated successfully","data":updateddata.serializers()}
code=200
except Exception as e:
print(e)
msg = {"msg": "Failed to update the userdetails! please contact your administartor."}
code=500
return msg
I was looking for something a little less intrusive then #Ramesh's answer (which was good) but still dynamic. Here is a solution attaching an update method to a db.Model object.
You pass in a dictionary and it will update only the columns that you pass in.
class SampleObject(db.Model):
id = db.Column(db.BigInteger, primary_key=True)
name = db.Column(db.String(128), nullable=False)
notes = db.Column(db.Text, nullable=False)
def update(self, update_dictionary: dict):
for col_name in self.__table__.columns.keys():
if col_name in update_dictionary:
setattr(self, col_name, update_dictionary[col_name])
db.session.add(self)
db.session.commit()
Then in a route you can do
object = SampleObject.query.where(SampleObject.id == id).first()
object.update(update_dictionary=request.get_json())
Update the Columns in flask
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
admin.save()
To use the update method (which updates the entree outside of the session) you have to query the object in steps like this:
query = db.session.query(UserModel)
query = query.filter(UserModel.id == user_id)
query.update(user_dumped)
db.session.commit()