]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
provide better detail on declared mixins w/ naming conventions
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 9 Oct 2023 13:03:42 +0000 (09:03 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 9 Oct 2023 13:57:26 +0000 (09:57 -0400)
Fixes: #10441
Change-Id: I392bc725a850616528d32a8b17805e219ff10aa3

doc/build/core/constraints.rst
doc/build/orm/declarative_mixins.rst

index a147ea359f3f8bb2fc4d896137be29d52a40eb63..c63ad858e2c4d17d3d0d257d2e10ff828d9900b8 100644 (file)
@@ -775,6 +775,14 @@ The above schema will produce:
         CONSTRAINT ck_foo_flag CHECK (flag IN (0, 1))
     )
 
+Using Naming Conventions with ORM Declarative Mixins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using the naming convention feature with :ref:`ORM Declarative Mixins
+<orm_mixins_toplevel>`, individual constraint objects must exist for each
+actual table-mapped subclass.  See the section
+:ref:`orm_mixins_named_constraints` for background and examples.
+
 Constraints API
 ---------------
 
index b8e17afdac1e4c4a3a5cad8704f000fa204cf15b..0ee8a952bb8af176ce6871bd02e26c6520a478b7 100644 (file)
@@ -738,7 +738,7 @@ from multiple collections::
     class MyModel(MySQLSettings, MyOtherMixin, Base):
         __tablename__ = "my_model"
 
-        @declared_attr
+        @declared_attr.directive
         def __table_args__(cls):
             args = dict()
             args.update(MySQLSettings.__table_args__)
@@ -747,25 +747,128 @@ from multiple collections::
 
         id = mapped_column(Integer, primary_key=True)
 
-Creating Indexes with Mixins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _orm_mixins_named_constraints:
+
+Creating Indexes and Constraints with Naming Conventions on Mixins
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-To define a named, potentially multicolumn :class:`.Index` that applies to all
-tables derived from a mixin, use the "inline" form of :class:`.Index` and
-establish it as part of ``__table_args__``::
+Using named constraints such as :class:`.Index`, :class:`.UniqueConstraint`,
+:class:`.CheckConstraint`, where each object is to be unique to a specific
+table descending from a mixin, requires that an individual instance of each
+object is created per actual mapped class.
+
+As a simple example, to define a named, potentially multicolumn :class:`.Index`
+that applies to all tables derived from a mixin, use the "inline" form of
+:class:`.Index` and establish it as part of ``__table_args__``, using
+:class:`.declared_attr` to establish ``__table_args__()`` as a class method
+that will be invoked for each subclass::
 
     class MyMixin:
         a = mapped_column(Integer)
         b = mapped_column(Integer)
 
-        @declared_attr
+        @declared_attr.directive
         def __table_args__(cls):
             return (Index(f"test_idx_{cls.__tablename__}", "a", "b"),)
 
 
-    class MyModel(MyMixin, Base):
-        __tablename__ = "atable"
-        c = mapped_column(Integer, primary_key=True)
+    class MyModelA(MyMixin, Base):
+        __tablename__ = "table_a"
+        id = mapped_column(Integer, primary_key=True)
+
+
+    class MyModelB(MyMixin, Base):
+        __tablename__ = "table_b"
+        id = mapped_column(Integer, primary_key=True)
+
+The above example would generate two tables ``"table_a"`` and ``"table_b"``, with
+indexes ``"test_idx_table_a"`` and ``"test_idx_table_b"``
+
+Typically, in modern SQLAlchemy we would use a naming convention,
+as documented at :ref:`constraint_naming_conventions`.   While naming conventions
+take place automatically using the :paramref:`_schema.MetaData.naming_convention`
+as new :class:`.Constraint` objects are created, as this convention is applied
+at object construction time based on the parent :class:`.Table` for a particular
+:class:`.Constraint`, a distinct :class:`.Constraint` object needs to be created
+for each inheriting subclass with its own :class:`.Table`, again using
+:class:`.declared_attr` with ``__table_args__()``, below illustrated using
+an abstract mapped base::
+
+    from uuid import UUID
+
+    from sqlalchemy import CheckConstraint
+    from sqlalchemy import create_engine
+    from sqlalchemy import MetaData
+    from sqlalchemy import UniqueConstraint
+    from sqlalchemy.orm import DeclarativeBase
+    from sqlalchemy.orm import declared_attr
+    from sqlalchemy.orm import Mapped
+    from sqlalchemy.orm import mapped_column
+
+    constraint_naming_conventions = {
+        "ix": "ix_%(column_0_label)s",
+        "uq": "uq_%(table_name)s_%(column_0_name)s",
+        "ck": "ck_%(table_name)s_%(constraint_name)s",
+        "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+        "pk": "pk_%(table_name)s",
+    }
+
+
+    class Base(DeclarativeBase):
+        metadata = MetaData(naming_convention=constraint_naming_conventions)
+
+
+    class MyAbstractBase(Base):
+        __abstract__ = True
+
+        @declared_attr.directive
+        def __table_args__(cls):
+            return (
+                UniqueConstraint("uuid"),
+                CheckConstraint("x > 0 OR y < 100", name="xy_chk"),
+            )
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+        uuid: Mapped[UUID]
+        x: Mapped[int]
+        y: Mapped[int]
+
+
+    class ModelAlpha(MyAbstractBase):
+        __tablename__ = "alpha"
+
+
+    class ModelBeta(MyAbstractBase):
+        __tablename__ = "beta"
+
+The above mapping will generate DDL that includes table-specific names
+for all constraints, including primary key, CHECK constraint, unique
+constraint:
+
+.. sourcecode:: sql
+
+    CREATE TABLE alpha (
+        id INTEGER NOT NULL,
+        uuid CHAR(32) NOT NULL,
+        x INTEGER NOT NULL,
+        y INTEGER NOT NULL,
+        CONSTRAINT pk_alpha PRIMARY KEY (id),
+        CONSTRAINT uq_alpha_uuid UNIQUE (uuid),
+        CONSTRAINT ck_alpha_xy_chk CHECK (x > 0 OR y < 100)
+    )
+
+
+    CREATE TABLE beta (
+        id INTEGER NOT NULL,
+        uuid CHAR(32) NOT NULL,
+        x INTEGER NOT NULL,
+        y INTEGER NOT NULL,
+        CONSTRAINT pk_beta PRIMARY KEY (id),
+        CONSTRAINT uq_beta_uuid UNIQUE (uuid),
+        CONSTRAINT ck_beta_xy_chk CHECK (x > 0 OR y < 100)
+    )
+
+
 
 .. _Pylance: https://github.com/microsoft/pylance-release