]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Update `table_per_related` example for Declarative API
authorMike Fiedler <miketheman@gmail.com>
Fri, 20 Jun 2025 21:49:26 +0000 (17:49 -0400)
committerFederico Caselli <cfederico87@gmail.com>
Fri, 20 Jun 2025 22:13:26 +0000 (00:13 +0200)
<!-- Provide a general summary of your proposed changes in the Title field above -->

### Description
When reading more of the Generic Associations, I found that the examples state "Annotated example", and wondered what that meant, since in other parts of the docs that means they are using the new 2.0 style.

I tried to update this example to be more in line with the new style, including a little f-string update.

I completely understand this is unlikely to be merged as-is - but wanted to understand more about the right way to use modern styles to properly code well-hinted, more "exotic" implementations.

Outstanding questions:
- Should examples pass pass `mypy --strict` ? It doesn't right now.
- Are there better ways to apply `Mapped` within the `type(...)` definition, so we could skip importing `Integer`?

### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)

-->

This pull request is:

(Do non-running examples count as code fixes/features?)

- [x] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed

**Have a nice day!**

Closes: #10450
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/10450
Pull-request-sha: a6a54d69487429c8b3a7758cf22913e6a2156316

Change-Id: Ie7c7526daed5bf907d41f666459759b9a986b8c2

examples/generic_associations/discriminator_on_association.py
examples/generic_associations/generic_fk.py
examples/generic_associations/table_per_association.py
examples/generic_associations/table_per_related.py

index 93c1b29ef98b7fe6d0b0ff2e7c1c7f806896426f..850bcb4f0634afa29e33e5ffc7f8152718fdbe1b 100644 (file)
@@ -16,43 +16,42 @@ objects, but is also slightly more complex.
 
 """
 
-from sqlalchemy import Column
 from sqlalchemy import create_engine
 from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
 from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import as_declarative
-from sqlalchemy.ext.declarative import declared_attr
 from sqlalchemy.orm import backref
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import declared_attr
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
 
 
-@as_declarative()
-class Base:
+class Base(DeclarativeBase):
     """Base class which provides automated table name
     and surrogate primary key column.
-
     """
 
     @declared_attr
     def __tablename__(cls):
         return cls.__name__.lower()
 
-    id = Column(Integer, primary_key=True)
+    id: Mapped[int] = mapped_column(primary_key=True)
 
 
 class AddressAssociation(Base):
     """Associates a collection of Address objects
     with a particular parent.
-
     """
 
     __tablename__ = "address_association"
 
-    discriminator = Column(String)
+    discriminator: Mapped[str] = mapped_column()
     """Refers to the type of parent."""
+    addresses: Mapped[list["Address"]] = relationship(
+        back_populates="association"
+    )
 
     __mapper_args__ = {"polymorphic_on": discriminator}
 
@@ -62,14 +61,17 @@ class Address(Base):
 
     This represents all address records in a
     single table.
-
     """
 
-    association_id = Column(Integer, ForeignKey("address_association.id"))
-    street = Column(String)
-    city = Column(String)
-    zip = Column(String)
-    association = relationship("AddressAssociation", backref="addresses")
+    association_id: Mapped[int] = mapped_column(
+        ForeignKey("address_association.id")
+    )
+    street: Mapped[str]
+    city: Mapped[str]
+    zip: Mapped[str]
+    association: Mapped["AddressAssociation"] = relationship(
+        back_populates="addresses"
+    )
 
     parent = association_proxy("association", "parent")
 
@@ -85,12 +87,11 @@ class Address(Base):
 class HasAddresses:
     """HasAddresses mixin, creates a relationship to
     the address_association table for each parent.
-
     """
 
     @declared_attr
-    def address_association_id(cls):
-        return Column(Integer, ForeignKey("address_association.id"))
+    def address_association_id(cls) -> Mapped[int]:
+        return mapped_column(ForeignKey("address_association.id"))
 
     @declared_attr
     def address_association(cls):
@@ -98,7 +99,7 @@ class HasAddresses:
         discriminator = name.lower()
 
         assoc_cls = type(
-            "%sAddressAssociation" % name,
+            f"{name}AddressAssociation",
             (AddressAssociation,),
             dict(
                 __tablename__=None,
@@ -117,11 +118,11 @@ class HasAddresses:
 
 
 class Customer(HasAddresses, Base):
-    name = Column(String)
+    name: Mapped[str]
 
 
 class Supplier(HasAddresses, Base):
-    company_name = Column(String)
+    company_name: Mapped[str]
 
 
 engine = create_engine("sqlite://", echo=True)
index d45166d333f72820cf35cec0520ddd08a2d7c5fb..f82ad635160018c8301e8f3b778afd8c51edb913 100644 (file)
@@ -19,32 +19,29 @@ or "table_per_association" instead of this approach.
 """
 
 from sqlalchemy import and_
-from sqlalchemy import Column
 from sqlalchemy import create_engine
 from sqlalchemy import event
-from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import as_declarative
-from sqlalchemy.ext.declarative import declared_attr
 from sqlalchemy.orm import backref
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import declared_attr
 from sqlalchemy.orm import foreign
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import remote
 from sqlalchemy.orm import Session
 
 
-@as_declarative()
-class Base:
+class Base(DeclarativeBase):
     """Base class which provides automated table name
     and surrogate primary key column.
-
     """
 
     @declared_attr
     def __tablename__(cls):
         return cls.__name__.lower()
 
-    id = Column(Integer, primary_key=True)
+    id: Mapped[int] = mapped_column(primary_key=True)
 
 
 class Address(Base):
@@ -52,17 +49,16 @@ class Address(Base):
 
     This represents all address records in a
     single table.
-
     """
 
-    street = Column(String)
-    city = Column(String)
-    zip = Column(String)
+    street: Mapped[str]
+    city: Mapped[str]
+    zip: Mapped[str]
 
-    discriminator = Column(String)
+    discriminator: Mapped[str]
     """Refers to the type of parent."""
 
-    parent_id = Column(Integer)
+    parent_id: Mapped[int]
     """Refers to the primary key of the parent.
 
     This could refer to any table.
@@ -72,9 +68,8 @@ class Address(Base):
     def parent(self):
         """Provides in-Python access to the "parent" by choosing
         the appropriate relationship.
-
         """
-        return getattr(self, "parent_%s" % self.discriminator)
+        return getattr(self, f"parent_{self.discriminator}")
 
     def __repr__(self):
         return "%s(street=%r, city=%r, zip=%r)" % (
@@ -105,7 +100,9 @@ def setup_listener(mapper, class_):
         backref=backref(
             "parent_%s" % discriminator,
             primaryjoin=remote(class_.id) == foreign(Address.parent_id),
+            overlaps="addresses, parent_customer",
         ),
+        overlaps="addresses",
     )
 
     @event.listens_for(class_.addresses, "append")
@@ -114,11 +111,11 @@ def setup_listener(mapper, class_):
 
 
 class Customer(HasAddresses, Base):
-    name = Column(String)
+    name: Mapped[str]
 
 
 class Supplier(HasAddresses, Base):
-    company_name = Column(String)
+    company_name: Mapped[str]
 
 
 engine = create_engine("sqlite://", echo=True)
index 04786bd49be3b909c1a86f307aad2e3a854c1b31..1b75d670c1f27a776cedfd00050caf0295fb1fa1 100644 (file)
@@ -15,27 +15,25 @@ has no dependency on the system.
 from sqlalchemy import Column
 from sqlalchemy import create_engine
 from sqlalchemy import ForeignKey
-from sqlalchemy import Integer
-from sqlalchemy import String
 from sqlalchemy import Table
-from sqlalchemy.ext.declarative import as_declarative
-from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import declared_attr
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
 
 
-@as_declarative()
-class Base:
+class Base(DeclarativeBase):
     """Base class which provides automated table name
     and surrogate primary key column.
-
     """
 
     @declared_attr
     def __tablename__(cls):
         return cls.__name__.lower()
 
-    id = Column(Integer, primary_key=True)
+    id: Mapped[int] = mapped_column(primary_key=True)
 
 
 class Address(Base):
@@ -43,12 +41,11 @@ class Address(Base):
 
     This represents all address records in a
     single table.
-
     """
 
-    street = Column(String)
-    city = Column(String)
-    zip = Column(String)
+    street: Mapped[str]
+    city: Mapped[str]
+    zip: Mapped[str]
 
     def __repr__(self):
         return "%s(street=%r, city=%r, zip=%r)" % (
@@ -81,11 +78,11 @@ class HasAddresses:
 
 
 class Customer(HasAddresses, Base):
-    name = Column(String)
+    name: Mapped[str]
 
 
 class Supplier(HasAddresses, Base):
-    company_name = Column(String)
+    company_name: Mapped[str]
 
 
 engine = create_engine("sqlite://", echo=True)
index 23c75b0b9d6553f80b80fb2f1cf36c5a2f6cbc7b..bd4e7d61d1b72f6fcbf948ca24e7f1ddc0da1ece 100644 (file)
@@ -17,19 +17,18 @@ is completely automated.
 
 """
 
-from sqlalchemy import Column
 from sqlalchemy import create_engine
 from sqlalchemy import ForeignKey
 from sqlalchemy import Integer
-from sqlalchemy import String
-from sqlalchemy.ext.declarative import as_declarative
-from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.orm import DeclarativeBase
+from sqlalchemy.orm import declared_attr
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
 
 
-@as_declarative()
-class Base:
+class Base(DeclarativeBase):
     """Base class which provides automated table name
     and surrogate primary key column.
 
@@ -39,7 +38,7 @@ class Base:
     def __tablename__(cls):
         return cls.__name__.lower()
 
-    id = Column(Integer, primary_key=True)
+    id: Mapped[int] = mapped_column(primary_key=True)
 
 
 class Address:
@@ -52,9 +51,9 @@ class Address:
 
     """
 
-    street = Column(String)
-    city = Column(String)
-    zip = Column(String)
+    street: Mapped[str]
+    city: Mapped[str]
+    zip: Mapped[str]
 
     def __repr__(self):
         return "%s(street=%r, city=%r, zip=%r)" % (
@@ -74,25 +73,25 @@ class HasAddresses:
     @declared_attr
     def addresses(cls):
         cls.Address = type(
-            "%sAddress" % cls.__name__,
+            f"{cls.__name__}Address",
             (Address, Base),
             dict(
-                __tablename__="%s_address" % cls.__tablename__,
-                parent_id=Column(
-                    Integer, ForeignKey("%s.id" % cls.__tablename__)
+                __tablename__=f"{cls.__tablename__}_address",
+                parent_id=mapped_column(
+                    Integer, ForeignKey(f"{cls.__tablename__}.id")
                 ),
-                parent=relationship(cls),
+                parent=relationship(cls, overlaps="addresses"),
             ),
         )
         return relationship(cls.Address)
 
 
 class Customer(HasAddresses, Base):
-    name = Column(String)
+    name: Mapped[str]
 
 
 class Supplier(HasAddresses, Base):
-    company_name = Column(String)
+    company_name: Mapped[str]
 
 
 engine = create_engine("sqlite://", echo=True)