Simon, you are a GENIUS!

Thank you SO much! Flushing was the answer. I simply added this to my 
original:

class Color(db.Model):
    ...
        def __init__(self, medium, name, *, pure=True, recipe=[]):
        ...
        self.pure = False if len(recipe) > 1 else True
*        db.session.add(self)*
*        db.session.flush([self])*
        if self.pure:
        ...

and it now works beautifully, as it is intended to. Thank you again.


On Monday, November 18, 2019 at 3:02:23 AM UTC-7, Simon King wrote:
>
> On Mon, Nov 18, 2019 at 5:32 AM Steven Riggs <[email protected] 
> <javascript:>> wrote: 
> > 
> > Hello, 
> > 
> > I have been all over the web, Stack Overflow and Youtube, and cannot 
> find any answers.  I have a self-referential many-to-many class Color, 
> which should contain an attribute recipe that gives a list of other colors 
> used to make it, along with the quantities of eacj color used.  The 
> simplified version of my code is: 
> > 
> > from flask import Flask 
> > 
> > from flask_sqlalchemy import SQLAlchemy 
> > 
> > app = Flask(__name__) 
> > app.config.from_mapping({ 
> >     'SQLALCHEMY_DATABASE_URI': 'sqlite:///colors.sqlite', 
> >     'SQLALCHEMY_ECHO': True, 
> >     'SQLALCHEMY_TRACK_MODIFICATIONS': False, 
> >     }) 
> > db = SQLAlchemy(app) 
> > 
> > 
> > class Color(db.Model): 
> >     id = db.Column(db.Integer, primary_key=True) 
> >     medium = db.Column(db.String(2), nullable=False) 
> >     name = db.Column(db.String, nullable=False, unique=True) 
> >     pure = db.Column(db.Boolean, nullable=False, default=True) 
> > 
> >     recipe = db.relationship('Recipe', 
> >             primaryjoin='Color.id==Recipe.base_id', 
> >             uselist=True, 
> >             join_depth=1, 
> >             lazy='joined' 
> >             ) 
> > 
> >     def __init__(self, medium, name, *, pure=True, recipe=[]): 
> >         self.medium = medium.upper() 
> >         self.name = name 
> >         self.pure = False if len(recipe) > 1 else True 
> >         if self.pure: 
> >             recipe = [(self, self, 1)] 
> >         for entry in recipe: 
> >             self.recipe.append(Recipe(entry)) 
> > 
> >     def __repr__(self): 
> >         return f'{self.name}' 
> > 
> > 
> > class Recipe(db.Model): 
> >     base_id = db.Column(db.Integer, primary_key=True, 
> autoincrement=False) 
> >     ingredient_id = db.Column(db.Integer, primary_key=True, 
> autoincrement=False) 
> >     ingredient_name = db.Column(db.String, db.ForeignKey('color.name')) 
> >     quantity = db.Column(db.Integer, nullable=False, default=1) 
> > 
> >     __table_args__ = ( 
> >             db.ForeignKeyConstraint( 
> >                 ['base_id', 'ingredient_id'], 
> >                 ['color.id', 'color.id'], 
> >                 onupdate = 'CASCADE', 
> >                 ondelete = 'CASCADE' 
> >                 ), 
> >             ) 
> > 
> >     def __init__(self, ingredient_tuple): 
> >         super().__init__() 
> >         self.base_id = ingredient_tuple[0].id 
> >         self.ingredient_id = ingredient_tuple[1].id 
> >         self.ingredient_name = ingredient_tuple[1].name 
> >         self.quantity = ingredient_tuple[2] 
> > 
> >     def __repr__(self): 
> >         return f'{self.ingredient_name}(x{self.quantity})' 
> > 
> > 
> > if __name__ == '__main__': 
> >     db.create_all() 
> > 
> > 
> > No matter how many times I have tweaked it with various settings, I 
> always get the following error when I try to commit: 
> > 
> > >>> from colors import Color, db, Recipe 
> > >>> blurg = Color('oa', 'blurg') 
> > ingredient_tuple=(blurg, blurg, 1) 
> > self.base_id=None 
> > self.ingredient_id=None 
> > >>> db.session.add(blurg) 
> > >>> db.session.commit() 
> > 2019-11-18 05:02:05,053 INFO sqlalchemy.engine.base.Engine SELECT 
> CAST('test pla 
> > in returns' AS VARCHAR(60)) AS anon_1 
> > 2019-11-18 05:02:05,054 INFO sqlalchemy.engine.base.Engine () 
> > 2019-11-18 05:02:05,054 INFO sqlalchemy.engine.base.Engine SELECT 
> CAST('test uni 
> > code returns' AS VARCHAR(60)) AS anon_1 
> > 2019-11-18 05:02:05,054 INFO sqlalchemy.engine.base.Engine () 
> > 2019-11-18 05:02:05,055 INFO sqlalchemy.engine.base.Engine BEGIN 
> (implicit) 
> > 2019-11-18 05:02:05,056 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> color (me 
> > dium, name, pure) VALUES (?, ?, ?) 
> > 2019-11-18 05:02:05,056 INFO sqlalchemy.engine.base.Engine ('OA', 
> 'blurg', 1) 
> > 
> C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-packages\s
>  
>
> > qlalchemy\sql\crud.py:799: SAWarning: Column 'recipe.ingredient_id' is 
> marked as 
> >  a member of the primary key for table 'recipe', but has no Python-side 
> or serve 
> > r-side default generator indicated, nor does it indicate 
> 'autoincrement=True' or 
> >  'nullable=True', and no explicit value is passed.  Primary key columns 
> typicall 
> > y may not store NULL. Note that as of SQLAlchemy 1.1, 
> 'autoincrement=True' must 
> > be indicated explicitly for composite (e.g. multicolumn) primary keys if 
> AUTO_IN 
> > CREMENT/SERIAL/IDENTITY behavior is expected for one of the columns in 
> the prima 
> > ry key. CREATE TABLE statements are impacted by this change as well on 
> most back 
> > ends. 
> >   util.warn(msg) 
> > 2019-11-18 05:02:05,059 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> recipe (b 
> > ase_id, ingredient_name, quantity) VALUES (?, ?, ?) 
> > 2019-11-18 05:02:05,059 INFO sqlalchemy.engine.base.Engine (1, 'blurg', 
> 1) 
> > 2019-11-18 05:02:05,060 INFO sqlalchemy.engine.base.Engine ROLLBACK 
> > Traceback (most recent call last): 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 1245, in _execute_context 
> >     self.dialect.do_execute( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\default.py", line 581, in do_execute 
> >     cursor.execute(statement, parameters) 
> > sqlite3.IntegrityError: NOT NULL constraint failed: recipe.ingredient_id 
> > 
> > The above exception was the direct cause of the following exception: 
> > 
> > Traceback (most recent call last): 
> >   File "<stdin>", line 1, in <module> 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\scoping.py", line 162, in do 
> >     return getattr(self.registry(), name)(*args, **kwargs) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 1027, in commit 
> >     self.transaction.commit() 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 494, in commit 
> >     self._prepare_impl() 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 473, in _prepare_impl 
> >     self.session.flush() 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 2470, in flush 
> >     self._flush(objects) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 2608, in _flush 
> >     transaction.rollback(_capture_exception=True) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\util\langhelpers.py", line 68, in __exit__ 
> >     compat.reraise(exc_type, exc_value, exc_tb) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\util\compat.py", line 153, in reraise 
> >     raise value 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\session.py", line 2568, in _flush 
> >     flush_context.execute() 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\unitofwork.py", line 422, in execute 
> >     rec.execute(self) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\unitofwork.py", line 586, in execute 
> >     persistence.save_obj( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\persistence.py", line 239, in save_obj 
> >     _emit_insert_statements( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\orm\persistence.py", line 1136, in 
> _emit_insert_statements 
> >     result = cached_connections[connection].execute( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 982, in execute 
> >     return meth(self, multiparams, params) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\sql\elements.py", line 287, in _execute_on_connection 
> >     return connection._execute_clauseelement(self, multiparams, params) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 1095, in _execute_clauseelement 
> >     ret = self._execute_context( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 1249, in _execute_context 
> >     self._handle_dbapi_exception( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 1476, in _handle_dbapi_exception 
> >     util.raise_from_cause(sqlalchemy_exception, exc_info) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\util\compat.py", line 398, in raise_from_cause 
> >     reraise(type(exception), exception, tb=exc_tb, cause=cause) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\util\compat.py", line 152, in reraise 
> >     raise value.with_traceback(tb) 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\base.py", line 1245, in _execute_context 
> >     self.dialect.do_execute( 
> >   File 
> "C:\Users\riggss2\Dropbox\programming\Playground\colors\wkenv\lib\site-pa 
> > ckages\sqlalchemy\engine\default.py", line 581, in do_execute 
> >     cursor.execute(statement, parameters) 
> > sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL 
> constraint fail 
> > ed: recipe.ingredient_id 
> > [SQL: INSERT INTO recipe (base_id, ingredient_name, quantity) VALUES (?, 
> ?, ?)] 
> > [parameters: (1, 'blurg', 1)] 
> > (Background on this error at: http://sqlalche.me/e/gkpj) 
> > 
> > I do not want the Recipe.ingredient_id to autoincrement, and even if I 
> set it explicitly, it is always input as NULL.  I am sure it's a simple 
> setup error, but I am lost as to where.  Any feedback is greatly 
> appreciated.  Thank you. 
>
> Out of interest, does it make any difference if you flush the Color 
> instance before creating the Recipe instances? The potential problem I 
> see is that, until the Color is flushed, its id will be None, and in 
> the Recipe constructor you set "self.ingredient_id = 
> ingredient_tuple[1].id", so that will therefore be set to None. 
>
> If you are using the "relationship" construct to link objects, you 
> should probably avoid assigning directly to foreign key columns, and 
> instead let SQLAlchemy handle those for you. 
>
> Conceptually, Recipe is related to 2 colors, the "base" and the 
> "ingredient". You've got a relationship (Color.recipe) which handles 
> the Recipe.base_id foreign key, but you haven't got one for 
> Recipe.ingredient_id. Personally, I would add 2 relationships to the 
> Recipe class, "base" (which is the other end of Color.recipe, and 
> probably ought to use "back_populates"), and "ingredient". Then rather 
> than assigning to the foreign key columns directly, you would assign 
> to the relationship properties. 
>
> Hope that helps, 
>
> Simon 
>

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/09ea0cf1-a294-4889-a9ed-2f1cc989d0a7%40googlegroups.com.

Reply via email to