Hi there -
I normally wouldn't do things this way, however in seeking to provide
an alternative architecture, the specific example you've given does
work when used with the correct constructs, so I can provide both
architectures. So you would need to illustrate specific scenarios
that aren't working in terms of a fully runnable example, e.g. the one
below. The awkardness of doing it this way is that ORM declarative is
mapping each subclass individually, using single table inheritance
without a polymorphic_identity. So when you for example query for
Location_lvlB and then later query for Location_lvlA, you can get the
*same* row but in two different object instances. This creates a
conflict against the row which while it might be something you can
work with, the ORM is not aware that it's working this way:
(Pdb) l1 = session.query(Location_lvlA).first()
(Pdb) l2 = session.query(Location_lvlB).first()
(Pdb) l1
<__main__.Location_lvlA object at 0x7f274c47a940>
(Pdb) l2
<__main__.Location_lvlB object at 0x7f274c47abe0>
The second example attached illustrates a safer way to go which is
that you only map one class per hiearchy, the bottommost one, and then
express your various levels of class functionality in terms of mixins.
Hope this helps.
# example 1, the requested 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(
"auth_user_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")),
Column("firstname", String),
)
# base definition of the Source-class for Mapper
Base = declarative_base(metadata=metadata)
class Source_orm(Base):
__table__ = source_table
_loc = relationship("Location_orm", uselist=False)
loc_name = association_proxy("_loc", "firstname")
def __init__(self, name):
self.name = name
# base definition of the Location-class for Mapping
class Location_orm(Base):
__table__ = location_table
def __init__(self, name):
self.name = name
# -------------------
# Higher functions - lvlA : possibly packed into a different module
class Source_lvlA(Source_orm):
@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):
session = object_session(self)
loc = (
session.query(Location_orm)
.filter(Location_orm.name == loc_name)
.one()
)
self._loc = loc
session.commit()
class Location_lvlA(Location_orm):
@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_id =
self.id
session.merge(src)
session.commit()
# -------------------
# Even Higher functions - lvlB : possibly packed into a different module
class Source_lvlB(Source_lvlA):
def assemble_info(self):
return f"<Source> {
self.name} at <Location> {self.loc_name}"
class Location_lvlB(Location_lvlA):
def assemble_info(self):
return f"<Location> {
self.name}"
if __name__ == "__main__":
engine = create_engine("sqlite://", echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
# create low level objects
s = Source_lvlA("MySource")
session.add(s)
session.flush()
l = Location_lvlB("MyLocation")
session.add(l)
session.flush()
# operate on the db use a higher level function
s = Source_lvlA.get_by_name(session, "MySource")
s.move_to_loc_by_name(
Location_lvlB.get_by_name(session, "MyLocation").name
)
# use highest level functionality
s = Source_lvlB.get_by_name(session, "MySource").assemble_info()
# 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(
"auth_user_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")),
Column("firstname", String),
)
# base definition of the Source-class for Mapper
Base = declarative_base(metadata=metadata)
class Source_orm(Base):
__abstract__ = True
@declared_attr
def __table__(cls):
return source_table
@declared_attr
def _loc(cls):
return relationship("Location_mapped", uselist=False)
loc_name = association_proxy("_loc", "firstname")
def __init__(self, name):
self.name = name
# base definition of the Location-class for Mapping
class Location_orm(Base):
__abstract__ = True
@declared_attr
def __table__(cls):
return location_table
def __init__(self, name):
self.name = name
# -------------------
# Higher functions - lvlA : possibly packed into a different module
class Source_abstract:
pass
class Location_abstract:
pass
class Source_lvlA(Source_abstract):
@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_lvlA(Location_abstract):
@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_id =
self.id
session.merge(src)
session.commit()
# -------------------
# Even Higher functions - lvlB : possibly packed into a different module
class Source_lvlB(Source_lvlA):
def assemble_info(self):
return f"<Source> {
self.name} at <Location> {self.loc_name}"
class Location_lvlB(Location_lvlA):
def assemble_info(self):
return f"<Location> {
self.name}"
class Source_mapped(Source_lvlB, Source_orm):
pass
class Location_mapped(Location_lvlB, Location_orm):
pass
if __name__ == "__main__":
engine = create_engine("sqlite://", echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
# create low level objects
s = Source_mapped("MySource")
session.add(s)
session.flush()
l = Location_mapped("MyLocation")
session.add(l)
session.flush()
# operate on the db use a higher level function
s = Source_mapped.get_by_name(session, "MySource")
s.move_to_loc_by_name(
Location_mapped.get_by_name(session, "MyLocation").name
)
# use highest level functionality
s = Source_mapped.get_by_name(session, "MySource").assemble_info()
> --
> 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
sqlalchemy+...@googlegroups.com.
> To post to this group, send email to
sqlal...@googlegroups.com.
> 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/ed3a4914-2b37-4f2e-b213-3c615b85c367%40googlegroups.com.
> For more options, visit
https://groups.google.com/d/optout.