Skip to content

Instantly share code, notes, and snippets.

@funseiki
Created December 4, 2020 16:20
Show Gist options
  • Save funseiki/3ddb35f849ed3c9bfb38eae404e7b8b4 to your computer and use it in GitHub Desktop.
Save funseiki/3ddb35f849ed3c9bfb38eae404e7b8b4 to your computer and use it in GitHub Desktop.
NULL Identity Key Error
import traceback
from contextlib import contextmanager
from sqlalchemy import create_engine, inspect, Column, Integer, String, DateTime, ForeignKey, Table, MetaData
from sqlalchemy.orm import sessionmaker, relationship, aliased, contains_eager, scoped_session, backref, mapper
engine = create_engine('sqlite:///:memory:', echo=False)
metadata = MetaData(bind=engine)
objecttypes = Table('objecttypes', metadata,
Column('id', Integer, primary_key=True),
Column('name', String)
)
class ObjectType(object):
def __repr__(self):
return "ObjectType({}, id={})".format(self.name, self.id)
mapper(ObjectType, objecttypes)
metadata.create_all(engine) # We'll call this again later
sessionFactory = sessionmaker(bind=engine, expire_on_commit=False)
scopedSessionFactory = scoped_session(sessionFactory)
def startScope():
return scopedSessionFactory()
def endScope():
scopedSessionFactory().close()
scopedSessionFactory.remove()
return
def addObjectTypes():
"""
Add in all the object types to the db without using a session
"""
values = ["('location')", "('locationinput')", "('gate')"]
q = """INSERT INTO objecttypes (name) VALUES {}""".format(",".join(values))
engine.execute(q)
return
def buildObjectTypes():
addObjectTypes()
session = startScope()
types = session.query(ObjectType).all()
endScope()
return dict([(objType.name, objType) for objType in types])
# Holds all the types
typeDict = buildObjectTypes()
things = Table('things', metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('name', String),
Column('object_type_id', Integer, ForeignKey('objecttypes.id')),
Column('version', Integer, nullable=False),
Column('timeCreated', DateTime)
)
class Thing(object):
def __init__(self, **kwds):
for key in kwds:
setattr(self, key, kwds[key])
return
def __repr__(self):
return "{}, id={}, type={}, version={}".format(self.name, self.id, self.objectType, self.version)
mapper(Thing, things, version_id_col=things.c.version,
polymorphic_on=things.c.object_type_id,
with_polymorphic='*',
polymorphic_load='inline',
properties={
'objectType': relationship(ObjectType)
})
locations = Table('locations', metadata,
Column('id', Integer, ForeignKey('things.id'), primary_key=True)
)
class Location(Thing):
pass
mapper(Location, locations, polymorphic_identity=typeDict['location'].id)
locationinputs = Table('locationinputs', metadata,
Column('id', Integer, ForeignKey('things.id'), primary_key=True),
Column('previousGateId', Integer, ForeignKey('gates.id'))
)
class LocationInput(Thing):
pass
mapper(LocationInput, locationinputs, polymorphic_identity=typeDict['locationinput'].id)
association_table = Table('association', metadata,
Column('parent_id', Integer, ForeignKey('gates.id')),
Column('child_id', Integer, ForeignKey('gates.id'))
)
gates = Table('gates', metadata,
Column('id', Integer, ForeignKey('things.id'), primary_key=True),
Column('originId', Integer, ForeignKey('locations.id')),
Column('destinationId', Integer, ForeignKey('locations.id')),
Column('originInputId', Integer, ForeignKey('locationinputs.id')),
Column('destinationInputId', Integer, ForeignKey('locationinputs.id'))
)
class Gate(Thing):
pass
mapper(Gate, gates, polymorphic_identity=typeDict['gate'].id,
properties={
'origin': relationship(Location, foreign_keys=[gates.c.originId], \
backref=backref("originGates", cascade_backrefs=False), \
cascade_backrefs=False),
'destination': relationship(Location, foreign_keys=[gates.c.destinationId], \
backref=backref("destinationGates", cascade_backrefs=False), \
cascade_backrefs=False),
'originInput': relationship(LocationInput, foreign_keys=[gates.c.originInputId], \
backref=backref("originInputGates", cascade_backrefs=False), \
cascade_backrefs=False),
'destinationInput': relationship(LocationInput, foreign_keys=[gates.c.destinationInputId], \
backref=backref("destinationInputGates", cascade_backrefs=False), \
cascade_backrefs=False),
'children': relationship(Gate,
secondary=association_table,
primaryjoin=gates.c.id == association_table.c.parent_id,
secondaryjoin=gates.c.id == association_table.c.child_id,
backref=backref("parents", cascade_backrefs=False),
cascade_backrefs=False)
})
LocationInput.previousGate = relationship(Gate, foreign_keys=[LocationInput.previousGateId])
metadata.create_all(engine)
@contextmanager
def sessionScope():
try:
session = sessionFactory()
yield session
session.commit()
except Exception as e:
session.rollback()
raise
finally:
session.close()
pass
return
@contextmanager
def sessionScopeLong():
try:
session = scopedSessionFactory()
yield session
session.commit()
except Exception as e:
session.rollback()
session.close()
scopedSessionFactory.remove()
raise
finally:
pass
return
def sessionScopeCallback(func, retries=0, useLongLivedSession=False):
ret = None
sessionRetriever = sessionScopeLong if useLongLivedSession else sessionScope
for attempt in range(0, retries + 1):
try:
with sessionRetriever() as session:
ret = func(session, attempt)
break
except Exception as e:
print("Found error : {}\n{}".format(e, traceback.format_exc(20)))
return ret
def write(obj):
def _onSession(session, attempt):
print("Merging object={}".format(obj))
return session.merge(obj)
return sessionScopeCallback(_onSession, useLongLivedSession=True)
def reproduceError():
tokyo = Location(name="tokyo", objectType=typeDict['location'])
tokyo = write(tokyo)
# Error "has a NULL identity key" here
return
westGate = Gate(name="westGate", destination=tokyo, objectType=typeDict['gate'])
# This is where I should see the issue
westGate = write(westGate)
print(tokyo.originGates)
return
london = Location(name="london", objectType=typeDict['location'])
london = write(london)
luggage = LocationInput(name="luggage", objectType=typeDict['locationinput'])
luggage = write(luggage)
# This is the line where the error occurs
eastGate = Gate(name="eastGate", origin=tokyo, destination=london, destinationInput=luggage,
objectType=typeDict['gate'])
eastGate.parents.append(westGate)
eastGate = write(eastGate)
moreEastGate = Gate(name="moreEastGate", origin=london, objectType=typeDict['gate'])
moreEastGate = write(moreEastGate)
print(tokyo.destinationGates)
print(london.destinationGates)
return
reproduceError()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment