Skip to content

Commit

Permalink
Replace all sqlalchemy backref with back_populates (#2123)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathangreen committed Oct 22, 2024
1 parent 1b52948 commit 869b407
Show file tree
Hide file tree
Showing 19 changed files with 195 additions and 91 deletions.
8 changes: 4 additions & 4 deletions src/palace/manager/feed/annotator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ def content(cls, work: Work | None) -> str:
and work.summary.representation.content
):
content = work.summary.representation.content
if isinstance(content, bytes):
content = content.decode("utf-8")
work.summary_text = content
summary = work.summary_text
content_str = (
content.decode("utf-8") if isinstance(content, bytes) else content
)
summary = work.summary_text = content_str
return summary


Expand Down
3 changes: 2 additions & 1 deletion src/palace/manager/sqlalchemy/model/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Admin(Base, HasSessionCache):

# An Admin may have many roles.
roles: Mapped[list[AdminRole]] = relationship(
"AdminRole", backref="admin", cascade="all, delete-orphan", uselist=True
"AdminRole", back_populates="admin", cascade="all, delete-orphan", uselist=True
)

# Token age is max 30 minutes, in seconds
Expand Down Expand Up @@ -290,6 +290,7 @@ class AdminRole(Base, HasSessionCache):

id = Column(Integer, primary_key=True)
admin_id = Column(Integer, ForeignKey("admins.id"), nullable=False, index=True)
admin: Mapped[Admin] = relationship("Admin", back_populates="roles")
library_id = Column(Integer, ForeignKey("libraries.id"), nullable=True, index=True)
library: Mapped[Library] = relationship("Library", back_populates="adminroles")
role = Column(Unicode, nullable=False, index=True)
Expand Down
14 changes: 13 additions & 1 deletion src/palace/manager/sqlalchemy/model/circulationevent.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# CirculationEvent

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Unicode
from sqlalchemy.orm import Mapped, relationship

from palace.manager.sqlalchemy.model.base import Base
from palace.manager.sqlalchemy.util import get_one_or_create
from palace.manager.util.datetime_helpers import utc_now

if TYPE_CHECKING:
from palace.manager.sqlalchemy.model.library import Library
from palace.manager.sqlalchemy.model.licensing import LicensePool


class CirculationEvent(Base):
"""Changes to a license pool's circulation status.
Expand All @@ -25,6 +31,9 @@ class CirculationEvent(Base):

# One LicensePool can have many circulation events.
license_pool_id = Column(Integer, ForeignKey("licensepools.id"), index=True)
license_pool: Mapped[LicensePool] = relationship(
"LicensePool", back_populates="circulation_events"
)

type = Column(String(32), index=True)
start = Column(DateTime(timezone=True), index=True)
Expand All @@ -36,6 +45,9 @@ class CirculationEvent(Base):
# The Library associated with the event, if it happened in the
# context of a particular Library and we know which one.
library_id = Column(Integer, ForeignKey("libraries.id"), index=True, nullable=True)
library: Mapped[Library] = relationship(
"Library", back_populates="circulation_events"
)

# The geographic location associated with the event. This string
# may mean different things for different libraries. It might be a
Expand Down
15 changes: 11 additions & 4 deletions src/palace/manager/sqlalchemy/model/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class Subject(Base):

# Each Subject may claim affinity with one Genre.
genre_id = Column(Integer, ForeignKey("genres.id"), index=True)
genre: Mapped[Genre] = relationship("Genre", back_populates="subjects")

# A locked Subject has been reviewed by a human and software will
# not mess with it without permission.
Expand Down Expand Up @@ -346,11 +347,15 @@ class Classification(Base):
__tablename__ = "classifications"
id = Column(Integer, primary_key=True)
identifier_id = Column(Integer, ForeignKey("identifiers.id"), index=True)
identifier: Mapped[Identifier | None]
identifier: Mapped[Identifier] = relationship(
"Identifier", back_populates="classifications"
)
subject_id = Column(Integer, ForeignKey("subjects.id"), index=True)
subject: Mapped[Subject] = relationship("Subject", back_populates="classifications")
data_source_id = Column(Integer, ForeignKey("datasources.id"), index=True)
data_source: Mapped[DataSource | None]
data_source: Mapped[DataSource] = relationship(
"DataSource", back_populates="classifications"
)

# How much weight the data source gives to this classification.
weight = Column(Integer)
Expand Down Expand Up @@ -481,7 +486,7 @@ class Genre(Base, HasSessionCache):
name = Column(Unicode, unique=True, index=True)

# One Genre may have affinity with many Subjects.
subjects: Mapped[list[Subject]] = relationship("Subject", backref="genre")
subjects: Mapped[list[Subject]] = relationship("Subject", back_populates="genre")

# One Genre may participate in many WorkGenre assignments.
works = association_proxy("work_genres", "work")
Expand All @@ -490,7 +495,9 @@ class Genre(Base, HasSessionCache):
"WorkGenre", back_populates="genre", cascade="all, delete-orphan"
)

lane_genres: Mapped[list[LaneGenre]] = relationship("LaneGenre", backref="genre")
lane_genres: Mapped[list[LaneGenre]] = relationship(
"LaneGenre", back_populates="genre"
)

def __repr__(self):
if classifier.genres.get(self.name):
Expand Down
6 changes: 3 additions & 3 deletions src/palace/manager/sqlalchemy/model/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ class Collection(Base, HasSessionCache, RedisKeyMixin):
)

catalog: Mapped[list[Identifier]] = relationship(
"Identifier", secondary="collections_identifiers", backref="collections"
"Identifier", secondary="collections_identifiers", back_populates="collections"
)

# A Collection can be associated with multiple CoverageRecords
# for Identifiers in its catalog.
coverage_records: Mapped[list[CoverageRecord]] = relationship(
"CoverageRecord", backref="collection", cascade="all"
"CoverageRecord", back_populates="collection", cascade="all"
)

# A collection may be associated with one or more custom lists.
Expand All @@ -145,7 +145,7 @@ class Collection(Base, HasSessionCache, RedisKeyMixin):
# the list and they won't be added back, so the list doesn't
# necessarily match the collection.
customlists: Mapped[list[CustomList]] = relationship(
"CustomList", secondary="collections_customlists", backref="collections"
"CustomList", secondary="collections_customlists", back_populates="collections"
)

export_marc_records = Column(Boolean, default=False, nullable=False)
Expand Down
3 changes: 3 additions & 0 deletions src/palace/manager/sqlalchemy/model/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ class CoverageRecord(Base, BaseCoverageRecord):
# coverage has taken place. This is currently only applicable
# for Metadata Wrangler coverage.
collection_id = Column(Integer, ForeignKey("collections.id"), nullable=True)
collection: Mapped[Collection] = relationship(
"Collection", back_populates="coverage_records"
)

__table_args__ = (
Index(
Expand Down
27 changes: 21 additions & 6 deletions src/palace/manager/sqlalchemy/model/customlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

if TYPE_CHECKING:
from palace.manager.sqlalchemy.model.collection import Collection
from palace.manager.sqlalchemy.model.edition import Edition
from palace.manager.sqlalchemy.model.lane import Lane
from palace.manager.sqlalchemy.model.library import Library


Expand All @@ -43,7 +45,6 @@ class CustomList(Base):
INIT = "init"
UPDATED = "updated"
REPOPULATE = "repopulate"
auto_update_status_enum = Enum(INIT, UPDATED, REPOPULATE, name="auto_update_status")

__tablename__ = "customlists"
id = Column(Integer, primary_key=True)
Expand All @@ -59,13 +60,14 @@ class CustomList(Base):
updated = Column(DateTime(timezone=True), index=True)
responsible_party = Column(Unicode)
library_id = Column(Integer, ForeignKey("libraries.id"), index=True, nullable=True)
library: Mapped[Library] = relationship("Library", back_populates="custom_lists")

# How many titles are in this list? This is calculated and
# cached when the list contents change.
size = Column(Integer, nullable=False, default=0)

entries: Mapped[list[CustomListEntry]] = relationship(
"CustomListEntry", backref="customlist", uselist=True
"CustomListEntry", back_populates="customlist", uselist=True
)

# List sharing mechanisms
Expand All @@ -80,11 +82,17 @@ class CustomList(Base):
auto_update_query = Column(Unicode, nullable=True) # holds json data
auto_update_facets = Column(Unicode, nullable=True) # holds json data
auto_update_last_update = Column(DateTime, nullable=True)
auto_update_status: Mapped[str] = Column(auto_update_status_enum, default=INIT) # type: ignore[assignment]
auto_update_status = Column(
Enum(INIT, UPDATED, REPOPULATE, name="auto_update_status"), default=INIT
)

lanes: Mapped[list[Lane]] = relationship(
"Lane", back_populates="customlists", secondary="lanes_customlists"
)

# Typing specific
collections: list[Collection]
library: Library
collections: list[Collection] = relationship(
"Collection", secondary="collections_customlists", back_populates="customlists"
)

__table_args__ = (
UniqueConstraint("data_source_id", "foreign_identifier"),
Expand Down Expand Up @@ -364,8 +372,15 @@ class CustomListEntry(Base):
__tablename__ = "customlistentries"
id = Column(Integer, primary_key=True)
list_id = Column(Integer, ForeignKey("customlists.id"), index=True)
customlist: Mapped[CustomList] = relationship(
"CustomList", back_populates="entries"
)
edition_id = Column(Integer, ForeignKey("editions.id"), index=True)
edition: Mapped[Edition] = relationship(
"Edition", back_populates="custom_list_entries"
)
work_id = Column(Integer, ForeignKey("works.id"), index=True)
work: Mapped[Work] = relationship("Work", back_populates="custom_list_entries")
featured = Column(Boolean, nullable=False, default=False)
annotation = Column(Unicode)

Expand Down
12 changes: 8 additions & 4 deletions src/palace/manager/sqlalchemy/model/datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ class DataSource(Base, HasSessionCache, DataSourceConstants):
)

# One DataSource can provide many Hyperlinks.
links: Mapped[list[Hyperlink]] = relationship("Hyperlink", backref="data_source")
links: Mapped[list[Hyperlink]] = relationship(
"Hyperlink", back_populates="data_source"
)

# One DataSource can provide many Resources.
resources: Mapped[list[Resource]] = relationship("Resource", backref="data_source")
resources: Mapped[list[Resource]] = relationship(
"Resource", back_populates="data_source"
)

# One DataSource can generate many Measurements.
measurements: Mapped[list[Measurement]] = relationship(
Expand All @@ -76,7 +80,7 @@ class DataSource(Base, HasSessionCache, DataSourceConstants):

# One DataSource can provide many Classifications.
classifications: Mapped[list[Classification]] = relationship(
"Classification", backref="data_source"
"Classification", back_populates="data_source"
)

# One DataSource can have many associated Credentials.
Expand All @@ -92,7 +96,7 @@ class DataSource(Base, HasSessionCache, DataSourceConstants):
# One DataSource can provide many LicensePoolDeliveryMechanisms.
delivery_mechanisms: Mapped[list[LicensePoolDeliveryMechanism]] = relationship(
"LicensePoolDeliveryMechanism",
backref="data_source",
back_populates="data_source",
foreign_keys="LicensePoolDeliveryMechanism.data_source_id",
)

Expand Down
6 changes: 2 additions & 4 deletions src/palace/manager/sqlalchemy/model/devicetokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from sqlalchemy import Column, Enum, ForeignKey, Index, Integer, Unicode
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Mapped, backref, relationship
from sqlalchemy.orm import Mapped, relationship

from palace.manager.core.exceptions import BasePalaceException
from palace.manager.sqlalchemy.model.base import Base
Expand Down Expand Up @@ -32,9 +32,7 @@ class DeviceToken(Base):
index=True,
nullable=False,
)
patron: Mapped[Patron] = relationship(
"Patron", backref=backref("device_tokens", passive_deletes=True)
)
patron: Mapped[Patron] = relationship("Patron", back_populates="device_tokens")

token_type_enum = Enum(
DeviceTokenTypes.FCM_ANDROID, DeviceTokenTypes.FCM_IOS, name="token_types"
Expand Down
15 changes: 9 additions & 6 deletions src/palace/manager/sqlalchemy/model/edition.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

if TYPE_CHECKING:
from palace.manager.sqlalchemy.model.customlist import CustomListEntry
from palace.manager.sqlalchemy.model.resource import Resource
from palace.manager.sqlalchemy.model.work import Work


Expand Down Expand Up @@ -85,17 +86,19 @@ class Edition(Base, EditionConstants):
# it. Through the Equivalency class, it is associated with a
# (probably huge) number of other identifiers.
primary_identifier_id = Column(Integer, ForeignKey("identifiers.id"), index=True)
primary_identifier: Identifier # for typing
primary_identifier: Mapped[Identifier] = relationship(
"Identifier", back_populates="primarily_identifies"
)

# An Edition may be the presentation edition for a single Work. If it's not
# a presentation edition for a work, work will be None.
work: Mapped[Work] = relationship(
"Work", uselist=False, backref="presentation_edition"
"Work", uselist=False, back_populates="presentation_edition"
)

# An Edition may show up in many CustomListEntries.
custom_list_entries: Mapped[list[CustomListEntry]] = relationship(
"CustomListEntry", backref="edition"
"CustomListEntry", back_populates="edition"
)

# An Edition may be the presentation edition for many LicensePools.
Expand Down Expand Up @@ -133,9 +136,7 @@ class Edition(Base, EditionConstants):
# A Project Gutenberg text was likely `published` long before being `issued`.
published = Column(Date)

MEDIUM_ENUM = Enum(*EditionConstants.KNOWN_MEDIA, name="medium")

medium = Column(MEDIUM_ENUM, index=True)
medium = Column(Enum(*EditionConstants.KNOWN_MEDIA, name="medium"), index=True)

# The playtime duration of an audiobook (seconds)
# https://github.com/readium/webpub-manifest/tree/master/contexts/default#duration-and-number-of-pages
Expand All @@ -146,6 +147,8 @@ class Edition(Base, EditionConstants):
ForeignKey("resources.id", use_alter=True, name="fk_editions_summary_id"),
index=True,
)
cover: Mapped[Resource] = relationship("Resource", back_populates="cover_editions")

# These two let us avoid actually loading up the cover Resource
# every time.
cover_full_url = Column(Unicode)
Expand Down
20 changes: 13 additions & 7 deletions src/palace/manager/sqlalchemy/model/identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from palace.manager.util.summary import SummaryEvaluator

if TYPE_CHECKING:
from palace.manager.sqlalchemy.model.collection import Collection
from palace.manager.sqlalchemy.model.edition import Edition
from palace.manager.sqlalchemy.model.patron import Annotation
from palace.manager.sqlalchemy.model.resource import Hyperlink
Expand Down Expand Up @@ -272,7 +273,7 @@ def __repr__(self):
# One Identifier may serve as the primary identifier for
# several Editions.
primarily_identifies: Mapped[list[Edition]] = relationship(
"Edition", backref="primary_identifier"
"Edition", back_populates="primary_identifier"
)

# One Identifier may serve as the identifier for many
Expand All @@ -286,29 +287,34 @@ def __repr__(self):

# One Identifier may have many Links.
links: Mapped[list[Hyperlink]] = relationship(
"Hyperlink", backref="identifier", uselist=True
"Hyperlink", back_populates="identifier", uselist=True
)

# One Identifier may be the subject of many Measurements.
measurements: Mapped[list[Measurement]] = relationship(
"Measurement", backref="identifier"
"Measurement", back_populates="identifier"
)

# One Identifier may participate in many Classifications.
classifications: Mapped[list[Classification]] = relationship(
"Classification", backref="identifier"
"Classification", back_populates="identifier"
)

# One identifier may participate in many Annotations.
annotations: Mapped[list[Annotation]] = relationship(
"Annotation", backref="identifier"
"Annotation", back_populates="identifier"
)

# One Identifier can have many LicensePoolDeliveryMechanisms.
delivery_mechanisms: Mapped[list[LicensePoolDeliveryMechanism]] = relationship(
"LicensePoolDeliveryMechanism",
backref="identifier",
foreign_keys=lambda: [LicensePoolDeliveryMechanism.identifier_id],
back_populates="identifier",
)

collections: Mapped[list[Collection]] = relationship(
"Collection",
secondary="collections_identifiers",
back_populates="catalog",
)

# Type + identifier is unique.
Expand Down
Loading

0 comments on commit 869b407

Please sign in to comment.