]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Document and test __table_cls__
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 26 Sep 2017 00:00:20 +0000 (20:00 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 26 Sep 2017 15:39:08 +0000 (11:39 -0400)
A use case has been identified for __table_cls__, which was
added in 1.0 just for the purpose of test fixtures.   Add this to
public API and ensure the target use case (conditional table generation)
stays supported.

Change-Id: I87be5bcb72205cab89871fa586663bf147450995
Fixes: #4082
(cherry picked from commit 04bbad660bcbb7b920f3e75110a7b1187d9ddc38)

doc/build/orm/extensions/declarative/api.rst
test/ext/declarative/test_basic.py

index 5ef209b75f2bb7cb51601f63cbaa77116a5a3378..d7625d47754141153c91a1072cbd9a6be29e822b 100644 (file)
@@ -49,8 +49,6 @@ assumed to be completed and the 'configure' step has finished::
             ""
             # do something with mappings
 
-.. versionadded:: 0.7.3
-
 ``__declare_first__()``
 ~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -109,6 +107,55 @@ created perhaps within distinct databases::
     DefaultBase.metadata.create_all(some_engine)
     OtherBase.metadata_create_all(some_other_engine)
 
-.. versionadded:: 0.7.3
+
+``__table_cls__``
+~~~~~~~~~~~~~~~~~
+
+Allows the callable / class used to generate a :class:`.Table` to be customized.
+This is a very open-ended hook that can allow special customizations
+to a :class:`.Table` that one generates here::
+
+    class MyMixin(object):
+        @classmethod
+        def __table_cls__(cls, name, metadata, *arg, **kw):
+            return Table(
+                "my_" + name,
+                metadata, *arg, **kw
+            )
+
+The above mixin would cause all :class:`.Table` objects generated to include
+the prefix ``"my_"``, followed by the name normally specified using the
+``__tablename__`` attribute.
+
+``__table_cls__`` also supports the case of returning ``None``, which
+causes the class to be considered as single-table inheritance vs. its subclass.
+This may be useful in some customization schemes to determine that single-table
+inheritance should take place based on the arguments for the table itself,
+such as, define as single-inheritance if there is no primary key present::
+
+    class AutoTable(object):
+        @declared_attr
+        def __tablename__(cls):
+            return cls.__name__
+
+        @classmethod
+        def __table_cls__(cls, *arg, **kw):
+            for obj in arg[1:]:
+                if (isinstance(obj, Column) and obj.primary_key) or \
+                        isinstance(obj, PrimaryKeyConstraint):
+                    return Table(*arg, **kw)
+
+            return None
+
+    class Person(AutoTable, Base):
+        id = Column(Integer, primary_key=True)
+
+    class Employee(Person):
+        employee_name = Column(String)
+
+The above ``Employee`` class would be mapped as single-table inheritance
+against ``Person``; the ``employee_name`` column would be added as a member
+of the ``Person`` table.
 
 
+.. versionadded:: 1.0.0
index d91a88276aee32bce27f432104c5354de372e857..f178006fe963ff8b4d7090c590bac14607018703 100644 (file)
@@ -1,6 +1,6 @@
 
 from sqlalchemy.testing import eq_, assert_raises, \
-    assert_raises_message
+    assert_raises_message, is_
 from sqlalchemy.ext import declarative as decl
 from sqlalchemy import exc
 import sqlalchemy as sa
@@ -17,6 +17,7 @@ from sqlalchemy.testing import fixtures, mock
 from sqlalchemy.orm.events import MapperEvents
 from sqlalchemy.orm import mapper
 from sqlalchemy import event
+from sqlalchemy import inspect
 
 Base = None
 
@@ -1141,6 +1142,44 @@ class DeclarativeTest(DeclarativeTestBase):
         assert Bar.__table__.c.id.references(Foo2.__table__.c.id)
         assert Bar.__table__.kwargs['mysql_engine'] == 'InnoDB'
 
+    def test_table_cls_attribute(self):
+        class Foo(Base):
+            __tablename__ = "foo"
+
+            @classmethod
+            def __table_cls__(cls, *arg, **kw):
+                name = arg[0]
+                return Table(name + 'bat', *arg[1:], **kw)
+
+            id = Column(Integer, primary_key=True)
+
+        eq_(Foo.__table__.name, "foobat")
+
+    def test_table_cls_attribute_return_none(self):
+        from sqlalchemy.schema import Column, PrimaryKeyConstraint
+
+        class AutoTable(object):
+            @declared_attr.cascading
+            def __tablename__(cls):
+                return cls.__name__
+
+            @classmethod
+            def __table_cls__(cls, *arg, **kw):
+                for obj in arg[1:]:
+                    if (isinstance(obj, Column) and obj.primary_key) or \
+                            isinstance(obj, PrimaryKeyConstraint):
+                        return Table(*arg, **kw)
+
+                return None
+
+        class Person(AutoTable, Base):
+            id = Column(Integer, primary_key=True)
+
+        class Employee(Person):
+            employee_name = Column(String)
+
+        is_(inspect(Employee).local_table, Person.__table__)
+
     def test_expression(self):
 
         class User(Base, fixtures.ComparableEntity):