Given this polymorphic model
class OrganizationBase(Base):
__tablename__ = "x_organization_base"
__mapper_args__ = {
"polymorphic_identity": "base",
"polymorphic_on": "model_type",
}
model_type = db.Column(db.String(), nullable=False)
id = Column(Integer(), primary_key=True)
cont'd
class UmbrellaOrganization(OrganizationBase):
__tablename__ = "x_umbrella_organization"
__mapper_args__ = {"polymorphic_identity": "umbrella"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(db.String(255), nullable=False, unique=True)
properties = db.relationship(
"UmbrellaOrganizationProperty",
lazy="joined",
backref=backref("umbrella_organization", uselist=False),
)
class Organization(OrganizationBase):
__tablename__ = "x_organization"
__mapper_args__ = {"polymorphic_identity": "organization"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(
db.String(255),
db.ForeignKey(UmbrellaOrganization.umbrella_accounting_id),
nullable=False,
index=True,
)
and this eagerly loaded relationship
class UmbrellaOrganizationProperty(Base):
__tablename__ = "x_umbrella_organization_property"
id = Column(Integer(), primary_key=True)
umbrella_organization_id = db.Column(
Integer, db.ForeignKey(UmbrellaOrganization.id), nullable=False, index=True
)
type = db.Column(db.String(), nullable=False)
this query will produce invalid SQL:
query = (
db.session.query(
Organization,
UmbrellaOrganization,
)
.join(
UmbrellaOrganization,
UmbrellaOrganization.umbrella_accounting_id == Organization.umbrella_accounting_id,
)
)
y = query.limit(5)
Specically, there main query will be duplicated with the same alias 'anon_1' occuring twice:
ProgrammingError: (psycopg2.errors.DuplicateAlias) table name "anon_1" specified more than once
This only happens with limit() applied.
It appears that the polymorphism mapper wants to join the (eagerly loaded) UmbrellaOrganziationProperty to both the UmbrellaOrganization and OrganizationBase, even though it does not belong there. Without changing the model, the only way I have found to prevent this is telling it to not load OrganizationProperty eagerly, by adding this query option:
.options(lazyload(UmbrellaOrganization.properties))
This is potentially problematic because client code may expect the properties in the results. What else can I do?
Related
I need to create four tables. First table include the "Users", second include "Group Name", the second table should be related to "Users" table. Third table include "Groups Columns Data" which is related to "Group" table, and finally the fourth table is "Group Borrow Lending Data" which is also linked to third table i.e "Groups".
But it's throwing an error when I try to get specific username.
TypeError: sqlalchemy.exc.InvalidRequestError: Can't compare a
collection to an object or collection; use contains() to test for
membership.
#v1.get("/get-specific-groups/{group_name}", tags=["GROUP"])
def get_specific_groups(group_name: str, current_user: CreateGroupSchema = Depends(get_current_user), db: Session = Depends(get_db)):
return db.query(User, Group, GroupColumnsData).join(GroupColumnsData).filter(Group.owner_username == current_user.get("username")).all()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(60), unique=True, nullable=False)
email = Column(String(80), unique=True, nullable=False)
password = Column(String(140), nullable=False)
groups = relationship("Group", backref="owner")
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True, index=True)
group_name = Column(String(60), unique=True, nullable=False)
description = Column(String, nullable=False)
owner_username = Column(String, ForeignKey("users.username"), default=User.username)
group_username = relationship("GroupColumnsData", backref="group_owner")
class GroupColumnsData(Base):
__tablename__ = "groupsColumnsData"
id = Column(Integer, primary_key=True, index=True)
payee_name = Column(String(60))
item_name = Column(String(100))
amount_spent = Column(Integer)
owner_group = Column(String, ForeignKey("groups.group_name"), default=Group.group_name)
class GroupBorrowLendingData(Base):
__tablename__ = "groupsBorrowLendingData"
id = Column(Integer, primary_key=True, index=True)
lender = Column(String(60))
money_borrowed = Column(Integer)
borrower = Column(String(60))
owner_group = Column(String, ForeignKey("groups.group_name"), default=Group.group_name)
The Following code worked!
#v1.get("/get-specific-groups/{group_name}", tags=["GROUP"])
def get_specific_groups(group_name: str, current_user: CreateGroupSchema = Depends(get_current_user), db: Session = Depends(get_db)):
return db.query(Group).filter(Group.owner_username == current_user.get("username")).filter(Group.group_name.match(group_name)).all()
These are the classes:
class User(db.Model):
__tablename__ = 'Users'
UserID = db.Column(
db.Integer,
primary_key=True,
autoincrement=True,
nullable=False
)
FirstName = db.Column(db.String(255), nullable=False)
LastName = db.Column(db.String(255), nullable=False)
Username = db.Column(db.String(255), nullable=False)
Password = db.Column(db.String(255), nullable=False)
class UserType(db.Model):
__tablename__ = 'UserTypes'
TypeID = db.Column(
db.Integer,
primary_key=True,
autoincrement=True,
nullable=False
)
Type = db.Column(db.String(255), nullable=False)
__table_args__ = (
db.CheckConstraint(
"Type IN ('Role1', 'Role2', 'Role3')"
),
)
class UserPrivilege(db.Model):
__tablename__ = 'UserPrivileges'
UserID = db.Column(db.Integer, primary_key=True)
UserTypeID = db.Column(db.Integer, primary_key=True)
__table_args__ = (
db.ForeignKeyConstraint(
['UserID'],
['Users.UserID'],
),
db.ForeignKeyConstraint(
['UserTypeID'],
['UserTypes.TypeID'],
),
)
PrivilegeUserInfoBackref = db.relationship(
'User',
backref='PrivilegeUserInfoBackref',
lazy=True,
)
PrivilegeUserTypeInfoBackref = db.relationship(
'UserType',
backref='PrivilegeUserTypeInfoBackref',
lazy=True,
)
And here is the code for reflecting the tables:
Base = automap_base()
engine = sa.create_engine(
DATABASE_CONNECTION,
convert_unicode=True,
pool_size=10,
max_overflow=20
)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base.prepare(engine, reflect=True)
The 'Users' and 'UserTypes' classes appear in Base.classes._data but for some reasson 'UserPrivileges' does not appear in Base.classes._data. All I managed to find is that tables with no primary key can't be reflected but as you can see that is not the case here. I also have some more tables that have composite primary key with backrefs but that are reflected with no problem.
So, can anyone give me any suggestions in order to reflect the last table as well, please ?
The table created for UserPrivilege ticks all the boxes of a many-to-many relationship's "secondary" table, and as such is not mapped directly when using the automap extension. This behaviour is also explained in the note of "Basic Use":
By viable, we mean that for a table to be mapped, it must specify a primary key. Additionally, if the table is detected as being a pure association table between two other tables, it will not be directly mapped and will instead be configured as a many-to-many table between the mappings for the two referring tables.
Your table should exist as Base.metadata.tables['UserPrivileges'].
This might be a dumb question, but I can't find anything that indicates that you can't, however, when I try it, it does not work. I have an example of such an instance, but I can't reproduce the results.
Using SQLAlchemy, here is the working instance:
class Commissions(Base):
__tablename__ = "commissions"
id = Column(
Integer,
Sequence("commission_id", optional=True),
primary_key=True
)
commission_period_name = Column(
Unicode(40), ForeignKey("commission_periods.name"))
commission_period = relationship(
"CommissionPeriods",
primaryjoin="CommissionPeriods.name == Commissions.commission_period_name",
uselist=False,
backref="commissions"
)
agent_id = Column(Integer, ForeignKey("agents.id"))
agent = relationship(
Agents,
primaryjoin=Agents.id == agent_id,
uselist=False,
backref="commissions"
)
create_stamp = Column(DateTime)
commission_type = Column(Unicode(40))
amount = Column(Numeric(10, 2))
transactions_id = Column(Integer, ForeignKey(Transactions.id))
transaction = relationship(Transactions, primaryjoin=Transactions.id ==
transactions_id, backref="commissions", uselist=False)
Note the commission_period uses name, not an id as reference.
Here is the CommissionPeriods table definition:
class CommissionPeriods(Base):
__tablename__ = "commission_periods"
id = Column(
Integer,
Sequence("commission_periods_id", optional=True),
primary_key=True
)
name = Column(Unicode(40), index=True)
start_date = Column(DateTime(), index=True)
end_date = Column(DateTime(), index=True)
network = Column(Unicode(40), index=True)
status = Column(Unicode(40), index=True)
created_by = Column(Unicode(40))
create_stamp = Column(DateTime())
modify_stamp = Column(DateTime())
The alembic scripts runs without errors.
I have tried to replicate the results, with different tables (more or less the same table structures, with a name column that I am trying to use as a FK), but I have had no luck - on the python side everything is okay, but once I try to run the alembic scripts it tells me that the Foreign key is incorrectly formed.
Can someone please explain to me how this works, why it works in the given instance but not when I try to replicate the results from the above?
This is what I have tried in replicating the results:
class Networks(Base):
__tablename__ = "networks"
id = Column(
Integer,
Sequence('networks_id', optional=True),
primary_key=True
)
name = Column(Unicode(40), index=True)
rica_name = Column(Unicode(40))
net_iccid_start = Column(Integer)
net_iccid_end = Column(Integer)
net_iccid_int = Column(Integer)
network_class = Column(Unicode(60))
_config = Column("config", Unicode(2048))
Note that in the above table I want to use the name column as foreign key in:
class AgentRecharges(Base):
__tablename__ = "agent_recharges"
id = Column(
Integer,
Sequence('agent_recharges_id', optional=True),
primary_key=True
)
status = Column(Unicode(40))
create_stamp = Column(DateTime, index=True)
create_by = Column(Integer, ForeignKey(Agents.id))
create_by_agent = relationship(
Agents, primaryjoin=Agents.id == create_by, uselist=False)
modify_stamp = Column(DateTime, index=True)
complete_stamp = Column(DateTime, index=True)
msisdn = Column(Unicode(20), index=True, nullable=False)
amount = Column(Float, index=True, nullable=False)
network_name = Column(Unicode(40), ForeignKey(
"networks.name"), nullable=False)
network = relationship(
"Networks", primaryjoin="Networks.name == AgentRecharges.network_name", uselist=False)
iccid = Column(Unicode(40))
sim = relationship(Sims, backref="agent_recharges")
agents_id = Column(Integer, ForeignKey(Agents.id))
agent = relationship(Agents, primaryjoin=Agents.id ==
agents_id, uselist=False)
transactions_id = Column(Integer, ForeignKey(Transactions.id))
transaction = relationship(Transactions, primaryjoin=Transactions.id ==
transactions_id, backref="agent_recharges", uselist=False)
recharge_batch_id = Column(Integer, ForeignKey(RechargeBatches.id))
recharge_batch = relationship(RechargeBatches)
When I run the alembic script to add this new table, it tells me that the foreign key is incorrectly formed.
Any ideas on why, and how I can accomplish my goal?
I have two simple models:
class Message(Backend.instance().get_base()):
__tablename__ = 'messages'
id = Column(Integer, primary_key=True, autoincrement=True)
sender_id = Column(Integer, ForeignKey('users.id'))
content = Column(String, nullable=False)
class ChatMessage(Message):
__tablename__ = 'chat_messages'
id = Column(Integer, ForeignKey('messages.id'), primary_key=True)
receiver_id = Column(Integer, ForeignKey('users.id'))
How to define constraint sender_id!=receiver_id?
This doesn't seem to work with joined table inheritance, I've tried and it complains that the column sender_id from Message doesn't exist when creating the constraint in ChatMessage.
This complaint makes sense, since sender_id wouldn't be in the same table as receiver_id when the tables are created, so the foreign key relationship would need to be followed to check the constraint.
One option is to make ChatMessage a single table.
Use CheckConstraint, placed in table args.
class ChatMessage(Base):
__tablename__ = 'chat_messages'
id = sa.Column(sa.Integer, primary_key=True)
sender_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))
receiver_id = sa.Column(sa.Integer, sa.ForeignKey(User.id))
content = sa.Column(sa.String, nullable=False)
__table_args__ = (
sa.CheckConstraint(receiver_id != sender_id),
)
The business logic - One Category may have multiple (1:M) attributes, like Category "Memory" could have attributes Speed, Size, Type etc.
at the same time one Category could be sorted by the attribute value (this is stored inside Category.sortByAttribute - which is foreign key to LookupCategoryAttributes table.
Trying to construct it via SQLAlchemy, but getting circular dependency detected. What is wrong?
class Attribute(Base):
__tablename__ = "LookupCategoryAttributes"
types = ["date", "float", "integer", "select", "string", "text"]
# Properties
ID = Column(BigInteger, primary_key=True)
categoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'), nullable=False )
attribute = Column(VARCHAR(255), nullable=False)
listValues = Column(VARCHAR(4000))
typeID = Column(VARCHAR(40), nullable=False)
isRequired = Column(SmallInteger, nullable=False, default=0)
displayInMenu = Column(SmallInteger, nullable=False, default=0)
displayInFilter = Column(SmallInteger, nullable=False, default=0)
class Category(Base):
__tablename__ = "LookupCategories"
# Properties
ID = Column(BigInteger, primary_key=True)
category = Column(VARCHAR(255), nullable=False)
description = Column(VARCHAR(1000), nullable=False)
parentCategoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'))
leftPos = Column(Integer)
rightPos = Column(Integer)
sortByAttribute = Column(BigInteger, ForeignKey('LookupCategoryAttributes.ID'))
sortOrder = Column(SmallInteger, default=1)
# Relationships
ParentCategory = relationship("Category", uselist=False, remote_side=[ID], backref='SubCategories')
SortByAttribute = relationship("Attribute", uselist=False, foreign_keys=[sortByAttribute], primaryjoin="Attribute.ID==Category.sortByAttribute")
Attributes = relationship("Attribute", backref="Category", primaryjoin="Attribute.categoryID==Category.ID")
and then the code looks like this:
category = Category(record['Name'], extID=extID)
attr1 = Attribute(v)
attr2 = Attribute(v)
category.Attributes.append(attr1)
category.Attributes.append(attr2)
category.SortByAttribute = attr1
when I execute commit I get:
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.
Okay found the answer - use post_update in relationship
http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#post-update
so what I did is inside Category class is changed this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute"
)
to this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute",
post_update=True
)