Here is my problem. I have three tables.
One called Project which has only one Column called id (this must be unique throughout the system).
One called ServiceAwarenessProject which has a one to one relationship with Project.id.
One called CorporateVPNProject which has a one to one relationship with Project.id
I'm using sqlalchemy ORM, so the code looks like the one below:
class Project(SqlAlchemyBase):
__tablename__ = 'project'
id = Column(Integer, primary_key=True, autoincrement=True)
class ServiceAwarenessProject(SqlAlchemyBase):
__tablename__ = 'sa_project'
id = Column(Integer, primary_key=True)
project_id = Column(Integer, ForeignKey(Project.id))
mop_url = Column(String, nullable=False)
expiration_date = Column(Datetime, index=True)
class CorporateVPNProject(SqlAlchemyBase):
__tablename__ = 'wvpn_project'
id = Column(Integer, primary_key=True)
project_id = Column(Integer, ForeignKey(Project.id))
mop_url = Column(String, nullable=False)
I designed my tables like that, so I can guarantee I have unique project_ids in the entire system. My problem is that I don't know how to join those tables together to find a project based on the project_id. To solve this problem for now, I'm querying both tables, using the function called get_project_by_id.
Is there a smarter way to solve this issue?
class ProjectService:
#staticmethod
def create_project_id():
session = DbSessionFactory.create_session()
result = session.query(Project.id).order_by(desc(Project.id)).first()
if result:
result = result[0]
if str(result)[:8] == datetime.datetime.now().strftime('%Y%m%d'):
project_id = str(result)[:8] + '{:03d}'.format(int(str(result)[8:]) + 1)
new_project = Project(id=project_id)
session.add(new_project)
session.commit()
return project_id
project_id = datetime.datetime.now().strftime('%Y%m%d') + '001'
new_project = Project(id=project_id)
session.add(new_project)
session.commit()
return project_id
#staticmethod
def get_project_by_id(project_id):
session = DbSessionFactory.create_session()
result = session.query(ServiceAwarenessProject) \
.filter(ServiceAwarenessProject.project_id == project_id) \
.first()
if result:
return result
result = session.query(CorporateVPNProject) \
.filter(CorporateVPNProject.project_id == project_id) \
.first()
if result:
return result
def create_serviceawareness_project(self):
session = DbSessionFactory.create_session()
project_id = self.create_project_id()
new_project = ServiceAwarenessProject(project_id=project_id, mop_url='http://www.thepacketwizards.com/1')
session.add(new_project)
session.commit()
return new_project
def create_corporatevpn_project(self):
session = DbSessionFactory.create_session()
project_id = self.create_project_id()
new_project = CorporateVPNProject(project_id=project_id, mop_url='http://www.thepacketwizards.com/wvpn')
session.add(new_project)
session.commit()
return new_project
Thank you!
Following #Ilja Everilä suggestion, I designed the table like so, using only joined table inheritance.
class Project(SqlAlchemyBase):
__tablename__ = 'project'
id = Column(Integer, primary_key=True)
created_on = Column(DateTime, default=datetime.datetime.now)
updated_on = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
project_url = Column(String(60))
mop_url = Column(String(60))
input_url = Column(String(60))
type = Column(String(60))
__mapper_args__ = {
'polymorphic_identity': 'project',
'polymorphic_on': type
}
class ServiceAwarenessProject(Project):
__tablename__ = 'sa_project'
id = Column(Integer, ForeignKey('project.id'), primary_key=True)
expiration_date = Column(DateTime)
__mapper_args__ = {
'polymorphic_identity': 'ServiceAwareness',
}
class CorporateVPNProject(Project):
__tablename__ = 'wvpn_project'
id = Column(Integer, ForeignKey('project.id'), primary_key=True)
client_name = Column(String(60))
__mapper_args__ = {
'polymorphic_identity': 'CorporateVPN',
}
Now, to query the DB I have to use with_polymorphic, so I can get different instances of Tables per row.
class ProjectService:
#staticmethod
def create_project_id():
session = DbSessionFactory.create_session()
result = session.query(Project.id).order_by(desc(Project.id)).first()
print(result)
if result:
result = result[0]
if str(result)[:8] == datetime.datetime.now().strftime('%Y%m%d'):
project_id = str(result)[:8] + '{:03d}'.format(int(str(result)[8:]) + 1)
return project_id
project_id = datetime.datetime.now().strftime('%Y%m%d') + '001'
return project_id
def create_serviceawareness_project(self):
session = DbSessionFactory.create_session()
project_id = self.create_project_id()
new_project = ServiceAwarenessProject(id=project_id,
project_url='http://project',
expiration_date=datetime.datetime.now() + datetime.timedelta(days=365),
mop_url='http://mop',
input_url='http://url',
type='ServiceAwareness')
session.add(new_project)
session.commit()
session.add(new_project)
return new_project
def create_corporatevpn_project(self):
session = DbSessionFactory.create_session()
project_id = self.create_project_id()
new_project = CorporateVPNProject(id=project_id,
project_url='http://project',
client_name='TIM',
mop_url='http://mop',
input_url='http://url',
type='CorporateVPN')
session.add(new_project)
session.commit()
session.add(new_project)
return new_project
#staticmethod
def get_project_by_id(project_id):
session = DbSessionFactory.create_session()
query = session.query(with_polymorphic(Project, [ServiceAwarenessProject, CorporateVPNProject])).filter(or_(
ServiceAwarenessProject.id == project_id,
CorporateVPNProject.id == project_id
)).first()
return query
Related
I'm trying to create a sqlite database that takes inputs from a CSV file using flask-sqlalchelmy.
Here is the config file:
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir,'applicant.db')
app.config['SQLALCHEMY_TRACK _MODIFICATIONS']=False
cors = CORS(app)
db = SQLAlchemy(app)
app.run(debug=True)
Here are the models:
class attributes(db.Model):
__tablename__ = 'attributes'
id = Column(Integer, primary_key=True)
URL = Column(Text)
Gender = Column(String(10))
SAT =Column(Integer)
ACT = Column(Integer)
major =db.relationship('Major',backref = 'attributes')
ecs = db.relationship('Ecs', backref = 'attributes')
Race = db.relationship('Race',backref = 'attributes')
Acceptances = db.relationship('Acceptances', backref = 'attributes')
Rejections = db.relationship('Rejections', backref = 'attributes')
class Ecs(db.Model):
id = Column(Integer, primary_key = True)
listofecs = Column(Text)
Attributeid = Column(Integer, db.ForeignKey('attributes.id'))
def __repr__(self):
return f'<Ecs "{self.title}">'
class Major(db.Model):
id = Column(Integer, primary_key = True)
majorlist = Column(Text)
Attributeid = Column(Integer, db.ForeignKey('attributes.id'))
def __repr__(self):
return f'<major "{self.title}">'
class Race(db.Model):
id = Column(Integer, primary_key = True)
racelist = Column(Text)
Attributeid = Column(Integer, db.ForeignKey('attributes.id'))
def __repr__(self):
return f'<race "{self.title}">'
class Acceptances(db.Model):
id = Column(Integer, primary_key = True)
acceptlist = Column(Text)
Attributeid = Column(Integer, db.ForeignKey('attributes.id'))
def __repr__(self):
return f'<acceptances "{self.title}">'
class Rejections(db.Model):
id = Column(Integer, primary_key = True)
rejectlist = Column(Text)
Attributeid = Column(Integer, db.ForeignKey('attributes.id'))
def __repr__(self):
return f'<rejections "{self.title}">'
They are mostly one-to-many relationships - but i'm not sure if thats the issue.
I'm trying to create the database by using this script:
import pandas as pd
from app import attributes, Major, Ecs, Race, Acceptances,Rejections, db, df
from ast import literal_eval
df = pd.read_csv('..\csvfiles\processeddata.csv')
for i in range(len(df['Gender'])):
applicant = attributes(Gender = df['Gender'].iloc[i],SAT = df['SAT'].iloc[i], ACT = df['ACT'].iloc[i])
db.session.add(applicant)
for racevalues in df['Race'].apply(literal_eval).iloc[i]:
race = Race(racelist = racevalues, Attributeid = i+1)
db.session.add(race)
for majorvalues in df['Major'].apply(literal_eval).iloc[i]:
major = Major(majorlist = majorvalues,Attributeid = i+1 )
db.session.add(major)
for ecvalues in df['Extracurriculars'].apply(literal_eval).iloc[i]:
ecs = Ecs(listofecs = ecvalues , Attributeid = i+1)
db.session.add(ecs)
for valuesacceptances in df["Acceptances"].apply(literal_eval).iloc[i]:
Accepts = Acceptances(acceptlist = valuesacceptances, Attributeid = i+1)
db.session.add(Accepts)
for valuesreject in df["Rejections"].apply(literal_eval).iloc[i]:
Rejects = Rejections(rejectlist = valuesreject, Attributeid = i+1)
db.session.add(Rejects)
db.create_all()
db.session.commit()
df is the data that is taken from the csv file that I want to be inserted into the database. I have confirmed that it is the correct file and has data by printing df. I have also tried to manually create the database using db.create_all() in the command line through the flask shell. This creates the scheme for the database but even after running the script, there is no data inserted into the database. Further, when I try to query the data onto a route the route indeed works but returns an empty array.
The strange thing is that this worked perfectly fine for a few days. It stopped working after my computer crashed and I wonder if that has something to do with it.
I have the following two tables defined in SQLAlchemy:
class DataCollection(Base):
__tablename__ = "datacollection"
id = Column(Integer, primary_key=True, index=True)
owner = Column(String, nullable=False)
class Data(Base):
__tablename__ = "data"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False)
collection_a_id = Column(Integer, ForeignKey("datacollection.id"))
collection_a = relationship("DataCollection", foreign_keys=[collection_a_id])
collection_b_id = Column(Integer, ForeignKey("datacollection.id"))
collection_b = relationship("DataCollection", foreign_keys=[collection_b_id])
collection_c_id = Column(Integer, ForeignKey("datacollection.id"))
collection_c = relationship("DataCollection", foreign_keys=[collection_c_id])
Given the following data:
data1 = Data(
name="data1",
collection_a=DataCollection(owner="user1"),
collection_b=DataCollection(owner="user1"),
collection_c=DataCollection(owner="user1"),
)
data2 = Data(
name="data2",
collection_a=DataCollection(owner="user1"),
collection_b=DataCollection(owner="user1"),
collection_c=DataCollection(owner="user2"),
)
I want to filter those data objects which are exclusively owned by "user1" using a method owned_by defined on Data:
with Session() as sess:
data = sess.query(Data).filter(Data.owned_by("user1")).all()
assert len(data) == 1
assert data[0].name == "data1"
How can I achieve this?
EDIT:
This is my current solution which does not use a method yet:
from sqlalchemy import (
Column,
String,
Integer,
create_engine,
ForeignKey,
inspect,
)
from sqlalchemy.orm import declarative_base, relationship, sessionmaker, aliased
Base = declarative_base()
class DataCollection(Base):
__tablename__ = "datacollection"
id = Column(Integer, primary_key=True, index=True)
owner = Column(String, nullable=False)
class Data(Base):
__tablename__ = "data"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False)
collection_a_id = Column(Integer, ForeignKey("datacollection.id"))
collection_a = relationship("DataCollection", foreign_keys=[collection_a_id])
collection_b_id = Column(Integer, ForeignKey("datacollection.id"))
collection_b = relationship("DataCollection", foreign_keys=[collection_b_id])
collection_c_id = Column(Integer, ForeignKey("datacollection.id"))
collection_c = relationship("DataCollection", foreign_keys=[collection_c_id])
engine = create_engine("sqlite:///")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
def find_data_owned_by(session, user):
DataCollA = aliased(DataCollection)
DataCollB = aliased(DataCollection)
DataCollC = aliased(DataCollection)
query = (
session.query(Data)
.join(DataCollA, Data.collection_a)
.join(DataCollB, Data.collection_b)
.join(DataCollC, Data.collection_c)
.filter(DataCollA.owner == user)
.filter(DataCollB.owner == user)
.filter(DataCollC.owner == user)
)
print(query)
return query.all()
def main():
data1 = Data(
name="data1",
collection_a=DataCollection(owner="user1"),
collection_b=DataCollection(owner="user1"),
collection_c=DataCollection(owner="user1"),
)
data2 = Data(
name="data2",
collection_a=DataCollection(owner="user1"),
collection_b=DataCollection(owner="user1"),
collection_c=DataCollection(owner="user2"),
)
with Session() as sess:
sess.add(data1)
sess.add(data2)
sess.commit()
with Session() as sess:
data = find_data_owned_by(sess, "user1")
assert len(data) == 1
assert data[0].name == "data1"
if __name__ == "__main__":
main()
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
I have these models
class Integration(Base, ModelBase, ModelSerializer):
__tablename__ = 'integration'
id = Column(Integer, primary_key=True)
domain = relationship('Domain', backref='integration')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
meta = Column(JSON)
tag = Column(String(512))
identifier_id = Column(String(512), unique=True, index=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Domain(Base, ModelBase, ModelSerializer):
__tablename__ = 'domain'
id = Column(Integer, primary_key=True)
integration_id = Column(Integer, ForeignKey('integration.id'), index=True)
url = relationship('Url', backref='domain')
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
domain = Column(String(512), nullable=True)
def __repr__(self):
return '<name {}>'.format(self.name)
class Url(Base, ModelBase, ModelSerializer):
__tablename__ = 'url'
id = Column(Integer, primary_key=True)
domain_id = Column(Integer, ForeignKey('domain.id'), index=True)
created_at = Column(DateTime, default=datetime.now())
updated_at = Column(DateTime, default=datetime.now())
name = Column(String(512), index=True)
url = Column(String(512), nullable=True)
meta = Column(JSON)
auth = Column(JSON)
def __repr__(self):
return '<name {}>'.format(self.name)
class ModelBase(object):
def __repr__(self):
return '<id {}>'.format(self.id)
class ModelSerializer(object):
def as_dict(self):
return {attribute.name: getattr(self, attribute.name) for attribute in self.__table__.columns}
Now I need to filter out the data based on the filters.
If suppose integration_filters are given then only filter the Integration data.
If Integration and Domain are given then filter only domain and integration by joining them
Same with Url and Domain
Solutions I tried -
1). Doesnt work as it doesnt join then tables. Later figured out that add_entity doesnt add the models.
if integration_filter:
query = query.add_entity(Integration)
query = query.filter_by(**kwargs['integration_filter'])
if domain_filter:
query = query.add_entity(Domain)
query = query.filter_by(**kwargs['domain_filter'])
if url_filter:
query = query.add_entity(Url)
query = query.filter_by(**kwargs['url_filter'])
2). It checks for the attribute 'domain' in Integration table
if integration_filter:
query = session.query(Integration).filter_by(**integration_filter)
if domain_filter:
if query:
query = query.join(Domain)
else:
query = session.query(Domain)
query = query.filter_by(**domain_filter)
if url_filter:
if query:
query = query.join(Url)
else:
query = session.query(Url)
query = query.filter_by(**url_filter)`
3). This doesnt work either
models = []
joins = []
if integration_filter:
models.append(Integration)
if domain_filter:
if models:
joins.append((Integration, Domain, Integration.id == Domain.integration_id))
models.append(Domain)
if url_filter:
if models:
joins.append((Domain, Url, Domain.id == Url.domain_id))
models.append(Url)
query = session.query(*models)
for join in joins:
query = query.join(*join)
This one worked. Was pretty simple though, didn't think of it in the beginning
try:
session = Backend().get_session()
models = []
join_filter = []
if integration_filter:
models.append(Integration)
join_filter.append({'filter': integration_filter})
if domain_filter:
models.append(Domain)
join_filter.append({'join': (Domain, Domain.integration_id == Integration.id), 'filter': domain_filter})
if url_filter:
models.append(Url)
join_filter.append({'join': (Url, Url.domain_id == Domain.id), 'filter': url_filter})
query = session.query(*models)
for jf in join_filter:
if 'join' in jf:
query = query.join(jf['join'])
if 'filter' in jf:
query = query.filter_by(**jf['filter'])
query = query.__getattribute__(query_type)
self.records = query()
except InvalidRequestError as ir:
raise Exception("Invalid query in URL Builder Error")
except NoResultFound as nrf:
raise Exception(
"No URL was found for filters integration - {} domain - {} url - {}".format(integration_filter,
domain_filter, url_filter))
except MultipleResultsFound as mrf:
raise Exception(
"Multiple Results was found for filters integration - {} domain - {} url - {}".format(
integration_filter, domain_filter, url_filter))
except Exception as e:
raise Exception("Error while fetching records in URL Builder {}")
finally:
session.close()
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