On Wed, May 15, 2019 at 4:12 AM Marcel Zoll
<[email protected]> wrote:
>>
>> 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.
it's the "functional at each level" part that I think goes beyond what
is practical. When you run an operation on a superclass, and it
expects to do ORM operations, it has to be referring to the ultimate
mapped class. The ORM is designed with the idea that the concrete
instantiation classes are the ones that are ultimately mapped and
dealt with in terms of persistence. It should be apparent that it
would be vastly more complicated to make it also "know" about all
kinds of arbitrary subclasses. How would you propose the identity
map issue I illustrated before function? That is, if i query for
Class_lvlA and later for Class_lvlB, what identity key should they
have? If you say they should have the same identity key, what if
both Class_lvlA and Class_lvlB perform an operation against the same
Session - there is only one Class_<xyz> object in the identity map,
suppose it is Class_lvlA, now Class_lvlB consults for its own class
and gets the wrong answer. If you propose they should have
individual identity keys, then how do these two classes consult for
the same row in the identity map? I appreciate that you are
going for some particular level of purity in your design, however I
believe there is some conflation here of separation of concerns vs.
concrete instantiation patterns.
>
> 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.
--
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/CA%2BRjkXGrRJtAE6ZpBVsm2%2B69vwqrN1RixwLp7AoY%3DNQwToJNHA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.