SQLalchemy/Flask - nested one to one - python

I'm trying to use Flask+SQLAlchemy to create a procedural universe. Each System has planets, and each planet has cities etc. etc.
It all seems pretty straight forward (famous last words) but I'm getting the following error:
' Traceback (most recent call last): line 75, in
print system.planets.cities
AttributeError: 'InstrumentedList' object has no attribute 'cities' '
when I run the below:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import libtcodpy as libtcod
import random
app = Flask(__name__)
db = SQLAlchemy(app)
class System(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
planets = db.relationship('Planet', backref='system')
def __repr__(self):
return '<System:{}>'.format(self.name)
class Planet(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
system_id = db.Column(db.Integer, db.ForeignKey('system.id'))
cities = db.relationship('City', backref='planet')
def __repr__(self):
return '<Planet:{}>'.format(self.name)
class City(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
pop = db.Column(db.Integer)
planet_id = db.Column(db.Integer, db.ForeignKey('planet.id'))
def __repr__(self):
return '<City:{}>'.format(self.name)
with app.app_context():
db.create_all()
def init_systems():
sysnum = random.randint(5,10)
#init namegenerator
libtcod.namegen_parse('data/systemnames.txt')
while sysnum > 0:
plannum = random.randint(3,8)
sname = libtcod.namegen_generate('Systems')
gensys = System(name=sname)
gensys.planets = []
while plannum > 0:
pname = libtcod.namegen_generate('Planets')
genplan = Planet(name=pname)
genplan.cities = []
citynum = random.randint(4,12)
while citynum > 0:
startpop = random.randint(10,1000)
cname = libtcod.namegen_generate('Cities')
gencit = City(name=cname, pop=startpop)
genplan.cities.append(gencit)
citynum -= 1
gensys.planets.append(genplan)
plannum -= 1
db.session.add(gensys)
db.session.commit()
sysnum -= 1
init_systems()
system = System.query.first()
print system.name
print system.planets
print system.planets.cities
Any ideas? I know there are other ways of structuring things, but I'm looking to have a pretty straightforward model of straight inheritance.

You can not access the cities by calling print system.planets.cities. As system.planets is a list of planets you need to access the cities for each planet separately.
for planet in system.planets:
print planet.cities

Related

How to get data from multiple tables using flask-sqlalchemy

Here is my tables.
class maindevotee(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(225))
phonenumber = db.Column(db.String(225))
gothram = db.Column(db.String(225))
date = db.Column(db.String(50))
address = db.Column(db.String(250))
def json(self):
return {'id': self.id, 'name':self.name, 'phonenumber': self.phonenumber, 'gothram': self.gothram,
'date': self.date, 'address': self.address}
class relatives(db.Model):
id = db.Column(db.Integer, primary_key=True)
main_id = db.Column(db.Integer, db.ForeignKey('maindevotee.id'), nullable=False)
name = db.Column(db.String(225))
star = db.Column(db.String(225))
gender = db.Column(db.String(45))
relation = db.Column(db.String(45))
def json(self):
return {'main_id': self.main_id, 'name': self.name, 'star':self.star,
'gender': self.gender, 'relation': self.relation}
class services(db.Model):
id = db.Column(db.Integer, primary_key=True)
main_id = db.Column(db.Integer, db.ForeignKey('maindevotee.id'), nullable=False)
pooja = db.Column(db.String(225))
god = db.Column(db.String(225))
price = db.Column(db.Float)
donation = db.Column(db.String(225))
booking_fromdate = db.Column(db.String(50))
booking_todate = db.Column(db.String(50))
prasadam = db.Column(db.String(225))
def json(self):
return {'main_id': self.main_id, 'pooja': self.pooja, 'god': self.god,
'price': self.price, 'donation': self.donation, 'booking_fromdate': self.booking_fromdate,
'booking_todate': self.booking_todate, 'prasadam': self.prasadam}
How to get data from multiple tables in a single request. Here is my scource code to join the three tables.
If i am try to get data from database it will raise an error.
and the error is AttributeError: 'result' object has no attribute 'get_data'
can i get the data from database using foreign key.
data = db.session.query(maindevotee, relatives, services)\
.filter(maindevotee.phonenumber == 3251469870)\
.join(relatives, maindevotee.id == relatives.main_id)\
.join(services, maindevotee.id == services.main_id)\
.first()
def get_data():
return [data.json(get) for get in data.query.all()]
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({'Devotee list': data.get_data()})
Correct
data = db.session.query(maindevotee, relatives, services)\
.filter(maindevotee.phonenumber == 3251469870)\
.join(relatives, maindevotee.id == relatives.main_id)\
.join(services, maindevotee.id == services.main_id)\
.first()
to
data = db.session.query(maindevotee, relatives, services)\
.filter(
(maindevotee.phonenumber == '3251469870')
& (maindevotee.id == relatives.main_id)
& (maindevotee.id == services.main_id)
).first()
for more clarifications, ask in the comments.
Upon comment
in
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({'Devotee list': data.get_data()})
data contains the query results, that do not include the function get_data(), therefore you face the mentioned error.
Try the following modification, I think this is the result form you may want:
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({**data.maindevotee.json(),**data.relatives.json(),**data.services.json()})
Good Luck

flask + sqlAlchemy COUNT, AVG and SUM in one query

Checked the sqlAlchemy docs but cannot see example of multiple columns query with filter and using FUNC.
How to compose a query based on my model to return result like this:
SELECT
COUNT(amount)a_cnt,
SUM(amount)a_sum,
AVG(amount)a_avg
FROM public.transaction
WHERE acc_id = 1
AND "traDate" >= '2019-11-20'
AND "traDate" <= '2019-12-01'
******************
a_cnt || a_sum || a_avg
------------------------
3 || 12 || 4
Please see below my model, and query functions, one with Class other with session, still unsure which one I should be using in this case. Both result in printing the query syntax.
Model:
class Transaction(db.Model):
id = db.Column(db.Integer, primary_key=True)
traDate = db.Column(db.Date, nullable=False)
amount = db.Column(db.Float, nullable=False)
desc = db.Column(db.String, nullable=False)
card = db.Column(db.String(1), nullable=False)
tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'), nullable=True)
acc_id = db.Column(db.Integer, db.ForeignKey('account.id'), nullable=False)
uplDate = db.Column(db.DateTime, nullable=False, default=datetime.now)
### this?
def sum_filtered(account_id, date_from, date_to):
return db.session.query(db.func.count(Transaction.amount).label('a_cnt'), db.func.sum(Transaction.amount).label('a_sum'), db.func.avg(Transaction.amount).label('a_avg')).filter_by(acc_id = account_id).filter(Transaction.traDate >= date_from, Transaction.traDate <= date_to)
### OR this?
def sum_filtered(account_id, date_from, date_to):
return Transaction.query.with_entities(func.sum(Transaction.amount).label('a_sum')).filter_by(acc_id = account_id).filter(Transaction.traDate >= date_from, Transaction.traDate <= date_to)
app:
#app.route(...)
templateData = {
...
'total_amnt' : model.Transaction.sum_filtered(accountid, f_from, f_to),
...
}
return render_template('/list.html', **templateData)
html:
...
<span class="input-group-text">Total £{{ total_amnt }}</span><!-- shows the query syntax-->
<span class="input-group-text">Total £{{ total_amnt.a_sum }}</span><!-- shows nothing-->
...
What am I missing?
Found this Docs. If no better answer provided, I will accept this.
def sum_filtered(account_id, date_from, date_to):
result = db.session.execute('SELECT COUNT(amount)a_cnt, AVG(amount)a_avg, SUM(amount)a_sum FROM transaction WHERE acc_id = :p1 AND "traDate" BETWEEN :p2 AND :p3',{'p1' : account_id, 'p2' : date_from, 'p3' : date_to})
return result.fetchone()
App:
'sum_avg_cnt' : model.Transaction.sum_filtered(accountid, f_from, f_to),
Then html:
{{ sum_avg_cnt.a_cnt }}

How to solve the dict return error when it is used in join table?

I have two models in which these model both return by toDict: as follows,
Tableone Model
class Base(db.Model):
__abstract__ = True
class Tableone(Base):
__tablename__ = 'tableone'
zxc = db.Column(db.VARCHAR(20), nullable=False)
asd = db.Column(db.VARCHAR(20), nullable=False)
qwe = db.Column(db.Integer, nullable=False)
tabletwo = db.relationship("Tabletwo", primaryjoin="foreign(Tabletwo.asd) == Tableone.bnm", uselist=True)
def __init__(zxc, asd, qwe):
self.zxc = zxc
self.asd = asd
self.qwe = qwe
def toDict(self):
return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs }
Tabletwo Model
class Base(db.Model):
__abstract__ = True
class Tabletwo(Base):
__tablename__ = 'tabletwo'
iop = db.Column(db.VARCHAR(20), nullable=False)
jkl = db.Column(db.VARCHAR(20), nullable=False)
bnm = db.Column(db.Integer, nullable=False)
def __init__(iop, jkl, bnm ):
self.iop = iop
self.jkl = jkl
self.bnm = bnm
def toDict(self):
return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs }
Both model has toDict function. When I access it via controller:
def index():
tre = session.query(Tableone, Tabletwo).outerjoin(Tableone.tabletwo).all()
treArr = []
for tr in tre:
treArr.append(tr.toDict())
return jsonify(treArr)
it gives me error of .... is not JSON serializable
It works good if I only use it without join method like this:
tre = session.query(Tableone).all()
Or if I mention the join one by one is also working :
tre = session.query(Tableone.iop, Tableone.jkl, Tableone.bnm, Tabletwo.zxc).outerjoin(Tableone.tabletwo).all()
Even though the result is only multidimensional array. [['iop1','jkl1', 'bnm1', 'zxc1'],[...],[...],[...],]
But I need to fetch all data in join table without have to mention it one by one what to select, and also need the key to access it in response.
Please help me how to do it correctly. I am very new in python. Many thanks in advance.
[UPDATE 1]
I am just aware if I access it like this:
for tr in tre:
treArr.append(tr.Tabletwo.toDict()) # here
return jsonify(treArr)
It throws error saying AttributeError: 'NoneType' object has no attribute 'toDict'
But if I access it like this
for tr in tre:
treArr.append(tr.Tableone.toDict()) # here
return jsonify(treArr)
It gives me the result of TableOne only
[UPDATE 2]
I was going to try it with merge function :
reference merge-two-dictionaries
for tr in tre:
merge_two_dicts(sim.Simses.toDict(), sim.Carrier.toDict())
...
def merge_two_dicts(x, y):
z = x.copy() # start with x's keys and values
z.update(y) # modifies z with y's keys and values & returns None
return z
But the error is mention in [UPDATE 1]

Flask-SQLAlchemy join

Hello I have a problem with join at flask-sqlalchemy. I am a beginner at database and flask.
These are my classes:
class Shopping(db.Model):
__tablename__ = 'shoppings'
id = db.Column(db.Integer, primary_key=True)
product_name = db.Column(db.String(30), index=True, unique=False)
price=db.Column(db.Float(10), index=True)
date=db.Column(db.Date())
s_type_id = db.Column(db.Integer, db.ForeignKey('shopping_types.id'))
def __repr__(self):
return 'Alisveris yeri :{0} Tutar :{1} Tarih: {2}'.format(self.product_name,self.price,self.date)
def __list__(self):
return [self.product_name,self.price,self.date]
class Shopping_Type(db.Model):
__tablename__='shopping_types'
id=db.Column(db.Integer,primary_key=True)
type_name=db.Column(db.String(30), index=True, unique=True)
types = db.relationship('Shopping', backref = 'shopping_types', lazy = 'dynamic')
def __repr__(self):
return '{0}'.format(self.type_name)
when I try on python terminal and run:
select shoppings.product_name ,shoppings.price, shoppings.date, shopping_types.type_name from shoppings join shopping_types ON shoppings.s_type_id=shopping_types.id
query
I get what I want but when I run flask-sqlalchemy command:
rslt=db.session.query(spng).join(st)
spng:Shopping(class)
st:Shopping_Type(class)
I get only Shopping data.
I want to get Shopping + Shopping_Type data.
Thank you.
rslt = db.session.query(spng, st).join(st)
The result would be an enumerable of tuples of (Shopping, Shopping_Type)
rslt = db.session.query(spng, st).filter(spng.s_type_id == st.id).all()
for x, y in rslt:
print("Product ID: {} Product Name: {} Sopping Type: {}".format(x.id, x.product_name, y.tye_name))
type(rslt) is a tuple, contains elements as the number of tables joining.

SQLAlchemy - Writing a hybrid method for child count

I'm using Flask-SQLAlchemy, and I'm trying to write a hybrid method in a parent model that returns the number of children it has, so I can use it for filtering, sorting, etc. Here's some stripped down code of what I'm trying:
# parent.py
from program.extensions import db
from sqlalchemy.ext.hybrid import hybrid_method
class Parent(db.Model):
__tablename__ = 'parents'
parent_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
children = db.relationship('Child', backref='parent', lazy='dynamic')
def __init__(self, name):
self.name = name
#hybrid_method
def child_count(self):
return self.children.count()
#child_count.expression
def child_count(cls):
return ?????
# child.py
from program.extensions import db
from program.models import Parent
class Child(db.Model):
__tablename__ = 'children'
child_id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey(Parent.parent_id))
name = db.Column(db.String(80))
time = db.Column(db.DateTime)
def __init__(self, name, time):
self.name = name
self.time = time
I'm running into two problems here. For one, I don't know what exactly to return in the "child_count(cls)", which has to be an SQL expression... I think it should be something like
return select([func.count('*'), from_obj=Child).where(Child.parent_id==cls.parent_id).label('Child count')
but I'm not sure. Another issue I have is that I can't import the Child class from parent.py, so I couldn't use that code anyway. Is there any way to use a string for this? For example,
select([func.count('*'), from_obj='children').where('children.parent_id==parents.parent_id').label('Child count')
Eventually, I'll want to change the method to something like:
def child_count(cls, start_time, end_time):
# return the number of children whose "date" parameter is between start_time and end_time
...but for now, I'm just trying to get this to work. Huge thanks to whoever can help me with this, as I've been trying to figure this out for a long time now.
The code below shows it all.
class Parent(Base):
__tablename__ = 'parents'
# ...
#hybrid_property
def child_count(self):
#return len(self.children) # #note: use when non-dynamic relationship
return self.children.count()# #note: use when dynamic relationship
#child_count.expression
def child_count(cls):
return (select([func.count(Child.child_id)]).
where(Child.parent_id == cls.parent_id).
label("child_count")
)
#hybrid_method
def child_count_ex(self, stime, etime):
return len([_child for _child in self.children
if stime <= _child.time <= etime ])
#child_count_ex.expression
def child_count_ex(cls, stime, etime):
return (select([func.count(Child.child_id)]).
where(Child.parent_id == cls.parent_id).
where(Child.time >= stime).
where(Child.time <= etime).
label("child_count")
)
# usage of expressions:
stime, etime = datetime.datetime(2012, 1, 1), datetime.datetime(2012, 1, 31)
qry = session.query(Parent)
#qry = qry.filter(Parent.child_count > 2)
qry = qry.filter(Parent.child_count_ex(stime, etime) > 0)

Categories

Resources