SQLAlchemy can't reflect table with primary key - python

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'].

Related

Subquery/alias duplication in SqlAlchemy with polymorphic tables and "limit"

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?

SQLAlchemy: How to describe a many to many relationship with a bidirectional association object and composite foreign key?

I want to define a Many-to-Many Relationship with an Association Object like it's described in the SQLAlchemy Documentation. Additionally, my Project model uses a composite primary key.
I also referred to the various answers at another Stackoverflow Question and came up with this minimal example:
class User(Base):
__tablename__ = "user"
user_id = Column(String, primary_key=True)
subscriptions = relationship(
"Subscription", back_populates="user"
)
class Subscription(Base):
__tablename__ = "subscription"
__table_args__ = (
ForeignKeyConstraint(
['project_id', 'repo'],
['project.project_id', 'project.repo'],
),
)
user_id = Column(ForeignKey("user.user_id"), primary_key=True)
project_id = Column(ForeignKey("project.project_id"), primary_key=True)
repo = Column(ForeignKey("project.repo"), primary_key=True)
extra = Column(String(50))
user = relationship("User", back_populates="subscriptions")
project = relationship("Project", back_populates="subscribers", foreign_keys="[User.user_id, Project.project_id, Project.repo]")
class Project(Base):
__tablename__ = 'project'
project_id = Column(String, primary_key=True)
repo = Column(String, primary_key=True)
subscribers = relationship(
"Subscription", back_populates="project"
)
However it gives me a NoForeignKeysError at the Subscription.project relationship. What am I doing wrong here?

Deleting many tables when one specific table is deleted in FLASK SQLAlchemy

I have an SQLAlchemy VolunteerClient Model which is a join table:
class VolunteerClient(Base):
__tablename__ = 'volunteer_client'
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
volunteer_id = Column(Integer, ForeignKey('provider_user.user_id', onupdate='CASCADE', ondelete='RESTRICT'), unique=True)
client_id = Column(Integer, ForeignKey('user.id', onupdate='CASCADE', ondelete='RESTRICT'), unique=True)
and a VolunteerReport model:
class VolunteerReport(Base):
__tablename__ = 'volunteer_report'
id = Column(Integer, primary_key=True, autoincrement=True, index=True)
volunteer_id = Column(Integer, ForeignKey('volunteer_client.volunteer_id', cascade="all, delete"))
client_id = Column(Integer, ForeignKey('volunteer_client.client_id', cascade="all, delete"))
report = Column(String(255), nullable=False)
report_category = Column(String(255), nullable=False)
If I was to delete a VolunteerClient table, which essentially unassignes a volunteer from a client but does not actually delete the users which they represent. with a specific volunteer_id and client_id set,
Example: I delete VolunteerClient table where: volunteer_id = 1, & client_id = 1,
I want any and all VolunteerReports where: volunteer_id = 1, & client_id = 1 to be deleted as well. Have I set this up correctly with the FK references to volunteer_client and the cascade='all, delete'?
Any advice would be awesome.
To automatically delete child rows when a parent is deleted, you need to set ondelete='CASCADE' on the foreign key defined in the child table. In this case, the foreign key is a composite key as it consists of volunteer_id and client_id. This means you need a unique constraint over the columns in the parent as well. This simplified version of your models show how it would work (I've removed the FK definitions from VolunteerClient the tables are defined in the question).
class VolunteerClient(Base):
__tablename__ = 'volunteer_client'
id = sa.Column(sa.Integer, primary_key=True)
volunteer_id = sa.Column(sa.Integer)
client_id = sa.Column(sa.Integer)
__table_args__ = (sa.UniqueConstraint(volunteer_id, client_id),)
class VolunteerReport(Base):
__tablename__ = 'volunteer_report'
id = sa.Column(sa.Integer, primary_key=True)
volunteer_id = sa.Column(sa.Integer)
client_id = sa.Column(sa.Integer)
__table_args__ = (
sa.ForeignKeyConstraint(
[volunteer_id, client_id],
[VolunteerClient.volunteer_id, VolunteerClient.client_id],
ondelete='CASCADE',
),
)
You can also configure delete cascades on SQLAlchemy relationships, which give you more control over what happens when a parent row is deleted. As you don't seem to be using relationships, and the database cascade does what you want I won't cover that in this answer.

Flask-SQLAlchemy how to do constraint foreign key with cascade in MySQL (InnoDB)?

I've been looking for ways to implement the CONSTRAINT FOREIGN KEY ON DELETE CASCADE in the below UsersAccessMapping model in SQLAlchemy with PyMySQL driver and MariaDB 10.0 with InnoDB in the database.
Python = 3.5.2
SQLAlchemy = 1.1.13
Flask-SQLAlchemy = 2.2
The SQL:
CREATE TABLE Users (
UserID int AUTO_INCREMENT,
Name varchar(200) NOT NULL,
Email varchar(200),
Username varchar(200) NOT NULL,
Password text NOT NULL,
Created datetime,
Updated datetime,
PRIMARY KEY (UserID)
);
CREATE TABLE UsersAccessLevels (
UsersAccessLevelID int AUTO_INCREMENT,
LevelName varchar(100) NOT NULL,
AccessDescription text,
PRIMARY KEY (UsersAccessLevelID)
);
CREATE TABLE UsersAccessMapping (
UsersAccessMappingID int AUTO_INCREMENT,
UserID int NOT NULL,
UsersAccessLevelID int NOT NULL,
PRIMARY KEY (UsersAccessMappingID),
CONSTRAINT fk_useraccess FOREIGN KEY (UserID)
REFERENCES Users(UserID) ON DELETE CASCADE,
CONSTRAINT fk_useraccess_level FOREIGN KEY (UsersAccessLevelID)
REFERENCES UsersAccessLevels(UsersAccessLevelID) ON DELETE CASCADE
);
What I have in my models.py now:
from app import db
class Users(db.Model):
"""All users' information is stored here"""
__tablename__ = "Users"
UserID = db.Column(db.Integer(), primary_key=True)
Name = db.Column(db.String(200), nullable=False)
Email = db.Column(db.String(200))
Username = db.Column(db.String(200), nullable=False)
Password = db.Column(db.Text, nullable=False)
Created = db.Column(db.DateTime)
Updated = db.Column(db.DateTime)
class UsersAccessLevels(db.Model):
"""This defines the various access levels users can have"""
__tablename__ = "UsersAccessLevels"
UsersAccessLevelID = db.Column(db.Integer, primary_key=True)
LevelName = db.Column(db.String(100), nullable=False)
AccessDescription = db.Column(db.Text)
class UsersAccessMapping(db.Model):
"""Each users' access level is defined here"""
__tablename__ = "UsersAccessMapping"
UsersAccessMappingID = db.Column(db.Integer, primary_key=True)
UserID = db.Column(db.Integer, nullable=False)
UsersAccessLevelID = db.Column(db.Integer, nullable=False)
__table_args__ = (
db.ForeignKeyConstraint(
["fk_useraccess", "fk_useraccess_level"],
["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"],
ondelete="CASCADE"
)
)
There is something wrong with the table_args syntax, but I haven't been able to find any examples on how it should be. I found one that was very similar, but in that the third parameter was an empty dict. However, I want to use the ondelete="CASCADE". How would that be added?
When running the python3 manage.py db init, it throws this:
File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/ext/declarative/base.py", line 196, in _scan_attributes
"__table_args__ value must be a tuple, "
sqlalchemy.exc.ArgumentError: __table_args__ value must be a tuple, dict, or None
I tried changing ondelete="cascade" to a dict {"ondelete": "cascade"}, but that doesn't work either. It gives the same error as above.
Update:
The problem was that the ondelete is supposed to be outside of the tuple, like this:
__table_args__ = (
db.ForeignKeyConstraint(
["fk_useraccess", "fk_useraccess_level"],
["Users.UserID", "UsersAccessLevels.UsersAccessLevelID"]
),
ondelete="CASCADE"
)
However, with this change there is still a syntax error, as ondelete="CASCADE" is not defined. Changing it to a dict {"ondelete": "cascade"} throws this:
File "/srv/vortech-backend/venv/lib/python3.5/site-packages/sqlalchemy/sql/base.py", line 282, in _validate_dialect_kwargs
"named <dialectname>_<argument>, got '%s'" % k)
TypeError: Additional arguments should be named <dialectname>_<argument>, got 'ondelete'
Okay, after some testing and reading, the answer is that SQLAlchemy does some internal magic to achieve it. So, this will accomplish the same result as the SQL:
from app import db # The value is from: db = SQLAlchemy(app)
class Users(db.Model):
"""All users' information is stored here"""
__tablename__ = "Users"
UserID = db.Column(db.Integer(), primary_key=True)
Name = db.Column(db.String(200), nullable=False)
Email = db.Column(db.String(200))
Username = db.Column(db.String(200), nullable=False)
Password = db.Column(db.Text, nullable=False)
Created = db.Column(db.DateTime)
Updated = db.Column(db.DateTime)
class UsersAccessLevels(db.Model):
"""This defines the various access levels users can have"""
__tablename__ = "UsersAccessLevels"
UsersAccessLevelID = db.Column(db.Integer, primary_key=True)
LevelName = db.Column(db.String(100), nullable=False)
AccessDescription = db.Column(db.Text)
class UsersAccessMapping(db.Model):
"""Each users' access level is defined here"""
__tablename__ = "UsersAccessMapping"
UsersAccessMappingID = db.Column(db.Integer, primary_key=True)
UserID = db.Column(
db.Integer, db.ForeignKey("Users.UserID", ondelete="CASCADE"), nullable=False
)
UsersAccessLevelID = db.Column(
db.Integer,
db.ForeignKey("UsersAccessLevels.UsersAccessLevelID", ondelete="CASCADE"),
nullable=False
)
The Constraints and such are automagically handled with the db.ForeignKey() parameters in the column definition. It does not need to be done on the Table directly, like in SQL.
The names for the foreign keys appear to be automatically generated by SQLAlchemy also. Here's how it looks like in the database:

Many to many relationship with a composite key on SQLAlchemy

Let's say I have the following model:
class Molecule(Base):
db = Column(Integer, primary_key=True)
id = Column(Integer, primary_key=True)
data = Column(Integer)
class Atom(Base):
id = Column(Integer, primary_key=True)
weight = Column(Integer)
And I want to establish a many-to-many relationship between Molecule and Atom, what would be the best way to do it? Notice that the primary key of Molecule is composite.
Thanks
many-to-many association tables should be defined like this:
molecule2atom = Table(
'molecule2atom',
Base.metadata,
Column('molecule_db', Integer),
Column('molecule_id', Integer),
Column('atom_id', Integer, ForeignKey('atom.id')),
ForeignKeyConstraint(
('molecule_db', 'molecule_id'),
('molecule.db', 'molecule.id') ),
)
And add the relatiohship to one of the models as usual, for example, in Class Atom add:
molecules = relationship("Molecule", secondary=molecule2atom, backref="atoms")
I liked the solution given here better - composite key many to many
If you're using an association table or fully declared table metadata, you can use the primary_key=True in both columns, as suggested here.
Association table example:
employee_role = db.Table(
"employee_role",
db.Column("role_id", db.Integer, db.ForeignKey("role.id"), primary_key=True),
db.Column("employee_id", db.Integer, db.ForeignKey("agent.id"), primary_key=True),
)
Metadata example:
# this is using SQLAlchemy
class EmployeeRole(Base):
__tablename__ = "employee_role"
role_id = Column(Integer, primary_key=True)
employee_id = Column(Integer, primary_key=True)
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class EmployeeRole(db.Model):
__tablename__ = "employee_role"
role_id = db.Column(db.Integer, primary_key=True)
employee_id = db.Column(db.Integer, primary_key=True)
Alembic migration for it:
op.create_table(
'employee_role',
sa.Column('role_id', sa.Integer(), nullable=False),
sa.Column('employee_id', sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint('role_id', 'employee_id')
)
SQL:
CREATE TABLE agent_role (
role_id INTEGER NOT NULL,
employee_id INTEGER NOT NULL,
PRIMARY KEY (role_id, employee_id)
);
In terms of relationship, declare it on one side (this should give you role.employees or employee.roles which should return a list):
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
roles = db.relationship("Role", secondary=employee_role, backref="employee")
Your Role class can be:
# this is using Flask-SQLAlchemy with factory pattern, db gives you access to all SQLAlchemy stuff
class Role(db.Model):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(25), nullable=False, unique=True)

Categories

Resources