>
> Thank you, Mike, for having a look at my clumsy code (there were some
> mistakes in the code, I only saw later) and making this effort.
>
Firstly, I have done a bit more tinkering yesterday, and also got aware,
that the Mapper or the internal cranking for sqlalchemy in this
hierarchical scenario have trouble to resolve the query via a inherited
relationship to the correct object. I think this is in essence what you
want to get at in your first paragraph.
I think the problem stems from that the relationship object takes the to be
querried/mapped-to first argument as a string, and inspects the namespace
for this very class, not its derivatives. I was able to resolve this in a
kind-off working example by overriding the relationship in every derived
class. Not nice, but I understand that it is kind of necessary, becausing
finding and resolving derived classes in the namespace is not trivial.
Arriving at your second example: I am all willing to learn about new
techniques and I can see the idea that you tried to established there.
However, I also noticed that you in the main routine only operate with the
highest derived class 'Source_mapped' and 'Location_mapped'. All other
classes are not mapped and in principle are just structure Mixin's. This is
kind of against the goal I to achieve a strict hierarchy, so that I could
break up the code into different packages, which build on top of each
other, but are functional at each level: myproj_lvl0 < myproj_lvlA <
myproj_lvlB. Anyhow, I took your second example and wrangled it into the
structure that I think is desiderable to make this happen. Please also look
at the __main__-routine at the bottom, which should make it more clear what
at what kind of functionality I would like to arrive at.
Many Thanks
Marcel
# example 2 - the safer architecture
import datetime as dt
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import DateTime
from sqlalchemy import ForeignKey
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import Sequence
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
from sqlalchemy.orm.session import object_session
metadata = MetaData()
source_table = Table("source", metadata,
Column("id", Integer, Sequence("src_id_seq", metadata=metadata),
primary_key=True),
Column("name", String(length=32), unique=True, nullable=False, index=True),
Column("created", DateTime(timezone=False), default=dt.datetime.utcnow,
nullable=False)
)
location_table = Table("location_detail", metadata,
Column("id", Integer, Sequence("loc_id_seq", metadata=metadata),
primary_key=True),
Column("name", String(length=32), unique=True, nullable=False, index=True),
Column("created", DateTime(timezone=False), default=dt.datetime.utcnow,
nullable=False),
Column("source_id", ForeignKey("source.id"))
)
Base = declarative_base(metadata=metadata)
# Low Level functions - lvl0 : possibly packed into a base package 'myproj_lvl0'
# base definition of the Source-class for Mapper
class Source_orm_lvl0(Base):
__abstract__ = True
@declared_attr
def __table__(cls):
return source_table
@declared_attr
def _loc(cls):
return relationship("Location_mapped_lvl0", uselist=False) # <<< need
to know the name of the mapped classes
loc_name = association_proxy("_loc", "name")
def __init__(self, name):
self.name = name
# base definition of the Location-class for Mapping
class Location_orm_lvl0(Base):
__abstract__ = True
@declared_attr
def __table__(cls):
return location_table
def __init__(self, name):
self.name = name
class Source_mapped_lvl0(Source_orm_lvl0):
pass
class Location_mapped_lvl0(Location_orm_lvl0):
pass
# Higher functions - lvlA : isolated into a different module/package
'myproj_lvlA'
class Source_orm_lvlA(Source_orm_lvl0):
__abstract__ = True
@declared_attr
def _loc(cls):
return relationship("Location_mapped_lvlA", uselist=False) # <<< need
to override with next hierarchy level class?!
@classmethod
def get_by_name(cls, session, name):
return session.query(cls).filter(cls.name == name).one()
def move_to_loc_by_name(self, loc_name):
Location_mapped = inspect(self).mapper.attrs._loc.mapper.class_
session = object_session(self)
loc = (
session.query(Location_mapped)
.filter(Location_mapped.name == loc_name)
.one()
)
self._loc = loc
session.commit()
class Location_orm_lvlA(Location_orm_lvl0):
__abstract__ = True
@classmethod
def get_by_name(cls, session, name):
return session.query(cls).filter(cls.name == name).one()
def move_src_here(self, src):
session = object_session(self)
src._loc = self
session.merge(src)
session.commit()
class Source_mapped_lvlA(Source_orm_lvlA):
pass
class Location_mapped_lvlA(Source_orm_lvlA):
pass
# Even Higher functions - lvlB : isolated into a different module/package
'myproj_lvlB'
class Source_orm_lvlB(Source_orm_lvlA):
__abstract__ = True
@declared_attr
def _loc(cls):
return relationship("Location_mapped_lvlB", uselist=False)
def assemble_info(self):
return f"<Source> {self.name} at <Location> {self.loc_name}"
class Location_orm_lvlB(Location_orm_lvlA):
__abstract__ = True
def assemble_info(self):
return f"<Location> {self.name}"
class Source_mapped_lvlB(Source_orm_lvlB):
pass
class Location_mapped_lvlB(Location_orm_lvlB):
pass
#----------------------------------------------------------------------
if __name__ == "__main__":
engine = create_engine("sqlite://", echo=False)
Base.metadata.create_all(engine)
session = Session(engine)
# from myproj_lvl0 import Source_mapped_lvl0, Location_mapped_lvl0
# create low level objects
s = Source_mapped_lvl0("MySource")
session.add(s)
session.flush()
l = Location_mapped_lvl0("MyLocation")
session.add(l)
session.flush()
# operate on the db use a higher level function
# from myproj_lvlA import Source_mapped_lvlA, Location_mapped_lvlA
s = Source_mapped_lvlA.get_by_name(session, "MySource")
s.move_to_loc_by_name(
Location_mapped_lvlA.get_by_name(session, "MyLocation").name
)
assert isinstance(s._loc, Location_mapped_lvlA)
# use highest level functionality
# from myproj_lvlB import Source_mapped_lvlA, Location_mapped_lvlB
s = Source_mapped_lvlB.get_by_name(session, "MySource")
print( s.assemble_info() )
assert isinstance(s._loc, Location_mapped_lvlB)
--
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 post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/9f20896a-b740-4107-a6b2-f6994b175d89%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.