Time for more pushing the limits of sqlalchemy. It never ceases to amaze!
Background
I have table for devices, and a table to record physical links between them.
class Device(Base):
__tablename__ = "device"
device_id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(255), nullable=False)
class PhysicalLink(Base):
__tablename__ = "physical_link"
physical_links_id = sa.Column(sa.Integer, primary_key=True)
device_id_1 = sa.Column(sa.types.Integer, sa.ForeignKey(Device.device_id), nullable=False)
device_port_1 = sa.Column(sa.String(255), nullable=False)
device_id_2 = sa.Column(sa.types.Integer, sa.ForeignKey(Device.device_id), nullable=False)
device_port_2 = sa.Column(sa.String(255), nullable=False)
cable_number = sa.Column(sa.String(255), nullable=False)
When I dealing with the physical links for a know device, I don’t want to have to always have if statements to decide whether I should be looking at device_[id|port]_ 1 or 2, so I did:
physical_links_table = PhysicalLinks.__table__
physical_links_ua = union_all(
select((
physical_links_table.c.physical_links_id,
label('this_device_id', physical_links_table.c.device_id_1),
label('this_device_port', physical_links_table.c.device_port_1),
label('other_device_id', physical_links_table.c.device_id_2),
label('other_device_port', physical_links_table.c.device_port_2),
physical_links_table.c.cable_number,
),),
select((
physical_links_table.c.physical_links_id,
label('this_device_id', physical_links_table.c.device_id_2),
label('this_device_port', physical_links_table.c.device_port_2),
label('other_device_id', physical_links_table.c.device_id_1),
label('other_device_port', physical_links_table.c.device_port_1),
physical_links_table.c.cable_number,
),),
).alias('physical_links_ua')
class PhysicalLinksDir(object):
pass
physical_links_dir_mapper = orm.mapper(PhysicalLinksDir, physical_links_ua)
physical_links_dir_mapper.add_property(
'this_device', orm.relation(Device, primaryjoin=(PhysicalLinksDir.this_device_id == Device.device_id)))
physical_links_dir_mapper.add_property(
'other_device', orm.relation(Device, primaryjoin=(PhysicalLinksDir.other_device_id == Device.device_id)))
This allows me to do:
physical_links = (db_session
.query(PhysicalLinksDir)
.filter(PhysicalLinksDir.this_device_id = my_device.device_id)
.options(joinedload('other_device')))
for pl in physical_links:
print pl.other_device
(Did I remember to tell you that I think that sqlalchmey rocks!)
Question
What do I need to do to make it possible to modify PhysicalLinksDir instance attributes, and be able to commit them back to the db?
In general, you will have to be very careful with updating it the way you want,
because those view objects
PhysicalLinksDirwill not always be in-sync with theunderlying
DeviceandPhysicalLinkyou might have in session/database.I obviously do not know your requirements, but I prefer not to have such inconsistencies when working with my model.
Also, there is a problem with the kind of mapping you have. You would expect to have 2 rows of
PhysicalLinksDirfor each row ofPhysicalLink(one for each side), but if you try it, you will see this is not the case. The reason for this is that the first column (physical_links_id) is considered to be aprimary_keyso thequery object will discard the second one with the same value.
In order to fix it, you need to configure the
primary_keymanually. Assuming there can be only oneconnection between two different Devices, the solution below will do the trick. You might need to extend it to include the
portas well:DELETE: Now, to support
delete, all you need to do is to add a relationship between yourPLDand the actualPhysicalLinkand thesession.delete(my_PLD); session.commit()will also delete thePhysicalLinkit represents:But in fact, the deletion might work out of the box as the model is soft-linked to the
physical_linktable.INSERT: Well, this is easily done with the
PhysicalLinkobject directly, so I would just keep it this way.UPDATE: You could potentially probably achieve this with Session Events, but the most simple way would be just to wrap all the attributes in a
@propertywhich would delegate the change to the proper object.IMPORTANT: I still think that this way of working is not really nice, because the links are not updated automatically and your in-memory
UnitOfWorkmight be inconsistent.If also would be useful to understand why you think this way of working with your objects would be better? What are the use cases of this app?