I have a SyncEntities class (shown below).
I have several other classes (such as CommodityTypes also shown below) related to the SyncEntities class.
All of my Base subclasses have this column uuidKey = Column(String, primary_key=True)
Assume se is an instance of SyncEntities.
se.entityKind is the name of a Base subclass.
How do I query for an object that is in the se.entityKind class filtering for se.uuidKey?
class SyncEntities(Base):
__tablename__ = 'SyncEntities'
uuidKey = Column(String, primary_key=True)
dateCreated = Column(DateTime, index=True)
dateModified = Column(DateTime, index=True)
dateSynced = Column(DateTime, index=True)
username = Column(String)
entityKind = Column(String)
deleted = Column(Boolean)
def __init__(self, entity, security):
self.uuidKey = newUUID()
self.dateCreated = security.now
self.dateModified = security.now
self.dateSynced = security.then
self.username = security.username
self.entityKind = entity.__tablename__
self.deleted = False
def modified(self, security):
self.dateModified = security.now
self.username = security.username
class CommodityTypes(Base):
__tablename__ = 'CommodityTypes'
uuidKey = Column(String, ForeignKey('SyncEntities.uuidKey'), primary_key=True)
myName = Column(String, unique = True)
sortKey = Column(Integer, unique = True)
mySyncEntity = relationship("SyncEntities")
def __init__(self, security, myName, sortKey):
self.syncEntity = SyncEntities(self, security)
self.uuidKey = self.syncEntity.uuidKey
self.myName = myName
self.sortKey = sortKey
The structure here is similar, though not quite the same, as a "polymorphic association", and you can read about this pattern over at this blog post: http://techspot.zzzeek.org/2007/05/29/polymorphic-associations-with-sqlalchemy/ . It's an old post but the example at http://techspot.zzzeek.org/files/2007/discriminator_on_association.py was added later as an updated example.
This case is a little different in that an object like CommodityTypes only refers to a single SyncEntities, not multiple as in the usual polymorphic association. The SyncEntities also can only refer to a single type of related object since you have entityKind on it locally.
I would note that a potential problem with this design is that you could have rows in other tables that have a uuidKey pointing to a particular SyncEntities instance, but are not of a type that matches "entityKind". If the relationship between CommodityTypes and SyncEntities is actually one-to-one, that changes everything - this pattern is really simple joined table inheritance and you'd use the patterns described at http://docs.sqlalchemy.org/en/rel_0_7/orm/inheritance.html.
You also don't have backrefs between the target and SyncEntities, which is often a way to automate these styles of lookup. But you can still approximate things using a lookup of entityKind types to classes:
def lookup_related(se):
types = {
'commodity':CommodityTypes,
'foobar':FooBarTypes
}
cls = types[se.entityKind]
session = object_session(se)
return session.query(cls).filter(cls.mySyncEntity==se).all()
here's a mixin that could do it also, using a backref:
class HasSyncEntity(object):
entity_kind = None
"subclasses need to populate this"
#declared_attr
def uuidKey(cls):
return Column(String, ForeignKey("SyncEntities.uuidKey"), primary_key=True)
#declared_attr
def mySyncEntity(cls):
return relationship("SyncEntities", backref="_%s_collection" % cls.entity_kind)
CommodityTypes becomes:
class CommodityTypes(HasSyncEntity, Base):
entity_kind = "commodity"
# ...
You then add a method like this to SyncEntities, which looks up the appropriate backref, and you're done:
def get_related(self):
return getattr(self, "_%s_collection" % self.entityKind)
Related
I already know that for general cases: this question is sufficient: SQLAlchemy set default value of one column to that of another column
But when my column depends on the Id column it doesn't work because id is auto generated.
For eg: Using the same example as the other question
class Substance(Base):
__tablename__ = "substances"
id = Column(Integer, primary_key=True)
code = Column(String, unique=True)
name = Column(String, unique=True)
subtance_hash = Column(String, unique=True, default=hashgen)
def hashgen(context):
uid = context.get_current_parameters()['id'] + 100
name = context.get_current_parameters()['name']
code = context.get_current_parameters()['code']
return some_hash_function(uid, name, code)
def some_hash_function(uid, name, code):
# this is a stupid example for completeness
return str(uid) + str(name) + str(code)
The problem here is that uid is None because it will be auto generated while committing the query I think?
So it throws an error:
sqlalchemy.exc.StatementError: (builtins.KeyError) 'id'
Is it even possible to do this? If so how?
You can flush your session to set the id before the commit. You can then calculate your hash. You can also use the event system is SQLAlchemy to listen for after_flush events and calculate the hash there. For example:
class Substance(Base):
__tablename__ = "substances"
id = Column(Integer, primary_key=True)
code = Column(String, unique=True)
name = Column(String, unique=True)
subtance_hash = Column(String, unique=True)
#event.listens_for(sessionOrSessionFactory, 'after_flush')
def hashgen(session, flush_context):
for obj in session:
if isinstance(obj, Substance):
obj.substance_hash = some_hash_function(obj)
def some_hash_function(obj):
# this is a stupid example for completeness
return str(obj.id + 100) + str(obj.name) + str(obj.code)
Another method is to make the hash hybrid_property https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html
I haven't played around with hybrid properties much so I will leave it for someone else to contribute an answer for that.
I need to set the entity_type_id as a column value when I persist a row to a generic table of various entity_types. I should be able to load the entity_type_id for every specific instance at instantiation time because it is accessible via a simple select statement. I'd like to have that id automatically retrieved/set at the class (or instance) level without executing a query and/or manually setting to every time I persist a row of an "entity_type".
I tried an entity_type_id #property on the mixin that returns the id of the entity_type using the object_session but for reasons I don't fully understand the orm still inserts null as the entity_type_id value when I commit/flush the session. (my guess is having the "property" itself isn't the same thing as setting the attribute value on the instance and/or causing an issue because the column name from the base class has the same name)
Here's a slimmed down version of the relevant models in my schema:
class EntityType(Base):
__tablename__ = 'entity_type'
id = Column(UUID(as_uuid=True), primary_key=True, server_default=FetchedValue())
table_name = Column(String, nullable=False)
ui_label = Column(Text, unique=True, nullable=False)
entry_key = Column(Text, unique=True, nullable=False)
Base class model:
class TrackedEntity(Base):
#declared_attr
def __tablename__(cls):
return convert(cls.__name__)
__table_args__ = (
UniqueConstraint('entity_type_id', 'label'),
)
id = Column(UUID(as_uuid=True), primary_key=True, server_default=FetchedValue())
entity_type_id = Column('entity_type_id', ForeignKey('entity_type.id'))
label = Column('label', String, nullable=False)
entity_type = relationship('EntityType')
polymorphic_discriminator = column_property(select([EntityType.table_name]).where(EntityType.id == entity_type_id).as_scalar())
#declared_attr
def entity_type_label(cls):
return association_proxy('entity_type', 'label')
#declared_attr
def __mapper_args__(cls):
if cls.__name__ == 'TrackedEntity':
return {
"polymorphic_on": cls.polymorphic_discriminator,
"polymorphic_identity": cls.__tablename__
}
else:
return {"polymorphic_identity": cls.__tablename__}
Children class mixin:
class TrackedEntityMixin(object):
# noinspection PyMethodParameters
#declared_attr
def id(cls) -> Column:
return Column(ForeignKey('tracked_entity.id'), primary_key=True)
#gets me the id but isn't very helpful like this, still needs to be manually set like child.entity_type_id = child._entity_type_id
#property
def _entity_type_id(self):
return object_session(self). \
scalar(
select([EntityType.id]).
where(EntityType.table_name == self.__tablename__)
)
A child class model:
class DesignedMolecule(TrackedEntityMixin, TrackedEntity):
extra = Column('extra', String)
parents = relationship('TrackedEntity', secondary='mix_dm_parent_entity')
I have following models:
class Details(db.Model):
details_id = db.Column(db.Integer, primary_key=True)
details_main = db.Column(db.String(50))
details_desc = db.Column(db.String(50))
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
#property
def details_desc(self):
result = object_session(self).\
scalar(
select([Details.details_desc]).
where(Details.details_main == self.details_main)
)
return result
Now, I would like to run query using filter which depends on defined property. I get empty results (of course proper data is in DB). It doesn't work because, probably, I have to map this property. The question is how to do this? (One limitation: FK are not allowed).
Data.query\
.filter(Data.details_desc == unicode('test'))\
.all()
You can implement this with a regular relationship and an association proxy:
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
details = relationship(
Details,
primaryjoin=remote(Details.details_main) == foreign(details_main))
details_desc = association_proxy('details', 'details_desc')
Since there are no foreign keys in the schema, you need to tell SQLAlchemy yourself what the join condition for the relationship should be. This is what the remote() and foreign() annotations do.
With that in place, you can use an association_proxy "across" the relationship to create a property on Data which will work the way you want.
I have an existing database an want to build an SQLAlchemy wrapper to use the DB in Python. Lookup tables like the following are commonly used in the DB:
class Industry(Base):
__tablename__ = 'industry'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=True)
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
What I would like to do is to create a new instance of IndustrySector using the name of the industry rather than the (technical) key of the industry, i.e.,
new_industry_sector = IndustrySector(industry_id = 'Manufacturing', name = 'Textile')
instead of
manu_industry_id = session.query(Industry.id).filter(Industry.name=='Manufacturing').first().id
new_industry_sector = IndustrySector(name = 'Textile', industry_id = new_industry_id)
Obviously, above example can't work because I am filtering on the ID rather than the name. But I don't know how to get the name of the foreign-keyed table into this. Of course I could simply add a #classmethod that handles the lookup, but if there exists any built-in functionality I'd much rather use that.
Any help / pointers are appreciated
You could create a constructor which will replace the string with the id value whenever an object is created.
class IndustrySector(Base):
__tablename__ = 'industry_sector'
id = Column(Integer, primary_key=True)
industry_id = Column(Integer, ForeignKey('industry.id'), nullable=False)
name = Column(String, nullable=True)
def __init__(self, industry_id, name):
self.name = name
self.industry_id = fetch_id(industry_id)
def fetch_id(industry_id):
# fetch and return the id
I have a model Zone, a model Entity and a model Transit. Transit is minimally defined as:
class Entity(db.Model):
__tablename__ = 'entities'
id = db.Column(db.Integer, primary_key=True)
contained_by = db.Column(db.Integer, db.ForeignKey('entities.id'))
contains = db.relationship('Entity', backref='container')
discriminator = db.Column('type', db.String(50))
__mapper_args__ = {'polymorphic_on': discriminator}
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
routes = db.relationship('Transit')
(stuff goes here)
class Transit(db.Model):
__tablename__ = "transits"
start = db.Column(db.Integer, db.ForeignKey('zones.id'))
end = db.Column(db.Integer, db.ForeignKey('zones.id'))
Zone also has a couple of bits about distance and how defensible it is, but that is irrelevant for this.
First off, due to the fact that Zone is subclassed from Entity using single-table inheritance can I reference zones.id?
Secondly, will the Zone.routes property merge Transit.start and Transit.end?
no, you need to use the table name, which in your case (Single-table inheritance) is entities
no, these will not be merged. You can create two relationships, and have a (hybrid) property which would combine both, but this will only be for reading purposes, as when you would like to modify this property (for example, add Transits), you would still need to specify both sides (start and end).
I am not sure I understand the question here
update-1: as requested in comment, Concrete-Table inheritance code below:
class Zone(Entity):
__mapper_args__ = {'polymorphic_identity': 'zones'}
__tablename__ = "zones"
id = Column(Integer, ForeignKey('entities.id'), primary_key=True)
#property
def transits(self):
return self.transits_from_here + self.transits_to_here
class Transit(Base):
__tablename__ = "transits"
id = Column(Integer, primary_key=True)
start = Column(Integer, ForeignKey('zones.id'))
end = Column(Integer, ForeignKey('zones.id'))
s_zone = relationship(Zone, primaryjoin=(start==Zone.id), backref="transits_from_here")
e_zone = relationship(Zone, primaryjoin=(end==Zone.id), backref="transits_to_here")