0

I'm using sqlalchemy 2.0.23, postgres 16.1 and python 3.11.6

I created a Many-to-One relationship between Publication and EducationalSystem models respectively. But, if I assign a Publication instance an unique instance of EducationalSystem that was assigned previously in the loop to another publication then the later just gets reassigned to the latest one and reference to it is removed, thus the foreign key in all previous Many side instances is null but for the latest one.

Here is the code regarding the models:

class Publication(Base):
    # __tablename__ = "publication"
    __table_args__ = (
        UniqueConstraint("section_id", "title", name="section_title_uc"),
        { "schema" : "publications", }
    )
    
    __mapper_args__ = {
        "polymorphic_identity": "publication",
        "polymorphic_on": "publication_type"
    }

    publication_type: Mapped[str] = mapped_column(String(), nullable=False)
    id = mapped_column(Integer, primary_key=True)
    title = mapped_column(String(), nullable=False)
    created_at: Mapped[datetime] = mapped_column(default=func.now())
    updated_at: Mapped[datetime] = mapped_column(onupdate=func.now(), nullable=True)
    section_id: Mapped[int] = mapped_column(ForeignKey(Section.__table__.c.name), nullable=True)
    section = relationship("Section", back_populates="publications")
#### EducationalSystem ForeignKey ########
    educational_system_fk: Mapped[str] = mapped_column(ForeignKey("publications.educational_system.name"), nullable=True)

##### ED SYSTEM relationship object ####
    ed_system: Mapped[EducationalSystem] = relationship(back_populates="publications")
######
    unique_title_section = UniqueConstraint("section_id", "title", "draft", name="utsc") #There can only be a published article in the same section
    schedule: Mapped[datetime] = mapped_column(default=None, nullable=True)
    content: Mapped[str] = mapped_column(nullable=True)
    draft: Mapped[bool] = mapped_column(default=True)
    summary: Mapped[str] = mapped_column(nullable=True)
    quotes: Mapped[list[Quote]] = relationship(back_populates="publication")
    important: Mapped[bool] = mapped_column(nullable=True, default=False)
    views: Mapped[str] = mapped_column(default=0)
    statement: Mapped[str] = mapped_column(nullable=True)
    authors: Mapped[list[Author]] = relationship(secondary=publication_authors_table, back_populates="publications")
    images: Mapped[Image] = relationship(secondary=publication_image_table)
    editor_fk: Mapped[str] = mapped_column(ForeignKey("users.editor.username"), nullable=True)
    editor: Mapped[Editor] = relationship(back_populates="publications")
    

    #synonyms
    educational_system = synonym("ed_system")

    def __repr__(self):
        return f"{self.__class__.__name__}({self.title!r})"


class EducationalSystem(Base):
    __table_args__ = (
        {"schema": "publications"},
    )
    name: Mapped[str] = mapped_column(primary_key=True)
    publications: Mapped[Publication] = relationship(back_populates="ed_system")


Initialization function:

@asynccontextmanager
async def initialize(app: FastAPI):
    with SessionLocal(autoflush=True) as session:
        Base.metadata.create_all(bind=engine)
        stmts = [
            "INSERT INTO users.role VALUES('admin') ON CONFLICT DO NOTHING;"
            "INSERT INTO users.role VALUES('editor') ON CONFLICT DO NOTHING;"
            "INSERT INTO users.role VALUES('normal') ON CONFLICT DO NOTHING;"
            "INSERT INTO publications.section VALUES('news') ON CONFLICT DO NOTHING;"
            "INSERT INTO publications.section VALUES('inquiries') ON CONFLICT DO NOTHING;"
            "INSERT INTO publications.section VALUES('resources') ON CONFLICT DO NOTHING;"
            "INSERT INTO publications.section VALUES('experiences') ON CONFLICT DO NOTHING;"
        ]
        
        for stmt in stmts:
            session.execute(text(stmt))

        users = None
        pubs = None
        authors = None
        ed_systems = None
        quotes = None
        statements = None
        
        with open("./users.json") as f: users = json.load(f)
        with open("./publication.json") as f: pubs = json.load(f)
        with open("./authors.json") as f: authors = json.load(f)
        with open("./educational_systems.json") as f: ed_systems = json.load(f)
        with open("./quotes.json") as f: quotes = json.load(f)
        with open("./statements.json") as f: statements = json.load(f)
        
        editor_objs = []

        for user in users:
            class_ = eval(user["role_fk"].capitalize())
            user_obj = class_(**user)
            if isinstance(user_obj, Editor): editor_objs.append(user_obj)
            session.add(user_obj)
        try: session.commit() #commit users
        except sqlalchemy.exc.IntegrityError as err: print(err)

        author_list = []
        for author in authors:
            author["social_media_handles"] = [SocialMediaHandle(handle=handle) for handle in author["social_media_handles"]] 
            author_obj = Author(**author)
            session.add(author_obj)
            author_list.append(author_obj)
        
        ed_system_objs = []
        
        ###### WHERE EducationalSystems created ######
        for ed_system in ed_systems:
            ed_system_obj = EducationalSystem(name=ed_system)
            ed_system_objs.append(ed_system_obj)
            session.add(ed_system_obj)
            session.commit()

        
        for pub in pubs:
            class_ = eval(
                coerce_genre(pub["publication_type"], to="camel_case").pop()
            )
            pub["authors"] = choices(author_list, k=randint(1,3))
            pub_obj = class_(**pub)
            
            pub_obj.quotes.extend([Quote(**quote) for quote in choices(quotes, k=randint(1,3))])
            pub_obj.statement = statements[randint(0, len(statements) -1)]
            pub_obj.summary = pub_obj.statement
            ## ADDING a same educationalsystem to every publication ###
            pub_obj.ed_system = ed_system_objs[0]
            pub_obj.editor = editor_objs[randint(0, len(editor_objs) - 1)]
            session.add(pub_obj)
            session.commit() # commit
            
        
        
        
        try: session.commit()
        except sqlalchemy.exc.IntegrityError as err: print("Initial registries already exist, SKIPPING!")
        except Exception as err: print(f"{err}")
    yield
    with SessionLocal.begin() as session:
        print("DROPING DATABASE!")
        Base.metadata.drop_all(bind=engine)


Here is a photo of how it looks in the database (postgres): Database view

  • I tried creating and assigning to multiple publications an unique educationalSystem object, and expected sqlalchemy to fill in the foreign key field of each one accordingly. But it seems is not that intuitive with ORM mappings.

I searched in the docs for the common relationship patterns section and see if I could spot some misdoing on my part, but in a nutshell is about the relationship pattern establishment, setting on_delete triggers and to-related-entity references on each side of the relationship.

Came across the Unit of Work system to try understand (newbee) how changes are captured and emitted to the database, and I guess it's reupdating the to-be-emitted sql each time I assign the educational_system to the latest pub, so after commiting changes it yields that result. And if allow myself to be a little paranoic, the cache could be affecting somehow implicitly.

3
  • Added comments in the parts of the code that are related to the problem. I'm not an english native speaker, so I may be redundant or ambiguous, or both. So, please, kindly.. Commented Dec 9, 2023 at 17:52
  • If one EducationalSystem can relate to many Publication objects then shouldn't the relationship in EducationalSystem refer to a list, as in publications: Mapped[list[Publication]] = relationship(back_populates="ed_system") ? Commented Dec 9, 2023 at 20:09
  • That did solve the problem! And I was pretty sure I had typed it as a list. Thanks! Commented Dec 9, 2023 at 22:12

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.