import sqlalchemy as db
from sqlalchemy import create_engine, text
from sqlalchemy.orm import declarative_base, relationship, Session
= declarative_base() Base
Relations
How do define relationships without explicitly setting foreign keys.
class User(Base):
= "user"
__tablename__ id = db.Column(db.Integer, primary_key=True)
= db.Column(db.String)
name = relationship("Product", back_populates="user")
products
class Product(Base):
= "product"
__tablename__ id = db.Column(db.Integer, primary_key=True)
= db.Column(db.String)
name = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user_id = relationship("User", back_populates="products") user
= "sqlite:///test.db"
db_path = db.create_engine(db_path)
engine =True)
Base.metadata.create_all(engine, checkfirst
= Session(bind=engine)
session # enforce foreign key constraint in SQLite (off by default!)
"PRAGMA foreign_keys=on")); session.execute(text(
= User(name="Alice")
user = Product(name="A", user=user)
product1 = Product(name="B", user=user)
product2
session.add(user) session.commit()
Foreign key will be correctly assigned by sqlalchemy:
product1.user_id
1
Works also the other way round:
= User(name="Kyle")
user = Product(name="A")
product3 = Product(name="B")
product4
= [product3, product4]
user.products
session.add(user) session.commit()
Foreign key will be correctly assigned by sqlalchemy:
product3.user_id
2
This does not work
The associated user.id will not be set correctly. It will be set to None
which is the current value of user.id
prior to committing.
= User(name="Dave")
user
= Product(name="C", user_id=user.id) # user.id is None at this point!
product5 = Product(name="D", user_id=user.id)
product6
session.add_all([user, product5, product6]) session.commit()
--------------------------------------------------------------------------- IntegrityError Traceback (most recent call last) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:2115, in Connection._exec_insertmany_context(self, dialect, context) 2114 else: -> 2115 dialect.do_execute( 2116 cursor, 2117 sub_stmt, 2118 sub_params, 2119 context, 2120 ) 2122 except BaseException as e: File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/default.py:942, in DefaultDialect.do_execute(self, cursor, statement, parameters, context) 941 def do_execute(self, cursor, statement, parameters, context=None): --> 942 cursor.execute(statement, parameters) IntegrityError: NOT NULL constraint failed: product.user_id The above exception was the direct cause of the following exception: IntegrityError Traceback (most recent call last) Cell In[8], line 7 4 product6 = Product(name="D", user_id=user.id) 6 session.add_all([user, product5, product6]) ----> 7 session.commit() File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:2032, in Session.commit(self) 2029 if trans is None: 2030 trans = self._autobegin_t() -> 2032 trans.commit(_to_root=True) File <string>:2, in commit(self, _to_root) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/state_changes.py:139, in _StateChange.declare_states.<locals>._go(fn, self, *arg, **kw) 137 self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS 138 try: --> 139 ret_value = fn(self, *arg, **kw) 140 except: 141 raise File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:1313, in SessionTransaction.commit(self, _to_root) 1311 if self._state is not SessionTransactionState.PREPARED: 1312 with self._expect_state(SessionTransactionState.PREPARED): -> 1313 self._prepare_impl() 1315 if self._parent is None or self.nested: 1316 for conn, trans, should_commit, autoclose in set( 1317 self._connections.values() 1318 ): File <string>:2, in _prepare_impl(self) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/state_changes.py:139, in _StateChange.declare_states.<locals>._go(fn, self, *arg, **kw) 137 self._next_state = _StateChangeStates.CHANGE_IN_PROGRESS 138 try: --> 139 ret_value = fn(self, *arg, **kw) 140 except: 141 raise File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:1288, in SessionTransaction._prepare_impl(self) 1286 if self.session._is_clean(): 1287 break -> 1288 self.session.flush() 1289 else: 1290 raise exc.FlushError( 1291 "Over 100 subsequent flushes have occurred within " 1292 "session.commit() - is an after_flush() hook " 1293 "creating new objects?" 1294 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:4353, in Session.flush(self, objects) 4351 try: 4352 self._flushing = True -> 4353 self._flush(objects) 4354 finally: 4355 self._flushing = False File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:4488, in Session._flush(self, objects) 4485 transaction.commit() 4487 except: -> 4488 with util.safe_reraise(): 4489 transaction.rollback(_capture_exception=True) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/util/langhelpers.py:146, in safe_reraise.__exit__(self, type_, value, traceback) 144 assert exc_value is not None 145 self._exc_info = None # remove potential circular references --> 146 raise exc_value.with_traceback(exc_tb) 147 else: 148 self._exc_info = None # remove potential circular references File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/session.py:4449, in Session._flush(self, objects) 4447 self._warn_on_events = True 4448 try: -> 4449 flush_context.execute() 4450 finally: 4451 self._warn_on_events = False File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/unitofwork.py:466, in UOWTransaction.execute(self) 464 else: 465 for rec in topological.sort(self.dependencies, postsort_actions): --> 466 rec.execute(self) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/unitofwork.py:642, in SaveUpdateAll.execute(self, uow) 640 @util.preload_module("sqlalchemy.orm.persistence") 641 def execute(self, uow): --> 642 util.preloaded.orm_persistence.save_obj( 643 self.mapper, 644 uow.states_for_mapper_hierarchy(self.mapper, False, False), 645 uow, 646 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/persistence.py:93, in save_obj(base_mapper, states, uowtransaction, single) 81 update = _collect_update_commands( 82 uowtransaction, table, states_to_update 83 ) 85 _emit_update_statements( 86 base_mapper, 87 uowtransaction, (...) 90 update, 91 ) ---> 93 _emit_insert_statements( 94 base_mapper, 95 uowtransaction, 96 mapper, 97 table, 98 insert, 99 ) 101 _finalize_insert_update_commands( 102 base_mapper, 103 uowtransaction, (...) 119 ), 120 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/orm/persistence.py:1143, in _emit_insert_statements(base_mapper, uowtransaction, mapper, table, insert, bookkeeping, use_orm_insert_stmt, execution_options) 1140 if do_executemany: 1141 multiparams = [rec[2] for rec in records] -> 1143 result = connection.execute( 1144 statement, multiparams, execution_options=execution_options 1145 ) 1147 if use_orm_insert_stmt is not None: 1148 if return_result is None: File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:1416, in Connection.execute(self, statement, parameters, execution_options) 1414 raise exc.ObjectNotExecutableError(statement) from err 1415 else: -> 1416 return meth( 1417 self, 1418 distilled_parameters, 1419 execution_options or NO_OPTIONS, 1420 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/sql/elements.py:523, in ClauseElement._execute_on_connection(self, connection, distilled_params, execution_options) 521 if TYPE_CHECKING: 522 assert isinstance(self, Executable) --> 523 return connection._execute_clauseelement( 524 self, distilled_params, execution_options 525 ) 526 else: 527 raise exc.ObjectNotExecutableError(self) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:1638, in Connection._execute_clauseelement(self, elem, distilled_parameters, execution_options) 1626 compiled_cache: Optional[CompiledCacheType] = execution_options.get( 1627 "compiled_cache", self.engine._compiled_cache 1628 ) 1630 compiled_sql, extracted_params, cache_hit = elem._compile_w_cache( 1631 dialect=dialect, 1632 compiled_cache=compiled_cache, (...) 1636 linting=self.dialect.compiler_linting | compiler.WARN_LINTING, 1637 ) -> 1638 ret = self._execute_context( 1639 dialect, 1640 dialect.execution_ctx_cls._init_compiled, 1641 compiled_sql, 1642 distilled_parameters, 1643 execution_options, 1644 compiled_sql, 1645 distilled_parameters, 1646 elem, 1647 extracted_params, 1648 cache_hit=cache_hit, 1649 ) 1650 if has_events: 1651 self.dispatch.after_execute( 1652 self, 1653 elem, (...) 1657 ret, 1658 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:1841, in Connection._execute_context(self, dialect, constructor, statement, parameters, execution_options, *args, **kw) 1838 context.pre_exec() 1840 if context.execute_style is ExecuteStyle.INSERTMANYVALUES: -> 1841 return self._exec_insertmany_context(dialect, context) 1842 else: 1843 return self._exec_single_context( 1844 dialect, context, statement, parameters 1845 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:2123, in Connection._exec_insertmany_context(self, dialect, context) 2115 dialect.do_execute( 2116 cursor, 2117 sub_stmt, 2118 sub_params, 2119 context, 2120 ) 2122 except BaseException as e: -> 2123 self._handle_dbapi_exception( 2124 e, 2125 sql_util._long_statement(sub_stmt), 2126 sub_params, 2127 cursor, 2128 context, 2129 is_sub_exec=True, 2130 ) 2132 if engine_events: 2133 self.dispatch.after_cursor_execute( 2134 self, 2135 cursor, (...) 2139 context.executemany, 2140 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:2352, in Connection._handle_dbapi_exception(self, e, statement, parameters, cursor, context, is_sub_exec) 2350 elif should_wrap: 2351 assert sqlalchemy_exception is not None -> 2352 raise sqlalchemy_exception.with_traceback(exc_info[2]) from e 2353 else: 2354 assert exc_info[1] is not None File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/base.py:2115, in Connection._exec_insertmany_context(self, dialect, context) 2113 break 2114 else: -> 2115 dialect.do_execute( 2116 cursor, 2117 sub_stmt, 2118 sub_params, 2119 context, 2120 ) 2122 except BaseException as e: 2123 self._handle_dbapi_exception( 2124 e, 2125 sql_util._long_statement(sub_stmt), (...) 2129 is_sub_exec=True, 2130 ) File ~/miniconda3/envs/satpy/lib/python3.13/site-packages/sqlalchemy/engine/default.py:942, in DefaultDialect.do_execute(self, cursor, statement, parameters, context) 941 def do_execute(self, cursor, statement, parameters, context=None): --> 942 cursor.execute(statement, parameters) IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: product.user_id [SQL: INSERT INTO product (name, user_id) VALUES (?, ?) RETURNING id] [parameters: ('C', None)] (Background on this error at: https://sqlalche.me/e/20/gkpj)