SqlAlchemy Database Issue - python

I created a Table a Bmarks which has two foreign keys which have relation with same table Url_hash
class Hashed(Base):
__tablename__ = "url_hash"
hash_id = Column(Unicode(22), primary_key=True)
url = Column(UnicodeText)
clicks = Column(Integer, default=0)
def __init__(self, url):
cleaned_url = str(unidecode(url))
self.hash_id = unicode(generate_hash(cleaned_url))
self.url = url
class Bmark(Base):
__tablename__ = "bmarks"
bid = Column(Integer, autoincrement=True, primary_key=True)
hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
clean_hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
description = Column(UnicodeText())
extended = Column(UnicodeText())
stored = Column(DateTime, default=datetime.utcnow)
updated = Column(DateTime, onupdate=datetime.utcnow)
clicks = Column(Integer, default=0)
inserted_by = Column(Unicode(255))
username = Column(Unicode(255), ForeignKey('users.username'),
nullable=False,)
tag_str = Column(UnicodeText())
hashed = relation(Hashed,
foreign_keys="Bmark.hash_id",
backref="bmark",
uselist=False
)
clean_hashed = relation(Hashed,
foreign_keys="Bmark.clean_hash_id",
backref="bmark",
uselist=False
)
I am trying to store url after cleaning it a little bit like removing headers,utm parameters etc for indexing purposes
Error is occurring while creating the database
sqlalchemy.exc.ArgumentError: Error creating backref 'bmark' on relationship 'Bmark.clean_hashed': property of that name exists on mapper 'Mapper|Hashed|url_hash'

Actually the error message is very informative.
Just rename one of your backref="bmark" to something else like backref="my_clean_bmark".

Related

Update property in all child objects if parent is updated in one to many relationship

I have a one to many relationship between a Project and a Note (i.e. a project will have many notes but a note belongs to a single project) in my Flask app:
class BaseDocument(db.Model):
__abstract__ = True
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, onupdate=datetime.now)
archived = db.Column(db.Boolean, default=False)
def __repr__(self):
return str(self.__dict__)
class Project(BaseDocument):
__tablename__ = "project"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
parent_id = db.Column(db.Integer, db.ForeignKey("project.id"))
notes = db.relationship(
"Note",
backref="project",
lazy=True,
order_by="Note.updated_at",
cascade="all, delete, delete-orphan",
)
class Note(BaseDocument):
__tablename__ = "note"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
content = db.relationship(
"Bullet", backref="note", lazy=True, order_by="Bullet.order"
)
project_id = db.Column(db.Integer, db.ForeignKey("project.id"))
I would like to do something that seems to be very simple but I can't figure out how: I want to update the archived property of all the child notes in a project to True if the parent project archived property is also set to True.
I can only find answers here in StackOverfow about how to update the parent object if a child is updated (the oposite of what I am trying to do), so I am assuming that what I want to do is trivial and I am just bad at sqlalchemy. How can I set this up? Do I need to use a after_update event on Project? If yes, how can I access all the child Notes and set archived=True for all of them?
I have tried to setup the following event listener with no success, I get the following error AttributeError: 'InstrumentedList' object has no attribute 'update':
#db.event.listens_for(Project, "after_update")
def archive_notes(mapper, connection, target):
obj = target.object
connection.execute(target.notes.update(archived=True))
Any help will be very appreciated!
You're on the right track by using after_update. Here's a working example:
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base, relationship
connection_uri = (
"mssql+pyodbc://#localhost:49242/myDb?driver=ODBC+Driver+17+for+SQL+Server"
)
engine = sa.create_engine(
connection_uri,
future=True,
echo=False,
)
Base = declarative_base()
class Project(Base):
__tablename__ = "project"
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.Unicode(100), nullable=False)
archived = sa.Column(sa.Boolean, nullable=False, default=False)
class ProjectNote(Base):
__tablename__ = "project_note"
id = sa.Column(sa.Integer, primary_key=True)
project_id = sa.Column(sa.Integer, sa.ForeignKey("project.id"))
project = relationship(Project)
note_text = sa.Column(sa.Unicode(255), nullable=False)
archived = sa.Column(sa.Boolean, nullable=False, default=False)
#sa.event.listens_for(Project, "after_update")
def archive_remaining_project_notes(mapper, connection, target):
if target.archived:
sql = """\
UPDATE project_note SET archived = :yes
WHERE project_id = :proj_id
AND archived = :no
"""
connection.execute(
sa.text(sql),
{"yes": True, "no": False, "proj_id": target.id},
)
# <just for testing>
Base.metadata.drop_all(engine, checkfirst=True)
Base.metadata.create_all(engine)
# </just for testing>
p1 = Project(title="project 1")
p1n1 = ProjectNote(
project=p1, note_text="project 1, note 1, archived", archived=True
)
p1n2 = ProjectNote(project=p1, note_text="project 1, note 2, not archived")
with sa.orm.Session(engine, future=True) as session:
session.add_all([p1, p1n1, p1n2])
session.commit()
print(f"p1n2.archived is: {p1n2.archived}") # p1n2.archived is: False
p1.archived = True
session.commit()
print(f"p1.archived is: {p1.archived}") # p1.archived is: True
print(f"p1n2.archived is: {p1n2.archived}") # p1n2.archived is: True

Flask SQL-Alchemy creating hierarchy table many foreign keys to same table

Have got a roles table:
from datetime import datetime
# from models.user_role import UserRoleModel
from models.role_hierarchy import RoleHierarchyModel
class RoleModel(db.Model):
__tablename__ = "security_role"
role_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
description = db.Column(db.Text)
created_timestamp = db.Column(db.DateTime, default=datetime.now())
updated_timestamp = db.Column(db.DateTime, default=datetime.now())
role = db.relationship('UserRoleModel', back_populates='role')
parent_role = db.relationship('RoleHierarchyModel',
back_populates='parent_role',
primaryjoin="RoleHierarchyModel.parent_role_id == RoleModel.role_id")
child_role = db.relationship('RoleHierarchyModel',
back_populates='child_role',
primaryjoin="RoleHierarchyModel.child_role_id == RoleModel.role_id")
and a Roles Hierarchy table
class RoleHierarchyModel(db.Model):
__tablename__ = "security_role_hierarchy"
role_h_id = db.Column(db.Integer, primary_key=True)
parent_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
child_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
created_at = db.Column(db.DateTime, default=datetime.now())
updated_at = db.Column(db.DateTime, default=datetime.now())
parent_role = db.relationship('RoleModel', back_populates='parent_role'
, foreign_keys='RoleHierarchyModel.parent_role_id')
child_role = db.relationship('RoleModel', back_populates='child_role'
, foreign_keys='RoleHierarchyModel.child_role_id')
Not sure if I have set this up correctly nor how to get it into the database correctly once I have done this. What I want to create is a role called super user that has other roles associated with it
I try:
base_role = RoleModel(title='Read')
other_base_role = RoleModel(title='Write')
base_role.save_to_db()
other_base_role.save_to_db()
super_user = RoleModel(title='Super)
hierarchy1 = RoleHierarchyModel()
hierarchy2 = RoleHierarchyModel()
hierarchy1.child_role(base_role)
hierarchy2.child_role(other_base_role)
super_user.parent_role.append(hierarchy1)
super_user.parent_role.append(hierarchy2)
but I keep getting the following error:
sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original
exception was: Attempting to flush an item of type <class 'models.role.RoleModel'> as a member of collection "RoleModel.child_role". Expected an object of type <class 'models.role_hierarchy.RoleHierarchyModel'> or
a polymorphic subclass of this type. (Background on this error at: http://sqlalche.me/e/7s2a)
what am I doing wrong?
In general, there should be no need to have two classes, which is kind of where your problem started.
To make this a proper tree, have a look at this answer. You only need to reference the parent_id through a column, and let SQLAlchemy get you the children through a relationship object.
Suppose you store the rights a role has in a list somehow. Then, if you want to see if someone has the right to do something, or if you want to return a list of all rights a person has (directly and indirectly):
class Role:
_rights = ['canChangePassword', 'canEditPosts', 'canDeletePosts'...]
def has_right(self, rightName):
if rightName in self._rights
return True
for child in self.children:
if child.has_right(rightName):
return True
return False
def get_rights(self):
all_rights = self._rights
for child in self.children:
all_rights += self.children.get_rights()
# there can be duplicates, cast to a set to remove them
return list(set(all_rights))
I can also see how rights are granted based on the description or title, but in that case, just iterate the same way and construct a list of role titles from itself and all children. Use recursion to your advantage here.
Found the answer hidden in the SQLAlchemy documentation: https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
Roles table now looks like:
class RoleModel(db.Model):
__tablename__ = "security_role"
role_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255))
description = db.Column(db.Text)
created_timestamp = db.Column(db.DateTime, default=datetime.now())
updated_timestamp = db.Column(db.DateTime, default=datetime.now())
role = db.relationship('UserRoleModel', back_populates='role')
child_roles = db.relationship('RoleModel', secondary='security_role_hierarchy'
, primaryjoin='RoleModel.role_id==RoleHierarchyModel.parent_role_id'
, secondaryjoin="RoleModel.role_id==RoleHierarchyModel.child_role_id",
backref="parent_roles")
and the hierarchy table is now:
class RoleHierarchyModel(db.Model):
__tablename__ = "security_role_hierarchy"
role_h_id = db.Column(db.Integer, primary_key=True)
parent_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
child_role_id = db.Column(db.Integer, db.ForeignKey('security_role.role_id'))
created_at = db.Column(db.DateTime, default=datetime.now())
updated_at = db.Column(db.DateTime, default=datetime.now())

Create table/generate schema in Postgres DB from Model in Python

I'm trying to re-start work for a previous project and Database server was deleted.
No database backup or SQL script. But schema is defined in Python Database Model.
Can I generate Database schema from it?
I have the following class defined:
class News(db.Model, AutoSerialize, Serializer):
__tablename__ = 'news'
news_id = Column(Integer, primary_key=True, server_default=text(
"nextval('news_news_id_seq'::regclass)"))
source = Column(String(64))
source_id = Column(String(64))
author = Column(String(128))
title = Column(String(256), nullable=False)
description = Column(String(65536))
url = Column(String(512))
url_to_image = Column(String(512))
published_at = Column(Date())
content = Column(String(65536))
campaign = Column(String(16))
score = Column(Float(53))
magnitude = Column(Float(53))
sentiment = Column(String(16))
rank_score = Column(Float(53))
rank_order = Column(Integer)
translated_content = Column(String(65536))
detected_language = Column(String(128))
inserted_at = Column(DateTime(True))
and
t_tags = Table(
'tags', metadata,
Column('tag_id', Integer, nullable=False,
server_default=text("nextval('tags_tag_id_seq'::regclass)")),
Column('tag_name', String(128), nullable=False)
)
When you have got the metadata and connection, you can just do
metadata.create_all(connection)
(I assume db.Model is a declarative base)

How to get parrent of object in session SQLAlchemy

I have two tables:
class Project(DataBase):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
name = Column(String, nullable=False, unique=True)
domain = Column(String, nullable=False)
phrases = relationship("Phrase", backref='proj')
def __init__(self, name, domain):
self.name = name
self.domain = domain
class Phrase(DataBase):
__tablename__ = 'phrases'
query_id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
query_text = Column(String, nullable=False)
project = Column(Integer, ForeignKey('projects.id'), nullable=False)
enable = Column(Boolean, nullable=False, default=True)
def __init__(self, query_text, project, city):
self.query_text = query_text
self.project = project
And I have a function:
def get_first_query():
session = Session(bind=engine)
q = session.query(Phrase).filter(Phrase.enable == True).first()
session.close()
return q
I want to get an object from table 2 and than get its parrent from first table:
session = Session(bind=engine)
q = get_first_query()
print(q.proj)
It doesn't work and print this error:
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance is not bound to a Session; lazy load operation of
attribute 'proj' cannot proceed
I can do this:
session = Session(bind=engine)
q = get_first_query()
q_project = session.query(Project).filter(Project.id == q.project)
But it's a bad way.
You can assess related object via proj attribute.
session.query(Phrase).filter(
Phrase.enable == True
).first().proj
This way you'll hit database one additional time to get it so you'll need to open session again. To avoid additional queries you can use joined load:
session.query(Phrase).filter(
Phrase.enable == True
).options(
joinedload('proj')
).first().proj

I am having an issue populating my tables for my pyramid project

So I recently changed from sqlite to postgres so that I could push my site to heroku. The issue I am having is that the tables are now getting built out of order??
class Data(Base):
__tablename__ = 'data'
id = Column(Integer, autoincrement=True, primary_key=True)
data_type = Column(Integer, primary_key=True, unique=True)
value = Column(Text, primary_key=True, unique=True)
range_lower = Column(Integer)
range_upper = Column(Integer)
gold = Column(Boolean)
def __init__(self, data_type, value, range_lower=0, range_upper=0, gold=False):
self.data_type = data_type
self.value = value
self.range_lower = range_lower
self.range_upper = range_upper
self.gold = gold
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, autoincrement=True, primary_key=True)
data_type = Column(Integer, ForeignKey('data.data_type'))
description = Column(Text)
annotations_per_page = Column(Integer)
uses_gold = Column(Boolean)
def __init__(self, data_type, description='default description', annotations_per_page=1, uses_gold=False):
self.data_type = data_type
self.description = description
self.annotations_per_page = annotations_per_page
self.uses_gold = uses_gold
The issue I am having is that sqlalchemy/pyramid or whatever the populate script generated by python setup.py develop on one of these types of projects, is trying to create the table Page before the table Data. Since there are foreign keys in Page that depend on Table, this is failing. I can't find any mention of this issue in the documentation, any insight would be greatly appreciated.
-Sam
you need to use database metadata
and these functions
metadata =MetaData()
creat_all()
it generates the tables in order of their dependency.
ref:http://docs.sqlalchemy.org/en/rel_0_7/core/schema.html

Categories