From: Mike Fiedler Date: Fri, 20 Jun 2025 21:49:26 +0000 (-0400) Subject: Update `table_per_related` example for Declarative API X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b9ee3daea9e22f3ec1babe7130b3885802e25af;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Update `table_per_related` example for Declarative API ### 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 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 --- diff --git a/examples/generic_associations/discriminator_on_association.py b/examples/generic_associations/discriminator_on_association.py index 93c1b29ef9..850bcb4f06 100644 --- a/examples/generic_associations/discriminator_on_association.py +++ b/examples/generic_associations/discriminator_on_association.py @@ -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) diff --git a/examples/generic_associations/generic_fk.py b/examples/generic_associations/generic_fk.py index d45166d333..f82ad63516 100644 --- a/examples/generic_associations/generic_fk.py +++ b/examples/generic_associations/generic_fk.py @@ -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) diff --git a/examples/generic_associations/table_per_association.py b/examples/generic_associations/table_per_association.py index 04786bd49b..1b75d670c1 100644 --- a/examples/generic_associations/table_per_association.py +++ b/examples/generic_associations/table_per_association.py @@ -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) diff --git a/examples/generic_associations/table_per_related.py b/examples/generic_associations/table_per_related.py index 23c75b0b9d..bd4e7d61d1 100644 --- a/examples/generic_associations/table_per_related.py +++ b/examples/generic_associations/table_per_related.py @@ -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)