because of the following line: contact_person.selections_to_persons[selection_id].post_address = address
i get the following error at the next commit:
AssertionError: Dependency rule tried to blank-out primary key column 'selections_to_persons.t_person_to_department_id' on instance ''
The important parts of the involved models are:
class SelectionToPerson(OrmModelBase, TableModelBase):
__tablename__ = 'selections_to_persons'
__table_args__ = (
ForeignKeyConstraint(
["address_tablename",
"address_id",
"t_person_to_department_id"],
["address_collection.tablename",
"address_collection.id",
"address_collection.t_person_to_department_id"],
name="fk_post_address_selection_to_person", use_alter=True
),
)
selection_id = Column(Integer,
ForeignKey('selections.selection_id',
onupdate=NO_ACTION,
ondelete=CASCADE),
primary_key=True, nullable=False)
t_person_to_department_id = Column(
Integer,
ForeignKey('t_persons_to_departments.t_person_to_department_id',
onupdate=NO_ACTION,
ondelete=CASCADE),
primary_key=True,
nullable=False)
address_tablename = Column(String, nullable=False)
address_id = Column(Integer, nullable=False)
post_address = relationship(AddressCollection)
class AddressCollection(OrmModelBase, ViewModelBase):
__tablename__ = 'address_collection'
tablename = Column(String, primary_key=True)
id = Column(Integer, primary_key=True)
t_person_to_department_id = Column(
Integer,
ForeignKey('t_persons_to_departments.t_person_to_department_id'),
primary_key=True)
Does anyone know why this error occurs?
One of the cases when this error occurs is an attempt to assign null to a field that is a primary key.
You have several primary keys that are specified by foreign keys.
I don't know for sure, but it is possible that the expression contact_person.selections_to_persons[selection_id].post_address = address created an object with null reference. That is, after assignment, some object remains with a null reference.
I am leaving a few links that describe how to use cascades in different cases. This might help those who get this error.
This is how cascades work:
https://docs.sqlalchemy.org/en/13/orm/cascades.html#unitofwork-cascades
Here's how you can configure cascades using the example of deleting: https://docs.sqlalchemy.org/en/13/orm/tutorial.html#configuring-delete-delete-orphan-cascade
Related
I get the following warning from SQLAlchemy and I wonder what the problem is:
\venv\lib\site-packages\sqlalchemy\orm\strategies.py:911: SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'Similarity.tag1'
util.warn(
\venv\lib\site-packages\sqlalchemy\orm\strategies.py:911: SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'Similarity.tag'
util.warn(
My ORM classes looks like this:
class Similarity(db.Model):
__tablename__ = 'similarities'
tag_id_1 = db.Column(db.ForeignKey('tags.id'), primary_key=True, nullable=False)
tag_id_2 = db.Column(db.ForeignKey('tags.id'), primary_key=True, nullable=False, index=True)
value = db.Column(DOUBLE, nullable=False)
tag = db.relationship('Tag', primaryjoin='Similarity.tag_id_1 == Tag.id', backref='tag_similarities')
tag1 = db.relationship('Tag', primaryjoin='Similarity.tag_id_2 == Tag.id', backref='tag_similarities_0')
class Tag(db.Model):
__tablename__ = 'tags'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
language_id = db.Column(db.ForeignKey('languages.id'), primary_key=True, nullable=False, index=True)
name = db.Column(db.String(45), nullable=False, index=True)
language = db.relationship('Language', primaryjoin='Tag.language_id == Language.id', backref='tags')
I use SQLAlchemy 1.3.22 and Python 3.8. Can you please explain what the message is about and how I can fix this?
As #zvone said, the relationships tag and tag1 can point to multiple tags because Tag has two primary columns. That's what the warning message is about: The relationships aren't unique.
Therefore, the solution is either to remove these relationships or to make them unique.
I have a relationship that is one to one between cage codes and duns numbers.
I have set up my relationship that looks like, where I store a ForeignKey on each of the respective tables.
class Cage(Base):
__tablename__ = 'DimCage'
id = Column(Integer, primary_key=True)
cage = Column(String(8), unique=True, nullable=False)
duns_id = Column(Integer, ForeignKey('DimDuns.id'))
duns = relationship('Duns', uselist=False, back_populates='cage')
class Duns(Base):
__tablename__ = 'DimDuns'
id = Column(Integer, primary_key=True)
duns = Column(String(10), unique=True, nullable=False)
dunsInt = Column(Integer, unique=True, nullable=False)
cage_id = Column(Integer, ForeignKey('DimCage.id'))
cage = relationship('Cage', uselist=False, back_populates='duns')
When I create the tables I get the below error, how to do I set up my foreign keys so I can keep a reference on both tables?
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'DimCage' and 'DimDuns'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
And During handling of the above exception, another exception occurred:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Cage.duns - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
I believe you only need to store one foreign key for a one-to-one relationship.
See https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html#one-to-one
You shouldn't lose any data access this way. If you remove the duns_id column from Cage, you now access the id by cage.duns.id instead of cage.duns_id.
You could set the child primary key as a foreign key to the parent.
For a design perspective, it is not a bad choice to use foreign keys as primary keys in One-to-One relationships.
class Cage(Base):
__tablename__ = 'DimCage'
id = Column(Integer, primary_key=True)
cage = Column(String(8), unique=True, nullable=False)
class Duns(Base):
__tablename__ = 'DimDuns'
id = Column(Integer, ForeignKey(Cage.id) primary_key=True)
duns = Column(String(10), unique=True, nullable=False)
dunsInt = Column(Integer, unique=True, nullable=False)
cage = relationship('Cage', uselist=False, backref='duns')
Now both cage and duns have the same id. So:
session.add(Cage(id=1, duns=Duns(duns='Duns', dunsInt=10)))
sesion.commit()
id = 1
cage = select(Cage).where(Cage.id == id)
duns = select(Duns).where(Duns.id == cage.id)
assert cage.id == duns.id
Please note that the child cannot exist without the parent.
If the parent is going to be deleted then the child must be deleted first, unless you configure some cascade option.
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:
I've got a Python Flask app using flask.ext.sqlalchemy and apscheduler.schedulers.background. I've created a JobStore and gotten a table called apscheduler_jobs is has the following fields:
|id |next_run_time|job_state|
------------------------------
|TEXT| REAL | TEXT |
I want to relate a an SQLAlchemy Model object to that table using something like this:
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
scheduler.add_jobstore('sqlalchemy', url=app.config['SQLALCHEMY_DATABASE_URI'])
class Event(db.Model):
__tablename__ = "event"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
jobs = db.relationship('scheduler', backref='apscheduler_jobs')
So I want to use the table from the APScheduler apscheduler_jobs and then associate that with a foreign key to my Event object. That last line there will basically break as "scheduler" isn't a defined SQLAlchmey model
qlalchemy.exc.InvalidRequestError: When initializing mapper Mapper|Event|event, expression 'scheduler' failed to locate a name ("name 'scheduler' is not defined"). If this is a class name, consider adding this relationship() to the <class 'project.models.Event'> class after both dependent classes have been defined.
So I think I need an inbetween Model class called "job" or something, then relate that to apscheduler_jobs, but something here still feels bad - because APScheduler is making this table up I've got no control over what's going on there - should I be concerned about that?
EDIT1:
So I created 2 models, one "Event" then one "Job", the "Job" then relates to the table apscheduler_jobs
class Job(db.Model):
__tablename__ = "job"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
apscheduler_job_id = db.Column(db.Integer, db.ForeignKey('apscheduler_jobs.id'))
event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
problem there is that when I dropped the DB and recreated it it's thrown the error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'job.apscheduler_job_id' could not find table 'apscheduler_jobs' with which to generate a foreign key to target column 'id'
Now I could get around that in my database creation script, but again it still feels like I'm doing this the wrong way
EDIT2
I managed to get it to work, though this feels pretty wrong, I've now got 3 models: Event, Job, and APSchedulerJobsTable. The final model basically matches what the APScheduler apscheduler_jobs looks like. There must be a better way to do this though.
from project import db
class Event(db.Model):
__tablename__ = "event"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
jobs = db.relationship('Job', backref='job_event')
class Job(db.Model):
__tablename__ = "job"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
apscheduler_job_id = db.Column(db.TEXT, db.ForeignKey('apscheduler_jobs.id'))
event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
class APSchedulerJobsTable(db.Model):
# TODO: This feels bad man
__tablename__ = "apscheduler_jobs"
id = db.Column(db.TEXT, primary_key=True, autoincrement=True)
next_run_time = db.Column(db.REAL)
job_state = db.Column(db.TEXT)
Ok, two solutions - neither really perfect IMO:
Solution One, probably more clean - simply have a Text field in the job table that contains aspscheduler_job_ids - this is not a foreign key though but once the aspscheduler_job ID is known it's possible to go ahead and store it in the job table for later reference
class Event(db.Model):
__tablename__ = "event"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
jobs = db.relationship('Job', backref='job_event')
class Job(db.Model):
__tablename__ = "job"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
apscheduler_job_id = db.Column(db.TEXT)
Catch for this one is in order to drop the full db you'll need to run this to include dropping the unmanaged table apscheduler_jobs:
db.reflect()
db.drop_all()
Solution Two, add the apscheduler table to the model itself, and then set up the foreign key:
class Event(db.Model):
__tablename__ = "event"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(255), nullable=False)
jobs = db.relationship('Job', backref='job_event')
class Job(db.Model):
__tablename__ = "job"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
apscheduler_job_id = db.Column(db.TEXT, db.ForeignKey('apscheduler_jobs.id'))
class APSchedulerJobsTable(db.Model):
# TODO: This feels bad man
__tablename__ = "apscheduler_jobs"
id = db.Column(db.TEXT, primary_key=True, autoincrement=True)
next_run_time = db.Column(db.REAL)
job_state = db.Column(db.TEXT)
job = db.relationship('Job', backref='job_event')
I'm having the worst time trying to get a many-many join to work using models in SQLAlchemy. I've found lots of examples online, but I can't ever seem to figure out if their strings represent the column names they'd like versus what the database tables actually have, or they're using Table instead of a declarative model, or something else is different and their example just doesn't work. I currently have the following setup:
Database tables TAG_TEST, TAG, and TEST
TAG_TEST has TS_TEST_ID, TG_TAG_ID, and TG_TYPE (foreign keys)
TAG has TG_TAG_ID and TG_TYPE
TEST has TS_TEST_ID
I have the following models:
class Test(Base):
from .tag import Tag
from .tag_test import TagTest
__tablename__ = u'TEST'
id = Column(u'TS_TEST_ID', INTEGER(), primary_key=True, nullable=False)
...
tags = relationship(Tag, secondary='TAG_TEST')
class Tag(Base):
from .tag_test import TagTest
__tablename__ = "TAG"
id = Column(u'TG_TAG_ID', INTEGER(), primary_key=True, nullable=False)
type = Column(u'TG_TYPE', VARCHAR(25))
...
tests = relationship("Test", secondary='TAG_TEST')
class TagTest(Base):
__tablename__ = u'TAG_TEST'
tagID = Column(u'TG_TAG_ID', INTEGER(), ForeignKey("TAG.TG_TAG_ID"), primary_key=True, nullable=False)
testID = Column(u'TS_TEST_ID', INTEGER(), ForeignKey("TEST.TS_TEST_ID"), primary_key=True, nullable=False)
tagType = Column(u'TG_TYPE', VARCHAR(50), ForeignKey("TAG.TG_TYPE"), primary_key=True, nullable=False)
...
tag = relationship("Tag", backref="testLinks")
test = relationship("Test", backref="tagLinks")
Currently I'm getting the following error:
ArgumentError: Could not determine join condition between parent/child tables on relationship Tag.tests. Specify a 'primaryjoin' expression. If 'secondary' is present, 'secondaryjoin' is needed as well.
What am I missing/doing wrong?
The tricky part with mine was the composite foreign key to the Tag table. Here's my setup:
class TagTest(Base):
__table_args__ = (ForeignKeyConstraint(['TG_TAG_ID', 'TG_TYPE'],
['TAG.TG_TAG_ID', 'TAG.TG_TYPE']), {})
tagID = Column(u'TG_TAG_ID', INTEGER(), primary_key=True, nullable=False)
testID = Column(u'TS_TEST_ID', INTEGER(), ForeignKey("TEST.TS_TEST_ID"), primary_key=True, nullable=False)
tagType = Column(u'TG_TYPE', VARCHAR(50), primary_key=True, nullable=False)
tag = relationship(Tag, backref="testLinks")
test = relationship(Test, backref="tagLinks")
class Tag(Base):
tests = relationship("Test", secondary="TAG_TEST")
Then to access the tests a tag has, I can do myTag.tests. To access the tags a test has, I can do myTest.tagLinks and then access .tag on each object in the .tagLinks property. Not as neat as I'd like, but it works.