I have 2 tables with the same column structure.
The script pulls from 2 different json sources with slightly different keys.
My Item class identifies the source and then parses the data.
In my Item class I want to be able to change the __tablename__ based on the data source.
Is this possible or do I need to write a separate class for each data source?
Thanks,
Code:
Base = declarative_base()
class Item(Base):
__tablename__ = 'products'
timestamp = Column(TIMESTAMP)
itemid = Column(String, primary_key=True, index=True, unique=True)
name = Column(String)
def __init__(self, item):
if type(item) == Product_A:
self.__tablename__ = "A_products"
# Parse Data
elif type(item) == Product_B:
self.__tablename__ = "B_products"
# Parse Data
This is not a good idea, in sqlalchemy each class should be mapped to a single table. A solution is to make two classes and a free function to dispatch between them:
Base = declarative_base()
class Item_A(Base):
__tablename__ = 'A_products'
timestamp = Column(TIMESTAMP)
itemid = Column(String, primary_key=True, index=True, unique=True)
name = Column(String)
class Item_B(Base):
__tablename__ = 'B_products'
timestamp = Column(TIMESTAMP)
itemid = Column(String, primary_key=True, index=True, unique=True)
name = Column(String)
def create_item_object(item):
if isinstance(item, Product_A):
result = Item_A()
#... more stuff
elif isinstance(item, Product_B):
result = Item_B()
#... more stuff
return result
Related
Context: I'm making an auctioning website for which I am using Flask-SQLAlchemy. My tables will need to have a many-to-many relationship (as one artpiece can have many user bids and a user can bid on many artpieces)
My question is: it is possible to add another column to my joining table to contain the id of the user bidding, the id of artpiece that they are bidding on and also how much they bid? Also if yes, how would I include this bid in the table when I add a record to said table?
bid_table = db.Table("bid_table",
db.Column("user_id", db.Integer, db.ForeignKey("user.user_id")),
db.Column("item_id", db.Integer, db.ForeignKey("artpiece.item_id"))
)
class User(db.Model):
user_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
username = db.Column(db.Integer, unique=True, nullable=False)
email = db.Column(db.String(50), unique =True, nullable=False)
password = db.Column(db.String(60), nullable=False)
creation_date = db.Column(db.DateTime, default=str(datetime.datetime.now()))
bids = db.relationship("Artpiece", secondary=bid_table, backref=db.backref("bids", lazy="dynamic"))
class Artpiece(db.Model):
item_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
artist = db.Column(db.String(40), nullable=False)
buyer = db.Column(db.String(40), nullable=False)
end_date = db.Column(db.String(40))
highest_bid = db.Column(db.String(40))
It is possible to do this with SQL Alchemy, but it's very cumbersome in my opinion.
SQLAlchemy uses a concept called an Association Proxy to turn a normal table into an association table. This table can have whatever data fields you want on it, but you have to manually tell SQLAlchemy which columns are foreign keys to the other two tables in question.
This is a good example from the documentation.
In your case, the UserKeyword table is the association proxy table that you want to build for your user/bid scenario.
The special_key column is the arbitrary data you would store like the bid amount.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
Check out the full documentation for instructions on how to access and create this kind of model.
Having used this in a real project, it's not particularly fun and if you can avoid it, I would recommend it.
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
I have 3 classes;
'Company' top class its subclass 'Department' its subclass 'DepartmentalUnit'
I can access the values of all classes from the 'DepartmentalUnit' class to the top class 'Company'
What I could not do and understand despite reading the document is that;;
I cannot access other subclasses from the 'company' class
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company')
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department')
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
The code from which I access the upper classes from the subclasses:
query = session.query(DepartmentalUnit)
instance = query.all()
for i in instance:
print(i.department.company.name)
print(i.department.department_name)
print(i.departmental_unit_name)
The code I can't access other subclasses from the company class:
query = session.query(Company)
instance = query.all()
for i in instance:
print(i.department.department_name)
Your last query should be used differently:
there is a typo in the name of the relationship: should be departments instead of department
given that the relationship is 1-N, the result is a list, so you should iterate over children.
This should work:
query = session.query(Company)
for company in query.all():
print(company.name)
for dep in company.departments:
print(" ", dep.department_name)
for dep_unit in dep.departmentalunits:
print(" ", dep_unit.departmental_unit_name)
I solved the problem. I added a backref to relationships and now I can access all of them from the company. Not sure if it's a correct method? However, I am currently getting the return I want. I have no unanswered request yet.
Example solved:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company',uselist=False)
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department',uselist=False)
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
query = session.query(Company)
instance = query.all()
for i in instance:
print(f"Company: {i.name}")
print(f"Department: {i.departments.department_name}")
print(f"Department Unit: {i.departments.departmentalunits.departmental_unit_name}")
print( f"Report Category : {i.departments.departmentalunits.reportcategoryoftheunit.report_category_name}")
Suppose we have a simple one-to-many relationship between Company and Employee, is there a way to query all companies and have a list of employees in the attribute of each company?
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
company_id = Column(Integer, ForeignKey(Company.id))
I'm looking for something like this:
>>> result = db.session.query(Company).join(Employee).all()
>>> result[0].Employee
[<Employee object at 0x...>, <Employee object at 0x...>]
The size of result should be same as the number of rows in company table.
I tried the following and it gives tuple of objects (which makes sense) instead of nice parent / child structure:
>>> db.session.query(Company, Employee).filter(Company.id = Employee.company_id).all()
It's not hard to convert this into my desired object structure but just wanted to see if there's any shortcut.
You have to configure the relationship in the parent class:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship('Employee', lazy='joined') # <<< Add this line
Then you can query it without a join:
companies = session.query(Company).all()
print(companies[0].employees)
Documentation:
https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html
You could do something like this:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = db.session.query(Company, Employee).filter(Company.id = self.id).all()
self.employee_list = ['{0} {1}'.format(c.first_name, c.last_name) for c in employees]
Then you could access a employee name with Company.employee_list[0]
I am new to ORM with SQLAlchemy and I only used to work with raw SQL. I have database tables, Label, Position, and DataSetlike following:
And the corresponding python classes following:
class Label(Base):
__tablename__ = 'Label'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=true)
class Position(Base):
__tablename__ = 'Position'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=true)
class DataSet(Base):
__tablename__ = 'DataSet'
id = Column(Integer, primary_key=True)
label_id = Column(Integer, ForeignKey('Label.id'))
position_id = Column(Integer, ForeignKey('Position.id'))
timestamp = Column(Integer, nullable=False)
But in my servie, I don't expose those label_id and position_id. So I made a new class Data to hold label and position as string.
# Not a full class to only show my concept
class Data:
# data dictionary will have data
def __init__(self, **kwargs):
# So it doesn't have ids. Label and Position as string
keys = {'label', 'position', 'timestamp'}
self.data = {k: kwargs[k] for k in keys}
# An example of inserting data.
# skipped detail and error handling to clarify
def insert(self):
session = Session()
# get id of label and position
# remember that it returns a tuple, not a single value
self.data['label_id'] = session.query(Label.id).\
filter(Label.name == self.data['label']).one_or_none()
self.data['position_id'] = session.query(Position.id).\
filter(Position.name == self.data['position']).one_or_none()
# add new dataset
self.data.pop('label')
self.data.pop('position')
new_data = DataSet(**self.data)
session.add(new_data)
session.commit()
But it looks somewhat ugly and I think there should be a simpler way to do it. Are there any way to combine these table classes using SQLAlchemy APIs?
You can use relationships and association proxies to make links from DataSet to Label and Position objects:
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
class DataSet(Base):
__tablename__ = 'DataSet'
id = Column(Integer, primary_key=True)
label_id = Column(Integer, ForeignKey('Label.id'))
label = relationship('Label')
label_name = association_proxy('label', 'name')
position_id = Column(Integer, ForeignKey('Position.id'))
position = relationship('Position')
position_name = association_proxy('position', 'name')
timestamp = Column(Integer, nullable=False)
After this you can access Label and Position objects linked to DataSet (and their names) through new attributes:
>>> d = session.query(DataSet).first()
>>> d.position
<Position object at 0x7f3021a9ed30>
>>> d.position_name
'position1'
Inserting DataSet objects is not so beautiful unfortunately. You can specify creator function for association_proxy which can get a name and create or retrieve a corresponding object (found in this answer):
def _label_creator(name):
session = Session()
label = session.query(Label).filter_by(name=name).first()
if not label:
label = Label(name=name)
session.add(label)
session.commit()
session.close()
return label
label_name = association_proxy('label', 'name', creator=_label_creator)
After specifying creator functions for both proxies you can create new DataSet objects this way:
dataset = DataSet(
label_name='label1',
position_name='position2',
timestamp=datetime.datetime.now()
)
I'm trying to take an existing string in a retrieved SQLAlchemy object and concatenate it with a second string, however no value is being written.
print(messages)
testsuite = session.query(Testsuite).get(test_id)
testsuite.console += messages
session.commit()
Inspecting the database, the record has kept its original empty value - messages was never added.
My Testsuite model is as follows:
# Represents schema - used by engine to create tables etc.
Base = declarative_base()
# These SQL fields are horrendously inefficient - need replacing ASAP!
class Testsuite(Base):
"""Testsuite model to map testsuite in progress to SQL DB."""
__tablename__ = 'testsuites'
id = Column(Integer, primary_key=True)
name = Column(String)
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
product_name = Column(String)
serial_number = Column(String)
total_tests = Column(Integer)
completed_tests = Column(Integer)
console = Column(Text)
report_id = Column(Integer)
testcases = relationship('Testcase', backref='testsuite')
result = Column(String)
def __init__(self, testsuite_name, product_name, serial_number, total_tests=0):
self.name = testsuite_name
self.product_name = product_name
self.serial_number = serial_number
self.total_tests = total_tests
self.completed_tests = 0
self.result = 'pending'
I've read that the way I am modifying my objects can lead to race conditions, though I am unsure of a suitable alternative. Can anyone point out the issues with what I'm doing and why my messages string isn't being added?
Thanks :)
So after a bit of experimentation, it seems that the code was failing because Testsuite.console never had an initial value.
The code now works with the following change to the mode:
class Testsuite(Base):
"""Testsuite model to map testsuite in progress to SQL DB."""
__tablename__ = 'testsuites'
id = Column(Integer, primary_key=True)
name = Column(String)
timestamp = Column(DateTime, default=datetime.datetime.utcnow)
product_name = Column(String)
serial_number = Column(String)
total_tests = Column(Integer)
completed_tests = Column(Integer, default=0)
console = Column(String, default="Waiting for incoming log data...\n")
report_id = Column(Integer)
testcases = relationship('Testcase', backref='testsuite')
result = Column(String, default='pending')
def __init__(self, testsuite_name, product_name, serial_number, total_tests=0):
self.name = testsuite_name
self.product_name = product_name
self.serial_number = serial_number
self.total_tests = total_tests