On Saturday, 25 November 2017 17:04:08 UTC, Mike Bayer wrote:
>
> This is the "safer" way to go and after
> working it up, it does look a little cleaner, sort of, here's a full
> demo (since I have to make these things run anyway), note on the query
> side, nothing changes:
>
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base
>
> Base = declarative_base()
>
> metadata = Base.metadata
>
>
> class Content(Base):
> __tablename__ = "content"
> txt = Column(Unicode(100), primary_key=True)
> widgets_id = Column(Integer, nullable=False)
> themes_id = Column(Integer, nullable=False)
> sections_id = Column(Integer, nullable=False)
>
> __table_args__ = (
> ForeignKeyConstraint(
> ['widgets_id', 'themes_id'],
> ['wt_assoc.widgets_id', 'wt_assoc.themes_id']),
> ForeignKeyConstraint(
> ['sections_id', 'themes_id'],
> ['ts_assoc.sections_id', 'ts_assoc.themes_id']),
> )
>
> wt = relationship("Wt_Assoc")
> ts = relationship("Ts_Assoc")
>
> section = relationship(
> "Sections", backref="content",
> primaryjoin="Sections.id == foreign(Content.sections_id)",
> viewonly=True)
>
>
> class Ts_Assoc(Base):
> __tablename__ = 'ts_assoc'
> themes_id = Column('themes_id', Integer, ForeignKey('themes.id'),
> primary_key=True)
> sections_id = Column('sections_id', Integer,
> ForeignKey('sections.id'), primary_key=True)
>
> theme = relationship("Themes")
> section = relationship("Sections")
>
>
> class Wt_Assoc(Base):
> __tablename__ = 'wt_assoc'
>
> widgets_id = Column('widgets_id', Integer,
> ForeignKey('widgets.id'), primary_key=True)
> themes_id = Column('themes_id', Integer, ForeignKey('themes.id'),
> primary_key=True)
>
> widget = relationship("Widgets")
> theme = relationship("Themes")
>
>
> class Sections(Base):
> __tablename__ = "sections"
> id = Column(Integer, primary_key=True)
> section = Column(Unicode(100), index=False, unique=False)
> themes = relationship("Themes", secondary="ts_assoc",
> back_populates="sections", viewonly=True)
>
>
> class Themes(Base):
> __tablename__ = "themes"
> id = Column(Integer, primary_key=True)
> theme = Column(Unicode(100), index=True, unique=True)
> widgets = relationship("Widgets", secondary="wt_assoc",
> back_populates="themes", viewonly=True)
> sections = relationship("Sections", secondary="ts_assoc",
> back_populates="themes", viewonly=True)
>
>
> class Widgets(Base):
> __tablename__ = "widgets"
> id = Column(Integer, primary_key=True)
> name = Column(Unicode(200), index=True, unique=True)
> themes = relationship("Themes", secondary="wt_assoc",
> back_populates="widgets", viewonly=True)
>
> e = create_engine("mysql://scott:tiger@localhost/test", echo=True)
> Base.metadata.drop_all(e)
> Base.metadata.create_all(e)
>
>
> sess = Session(e)
>
> w = Widgets(name='Widget A')
> s = Sections()
> t = Themes()
>
> wt = Wt_Assoc(widget=w, theme=t)
> ts = Ts_Assoc(section=s, theme=t)
>
> # this is the part that's "cleaner", e.g. we explicitly see Content mapped
> # to the associations which are themselves entities
> c = Content(txt="txt", ts=ts, wt=wt)
>
> # unit of work eats this right up, no problem
> sess.add_all([wt, ts, c])
>
> sess.commit()
>
>
> # query time - use those viewonly=True relationships
> w = sess.query(Widgets) \
> .join(Themes, Widgets.themes) \
> .join(Sections, Themes.sections) \
> .filter(Widgets.name == u'Widget A') \
> .one()
>
> print(w.themes[0].sections[0].content[0].txt)
>
>
Mike, this is great, and it looks like it should work perfectly, but
there's something funny going on with the Sections <--> Themes mapping (I
think). If we modify your last example to insert a second Themes instance,
attach the Sections instance to it, then attach a second Content instance
to that combination:
# one widget, one section
w = Widgets(name="Widget A")
s = Sections(section="first section")
# two themes
t = Themes(theme="first theme")
t2 = Themes(theme="second theme")
# add both themes to Widget A
wt = Wt_Assoc(widget=w, theme=t)
wt2 = Wt_Assoc(widget=w, theme=t2)
# add first section to both themes
ts = Ts_Assoc(section=s, theme=t)
t2s = Ts_Assoc(section=s, theme=t2)
# add content to section one for both themes
c = Content(txt="first theme, first section", ts=ts, wt=wt)
c3 = Content(txt="second theme, first section", ts=t2s, wt=wt2)
sess.add_all([wt, ts, c, t2, wt2, t2s, c3])
sess.commit()
This should work correctly (and inspecting the Content table in a sqlite
DB, it's correct), but if we query as before:
q = sess.query(Widgets) \
.join(Themes, Widgets.themes) \
.join(Sections, Themes.sections) \
.filter(Widgets.name == u'Widget A') \
.one()
# should give us content for first theme, first section
print(q.themes[0].sections[0].content[0].txt)
# should give us content for second theme, first section
print(q.themes[1].sections[0].content[0].txt)
We would expect to see:
"first theme, first section"
"second theme, first section"
But I get "first theme, first section" x 2, which is confusing -- as I
said, the rows are correct in the Content table.
Other stuff I left out before: macOS 10.13.1, SQLA 1.1.15, Python 3.5
Also, I get an SAWarning when I commit:
relationship 'Content.ts' will copy column ts_assoc.themes_id to column
content.themes_id, which conflicts with relationship(s): 'Content.wt'
(copies wt_assoc.themes_id to content.themes_id). Consider applying
viewonly=True to read-only relationships, or provide a primaryjoin
condition marking writable columns with the foreign() annotation.
--
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.
For more options, visit https://groups.google.com/d/optout.