How to effectively query associated objects in SQLAlchemy ORM (SQLite3)? - python

I am trying to create a working relationship between two objects Mentor and Student, and be able to retrieve student objects from their mentor:
class Mentor_Student(Base):
__tablename__ = 'mentor_student'
__table_args__ = {'extend_existing': True}
mentor_id = Column("mentor_id", Integer, ForeignKey('mentor.mentor_id'), primary_key = True)
student_id = Column("student_id", Integer, ForeignKey('student.student_id'), primary_key = True)
def __init__(self, student_id):
self.mentor_id = random.choice(list(session.query(Mentor.mentor_id)))[0]
self.student_id = student_id
session.add(self)
session.commit()
class Mentor(Base):
__tablename__ = 'mentor'
__table_args__ = {'extend_existing': True}
mentor_id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
phone = Column(String(20), nullable=False)
mentees = relationship(
"Mentor",
secondary='mentor_student',
backref=backref("student", lazy='joined'))
def __init__(self):
self.name = Faker().name()
self.phone = Faker().phone_number()
session.add(self)
session.commit()
def __str__(self):
return self.name
class Student(Base):
__tablename__ = 'student'
__table_args__ = {'extend_existing': True}
student_id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
phone = Column(String(20), nullable=False)
mentors = relationship(
"Student",
secondary='mentor_student',
backref=backref("mentor",
lazy='joined'))
def __init__(self):
self.name = Faker().name()
self.phone = Faker().phone_number()
session.add(self)
session.commit()
Mentor_Student(self.student_id)
def __str__(self):
return self.name
Every mentor has multiple students. I would like to create a query that will return the students(mentees) associated with each Mentor. Please observe below:
for x, y in session.query(Mentors, Mentors.mentees).all()
print(x,':',y)
could produce the result:
MentorObject : [StudentObject, StudentObject, StudentObject]
Right now the closest I can get is printing out a single mentor object and a single student object associated with it. I also could hard code it with the accumulator pattern into a dicitonary:
maps = {}
for student, mentor in session.query(Student, Mentor).filter(Student.student_id == Mentor_Student.student_id, Mentor_Student.mentor_id == Mentor.mentor_id).all():
if mentor in maps.keys():
maps[mentor].append(student)
else:
maps[mentor] = [student]
Which gives me the result:
{<__main__.Mentor object at 0x7f3309887070>: [<__main__.Student object at 0x7f3309850f40>, <__main__.Student object at 0x7f3309887280>, <__main__.Student object at 0x7f3309887580>, <__main__.Student object at 0x7f330988f7c0>, <__main__.Student object at 0x7f330988fa00>, <__main__.Student object at 0x7f330982b4c0>],
...
<__main__.Mentor object at 0x7f33097e2550>: [<__main__.Student object at 0x7f33097e2490>, <__main__.Student object at 0x7f33097e2790>]}
'''
But this does not seem like a refined solution. Any ideas how I can improve my code. I am relatively new to SQLAlchemy.

Related

Why does many -to-many relationship in SQLAlchemy showing Nonetype?

Base = declarative_base()
class Products(Base):
__tablename__ = 'tb_products'
#__table_args__ = {'extend_existing': True}
prod_id = Column(Integer, primary_key=True)
prod_category = Column(Integer,ForeignKey('tb_categories.ctg_id'))
category = relationship("Categories",back_populates="product") # many to one
prod_name = Column(String(100), nullable=False)
description = Column(String(100), nullable=False)
image = Column(String(100), nullable=False,default='default.jpg')
qty_available = Column(Integer, nullable=False,default=0)
regular_price = Column(DECIMAL,nullable=False,default=0.0)
discounted_price = Column(DECIMAL,nullable=False,default=0.0)
product_rating = Column(DECIMAL,nullable=False,default=0.0)
def __init__(self, prod_name = "test", description = "Dummy desciption",image =
"Dummy.png",qty_available = 10,regular_price = 1000,discounted_price =
800,product_rating = 0.0):
self.prod_name = prod_name
self.description = description
self.image = image
self.qty_available = qty_available
self.regular_price = regular_price
self.discounted_price = discounted_price
self.product_rating = product_rating
class Cart(Base):
__tablename__ = 'tb_cart'
#__table_args__ = {'extend_existing': True}
cart_id = Column(Integer, ForeignKey('tb_user.userid'), primary_key=True)
user = relationship("User",back_populates='cart') # one to one
class Cart_Products(Base):
__tablename__ = 'tb_cart_products'
__table_args__ = {'extend_existing':True}
prod_id = Column(Integer, ForeignKey('tb_products.prod_id'),primary_key=True)
cart_id = Column(Integer, ForeignKey('tb_cart.cart_id'),primary_key=True)
products = relationship("Products")
When i print tried printing
cartObj = Cart()
cart_productsObj = Cart_Products()
userObj.cart = cartObj
cart_productsObj.products.append(productObj)
userObj.cart.cart_products.append(cart_productsObj)
print(f"&&&&&&&{type(cartObj.cart_products)}")
print(f"&&&&&&&{type(cart_productsObj.products)}")
print(f"$$$$$$${type(userObj.cart)}")
print(f"$$$$$$${type(cartObj.user)}")
It is showing me following error
File "/home/devendra/Python_WS/ecommerce-project/app.py", line 248, in addToCart
cart_productsObj.products.append(productObj)
AttributeError: 'NoneType' object has no attribute 'append'
i was facing same problem when i was implementing one-to-one relation between user and cart. it was solved after i dropped whole database and created a new one.but i want to understand why does these error occurs
Also i want use Association objects for many-to-many so that i don't have to use manual queries.

TypeError: unhashable type: 'list' error with the sqlalchemy insert query

I am trying to insert a record into a oracle db via sqlalchemy, here is my below model:
class Item(Base):
__tablename__ = 'items'
id = db.Column(db.Integer, db.Sequence('item_id_seq'), nullable=False, primary_key=True)
name = db.Column(db.String(128), nullable=True)
def __init__(self, name):
self.name = name
I have already created the table in the db by running the db migrate and upgrade. I am trying to insert a simple row in the db table as in :
test_obj = Item(name='item-1')
db.session.add(test_obj)
db.session.commit()
But the insert fails with the error TypeError: unhashable type: 'list'
But if I insert it along with the id (as below) and with the id initialized in the constructor, it inserts successfully.
test_obj = Item(id = 1, name='item-1')
db.session.add(test_obj)
db.session.commit()
class Item(Base):
...
def __init__(self, id, name):
self.id = id
self.name = name
Since I have set id with a sequence, it should auto increment and get inserted automatically, but this is not the case here, any help would be appreciated.
USE sqlalchemy 1.3.6 or above
Sqlalchemy does not support auto increment for oracle 11g
if you are using "oracle 11g" then use following code:
from sqlalchemy import event
from sqlalchemy.schema import CreateSequence, DDL
db_session = sessionmaker(bind={your database engine})
class Item(Base):
__tablename__ = 'items'
id = db.Column(db.Integer, nullable=False, primary_key=True)
name = db.Column(db.String(128), nullable=True)
def __init__(self, name):
self.name = name
def create_trigger_for_items(*args, **kwargs):
with con.db_session() as session:
session.execute(CreateSequence(db.Sequence('item_id_seq')))
session.execute(DDL("""CREATE OR REPLACE TRIGGER ITEM_ID_TRIGGER
BEFORE INSERT ON items
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
:NEW.id := item_id_seq.NEXTVAL;
END;"""))
event.listen(Item.__table__, "after_create",
create_trigger_for_items)
if you are using "oracle 12" then use following code:
class Item(Base):
__tablename__ = 'items'
id = db.Column(db.Integer, db.Sequence('item_id_seq'), default=db.Sequence('item_id_seq').next_value(), primary_key=True)
name = db.Column(db.String(128), nullable=True)
def __init__(self, name):
self.name = name
if you are using "oracle 12c" then use following code:
class Item(Base):
__tablename__ = 'items'
id = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(128), nullable=True)
def __init__(self, name):
self.name = name
then you can use:
test_obj = Item(name='item-1')
db.session.add(test_obj)
db.session.commit()
Just mark id as the primary_key.
class Item(Base):
__tablename__ = 'items'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), nullable=True)
def __init__(self, name):
self.name = name

Use hybridattribute on relationship in sqlalchemy

I'm trying to get the following contrived example to work:
class Person(Base):
__tablename__ = "persons"
__table_args__ = {"mysql_charset": "utf8mb4"}
id = Column(Integer, primary_key=True)
name = Column(String(50), index=True, unique=True)
birthday = Column(Boolean, default=False)
#hybrid_property
def is_birthday_person(self):
return self.birthday == True
class Party(Base):
__tablename__ = "parties"
__table_args__ = {"mysql_charset": "utf8mb4"}
id = Column(BigInteger, primary_key=True)
one_id = Column(Integer, ForeignKey("persons.id"))
two_id = Column(Integer, ForeignKey("persons.id"))
one_person = relationship("Person", foreign_keys=[one_id])
two_person = relationship("Person", foreign_keys=[two_id])
sunshine = Column(Boolean, default=False)
#hybrid_property
def everyones_birthday_in_sunshine(self):
return self.one_person.is_birthday_person and self.two_person.is_birthday_person and self.sunshine
What I am trying to achieve is to filter all parties and only return parties where both persons celebrate their birthday and the sun is shining when I run the following query:
session.Query(Party).filter(Party.everyones_birthday_in_sunshine).all()
The error I am getting is:
AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Party.one_person has an attribute 'is_birthday_person'
When I change everyones_birthday_in_sunshine to:
return self.sunshine == True
It works and return all parties where the sun in shining when I run:
session.Query(Party).filter(Party.everyones_birthday_in_sunshine).all()
So the problem lies in the relationship between Party and Person, but I can't figure out how to get it to work.
Edit:
When I try to print properties from the person instance in Party: everyones_birthday_in_sunshine eg: one_person.name it just prints out Party.one_person. Definitly a relationship issue I guess.

Python Flask-SQLAlchemy inheritance

I'm trying to make an insert query but I'm getting this error:
sqlalchemy.orm.exc.FlushError: Attempting to flush an item of type
as a member of collection "Tag.item". Expected
an object of type or a polymorphic subclass of
this type. If is a subclass of , configure mapper "Mapper|Item|items" to load this
subtype polymorphically, or set enable_typechecks=False to allow any
subtype to be accepted for flush.
The insert query
project = Proyecto.query.filter_by(id_project=id_project).first()
creado_en = datetime.datetime.utcnow()
nuevo_hub = Hub(name,location,comments,creado_en)
#FAIL HERE, even if Hub extends of Item
nuevo_tag = Tag(project,nuevo_hub,TYPE_HUB,creado_en)
db.session.add(nuevo_tag)
db.session.add(nuevo_hub)
db.session.commit()
The rest of code
class Item(db.Model):
__tablename__ = "items"
id_item = db.Column(db.Integer, autoincrement=True, primary_key=True)
type = db.Column(db.Integer)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
__mapper_args__ = {
'polymorphic_identity': 'items',
'polymorphic_on':type,
'with_polymorphic':'*'
}
def __init__(self,creado_en=None):
self.created_at = creado_en
self.updated_at = creado_en
class Hub(Item):
__tablename__ = "hubs"
__mapper_args__ = {
'polymorphic_identity': 0,
'with_polymorphic':'*'
}
id_hub = db.Column(db.Integer, db.ForeignKey('items.id_item'), primary_key=True)
# id_hub = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), nullable=False, index= True)
location = db.Column(db.String(50))
comments = db.Column(db.String(128))
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
# conexiones = db.Column(db.Integer)
def __init__(self, nombre=None, location=None,comments=None, creado_en=None):
self.name = nombre
self.location = location
self.comments = comments
self.created_at = creado_en
self.updated_at = creado_en
class Tag(db.Model):
__tablename__ = "tags"
id_tag = db.Column(db.Integer, autoincrement=True, primary_key=True)
id_project = db.Column(db.Integer,db.ForeignKey("projects.id_project"))
id_item = db.Column(db.Integer,db.ForeignKey("items.id_item"))
project = db.relationship(Proyecto, backref=db.backref('list_tags', lazy='dynamic'))
item = db.relationship(Item, backref=db.backref('list_tags', lazy='dynamic'))
type = db.Column(db.Integer) #(0,hub);(1,cable);(2,pipe);(3,electrical_pipes)
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
def __init__(self,project,item,type,created_at):
self.project = project
self.item = item
self.type = type
self.created_at = created_at
self.updated_at = created_at
I had the same problem, in my case I fix that based on this answer:
Flask-SQLAlchemy polymorphic association
Also, the official SQLAlchemy docs can help:
Joined Table Inheritance In joined table inheritance, each class along
a particular classes’ list of parents is represented by a unique
table. The total set of attributes for a particular instance is
represented as a join along all tables in its inheritance path. Here,
we first define the Employee class. This table will contain a primary
key column (or columns), and a column for each attribute that’s
represented by Employee. In this case it’s just name:
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'employee',
'polymorphic_on':type
}
I think your code should look like:
class Hub(Item):
__tablename__ = "hubs"
id_hub = db.Column(db.Integer, autoincrement=True, primary_key=True)
name = db.Column(db.String(50), nullable=False, index= True)
location = db.Column(db.String(50))
comments = db.Column(db.String(128))
created_at = db.Column(db.DateTime)
updated_at = db.Column(db.DateTime)
# conexiones = db.Column(db.Integer)
__mapper_args__ = {
'polymorphic_identity': 0,
'polymorphic_on': id_hub
}
def __init__(self, nombre=None, location=None,comments=None, creado_en=None):
self.name = nombre
self.location = location
self.comments = comments
self.created_at = creado_en
self.updated_at = creado_en
You may also need to set is to concrete = True to be able to query it from child class.
Concrete inheritance is inheritance of method implementations and member variables from a super-class.
Details at:
Abstract Concrete Classes
__mapper_args__ = {
'polymorphic_identity': 0,
'polymorphic_on': id_hub,
'concrete': True
}

sqlalchemy: 'InstrumentedList' object has no attribute 'filter'

I have the following 3 classes:
class Resource:
id = Column(Integer, primary_key=True)
path = Column(Text)
data = Column(Binary)
type = Column(Text)
def set_resource(self, path, data, type):
self.path = path
self.data = data
self.type = type
class EnvironmentResource(Base, Resource):
__tablename__ = 'environment_resources'
parent_id = Column(Integer, ForeignKey('environments.id', ondelete='CASCADE'))
def __init__(self, path, data, type):
self.set_resource(path, data, type)
class Environment(Base):
__tablename__ = 'environments'
id = Column(Integer, primary_key=True)
identifier = Column(Text, unique=True)
name = Column(Text)
description = Column(Text)
_resources = relationship("EnvironmentResource",
cascade="all, delete-orphan",
passive_deletes=True)
_tools = relationship("Tool",
cascade="all, delete-orphan",
passive_deletes=True)
def __init__(self, name, identifier, description):
self.name = name
self.identifier = identifier
self.description = description
def get_resource(self, path):
return self._resources.filter(EnvironmentResource.path==path).first()
On calling get_resource, I am told that 'InstrumentedList' object has no attribute 'filter' - I've gone through the documentation and can't quite figure this out. What am I missing, so that I may be able to filter the resources corresponding to an environment inside my 'get_resource' method?
PS: I know get_resource will throw an exception, that's what I'd like it to do.
In order to work with the relationship as with Query, you need to configure it with lazy='dynamic'. See more on this in Dynamic Relationship Loaders:
_resources = relationship("EnvironmentResource",
cascade="all, delete-orphan",
lazy='dynamic',
passive_deletes=True)

Categories

Resources