SQLAlchemy Foreign key constraint confusion - python

I just want to delete a survey record in the Survey table, and the record in SurveyQuestions should be deleted too. I've tried cascade, passive_deletes, and ondelete. I keep getting the foreign key violation error no matter what I try from the documentation. Is it the way my tables are set up?
class Survey(Base):
__tablename__ = 'survey'
id = Column(Integer, primary_key=True, autoincrement=True)
survey_description = Column(String(100))
survey_start_date = Column(Date)
survey_end_date = Column(Date)
survey_is_active = Column(Boolean)
survey_questions = relationship(Question, secondary='survey_questions',cascade="all, delete",passive_deletes=True)
class SurveyQuestions(Base):
__tablename__ = 'survey_questions'
id = Column(Integer, primary_key=True, autoincrement=True)
survey_id = Column(Integer, ForeignKey('survey.id', ondelete='CASCADE'))
question_id = Column(Integer, ForeignKey('question.id', ondelete='CASCADE'))

Are there other models that use Survey as a foreign key?
Perhaps the foreign key violation happens because you didn't specify ondelete strategy in another model that points to Survey

So I had to drop all the tables using Base.metadata.drop_all(engine) before the foreign key relations would take effect. Since then we have adopted alembic to remedy this issue through the use of migrations.

Related

onupdate='cascade' is not working in the column in a table in Flask

as I am not an expert on flask-sqlalchemy, this might seem to be very basic question.
I have two table i)project ii)project_users in postgres db
i)Project Table
class Project(BaseModel):
__tablename__ = 'projects'
id = Column(fields.Integer, primary_key=True, autoincrement=True)
uid = Column(fields.UID, nullable=False, unique=True)
users = relationship("ProjectUser", lazy='select')
ii)Project_Users Table
class ProjectUser(BaseModel):
__tablename__ = 'project_users'
id = Column(fields.Integer, primary_key=True, autoincrement=True)
project = Column(fields.UID, ForeignKey(Project.uid), nullable=False)
NOW as we can see project field in PROJECT_USERS table is a foreign key and references from PROJECT table.
So, I want to modify uid in the project table but that gives foreign_key_violation error , so in order to solve that I have tried to cascade the foreign key in PROJECT_USERS Table using the following ways
i)In PROJECT_USERS table
project = Column(fields.UID, ForeignKey(Project.uid), nullable=False, onupdate=u'CASCADE)
ii)In PROJECT Table
users = relationship("ProjectUser", lazy='select', cascade="save-update)
I am still not able to solve key violation error. "sqlalchemy.exc.IntegrityError: (psycopg2.errors.ForeignKeyViolation)"
Can anyone please help and tell how can I correctly add cascade?
Thanks

Set foreign key to nullable=false?

Do you set your foreign keys as nullable=false if always expect a foreign key on that column in the database?
I'm using sqlalchemy and have set my models with required foreign keys. This sometimes causes me to run session.commit() more often, since I need the parent model to have an id and be fully created in order to build a child object in the ORM. What is considered best practice? My models are below:
class Location(Base):
__tablename__ = 'locations'
id = Column(Integer, primary_key=True)
city = Column(String(50), nullable=False, unique=True)
hotels = relationship('Hotel', back_populates='location')
class Hotel(Base):
__tablename__ = 'hotels'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False, unique=True)
phone_number = Column(String(20))
parking_fee = Column(String(10))
location_id = Column(Integer, ForeignKey('locations.id'), nullable=False)
location = relationship('Location', back_populates='hotels')
You don't need to do session.commit() to get an ID; session.flush() will do.
Even better, you don't need to get an ID at all if you set the relationship because SQLalchemy will figure out the order to do the INSERTs in. You can simply do:
loc = Location(city="NYC", hotels=[Hotel(name="Hilton")])
session.add(loc)
session.commit()
and it will work fine.
I would suggest that you'd better not set nullable=False. Make foreign key nullable is very reasonable in many situations. In your scenario, for example, if I want to insert a hotel whose location is currently underdetermined, you can not accomplish this with the foreign key not null. So the best practice when using foreign key is to set it nullable.
See necessary nullable foreign key Any example of a necessary nullable foreign key?

Foreign Key to Same Table in sqlalchemy

I have a MySQL table, defined in sqlalchemy with following structure:
class User(Base):
__tablename__ = 'user'
__table_args__ = {'mysql_charset': 'utf8', 'mysql_engine': 'InnoDB'}
id = Column(Integer, primary_key=True)
handle = Column(String(250), nullable=False)
owned = Column(Boolean(), default=False)
owner_id = Column(Integer, ForeignKey("user.id"), nullable=True, default=null )
current_price = Column(Integer, nullable=False, default=1)
balance = Column(Integer, nullable=False, default=0)
I want a relationship so that the owner_id can either be null, OR if it is set it must refer to a valid user.id, in the same table.
I don't quite understand the sqlalchemy relationship stuff well enough to be able to do it. The special stuff at the top of this page http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html seems to suggest that it's possible, but I can't figure it out.
I want to then be able to either add Users like:
u1 = User(handle="bob")
u2 = User(handle="jim", owner=u1)
Thanks for any help!
I should add that sqlalchemy has no problem doing the CREATE TABLE with the correct FOREIGN KEY constraint, and I can manually INSERT data into the table that obeys the rules as I want them in MySQL, it's only using the sqlalchemy model that fails.
EDIT: SOLVED
The 'default=null' on owner_id was causing the problem for some reason. Helpful docs were here: http://docs.sqlalchemy.org/en/rel_1_0/orm/self_referential.html and code example from that page here: http://docs.sqlalchemy.org/en/rel_1_0/orm/examples.html#examples-adjacencylist
For the google spider bots, errors that I got during this process were:
sqlalchemy.exc.IntegrityError: (_mysql_exceptions.IntegrityError) (1452, 'Cannot add or update a child row: a foreign key constraint fails (`twitfriends`.`tree`, CONSTRAINT `tree_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `tree` (`id`))') [SQL: u'INSERT INTO tree (parent_id, name) VALUES (%s, %s)'] [parameters: (<sqlalchemy.sql.elements.Null object at 0x7fe7e8c468d0>, 'rootnode')]
And
ArgumentError: Node.next and back-reference Node.prev are both of the same direction <symbol 'ONETOMANY>. Did you mean to set remote_side on the many-to-one side ?
Since there is only one foreign key for User, I would expect sqlalchemy to automatically figure out the join conditions. You can also add a backref so you can get the other side of the relationship.
class User(Base):
...
owner = relationship('User', remote_side=['id'], backref='owned_users')
Docs
Ex.
u1 = User(handle="bob")
u2 = User(handle="jim", owner=u1)
print u2.owned_users[0] == u1
# True

SQLAlchemy multiple foreign keys in one mapped class to the same primary key

Am trying to setup a postgresql table that has two foreign keys that point to the same primary key in another table.
When I run the script I get the error
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Company.stakeholder - 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.
That is the exact error in the SQLAlchemy Documentation yet when I replicate what they have offered as a solution the error doesn't go away. What could I be doing wrong?
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='company_id')
stakeholder = relationship("Company", foreign_keys='stakeholder_id')
I have seen similar questions here but some of the answers recommend one uses a primaryjoin yet in the documentation it states that you don't need the primaryjoin in this situation.
Tried removing quotes from the foreign_keys and making them a list. From official documentation on Relationship Configuration: Handling Multiple Join Paths
Changed in version 0.8: relationship() can resolve ambiguity between
foreign key targets on the basis of the foreign_keys argument alone;
the primaryjoin argument is no longer needed in this situation.
Self-contained code below works with sqlalchemy>=0.9:
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine(u'sqlite:///:memory:', echo=True)
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys=[company_id])
stakeholder = relationship("Company", foreign_keys=[stakeholder_id])
Base.metadata.create_all(engine)
# simple query test
q1 = session.query(Company).all()
q2 = session.query(Stakeholder).all()
The latest documentation:
http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#handling-multiple-join-paths
The form of foreign_keys= in the documentation produces a NameError, not sure how it is expected to work when the class hasn't been created yet. With some hacking I was able to succeed with this:
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='Stakeholder.company_id')
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder = relationship("Company",
foreign_keys='Stakeholder.stakeholder_id')
In other words:
… foreign_keys='CurrentClass.thing_id')

Update SQLAlchemy relationship

I have two relationships to the same table. When I add an element to one relationship, this does not reflect to the other relationship until I submit the session. Is there a way to force "update" the relationships?
Concrete example:
class Event(ManagerBase):
"""Defines an event."""
__tablename__ = 'eventing_events'
id = Column(Integer, primary_key=True)
device_id = Column(Integer, ForeignKey(EventingDevice.id), nullable=False)
device = relation(EventingDevice)
type_id = Column(Integer, ForeignKey(EventType.id), nullable=False)
type = relation(EventType)
datetime = Column(DateTime, nullable=False)
summary = Column(String(500))
fields = relation("EventFieldValue",
viewonly=True,
collection_class=attribute_mapped_collection("field.name"))
class EventFieldValue(ManagerBase):
"""The value of a single field of an event."""
__tablename__ = 'eventing_event_field_values'
event_id = Column(Integer, ForeignKey(Event.id), primary_key=True)
event = relation(Event, backref=backref("field_values",
collection_class=attribute_mapped_collection("field")))
field_id = Column(Integer, ForeignKey(Field.id), primary_key=True)
field = relation(Field)
value = Column(Text)
I have two realations from Event to EventFieldValue: fields and field_values (via backref of event). When I add a EventFieldValue to event.field_values, it does not reflect in event.fields until I commit the session.
Because you have two relations, sqlalchemy have to make requests for each one, and doesn't share their cache in the session.
You should take a look at Association proxies, that seems to be exactly what you need. They allow you to define only one relation and to put proxies on the top of them to access stuff in the relation more easily.
Flushing the session should solve this problem. It updates your session with all the new state but doesn't do a commit. You can also look into Refresh/Expire which will reload your objects.

Categories