summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-09-25 20:00:20 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2017-09-26 11:38:18 -0400
commit04bbad660bcbb7b920f3e75110a7b1187d9ddc38 (patch)
tree570a52f98b23ece6c49ed1db9a584535065c490c
parent1b0b35f254f545dbeb3ad6e2215ba24ae1c02894 (diff)
downloadsqlalchemy-ticket_4082.tar.gz
Document and test __table_cls__ticket_4082
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
-rw-r--r--doc/build/orm/extensions/declarative/api.rst53
-rw-r--r--test/ext/declarative/test_basic.py41
2 files changed, 90 insertions, 4 deletions
diff --git a/doc/build/orm/extensions/declarative/api.rst b/doc/build/orm/extensions/declarative/api.rst
index 5ef209b75..d7625d477 100644
--- a/doc/build/orm/extensions/declarative/api.rst
+++ b/doc/build/orm/extensions/declarative/api.rst
@@ -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
diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py
index 16fef5128..1b2649abf 100644
--- a/test/ext/declarative/test_basic.py
+++ b/test/ext/declarative/test_basic.py
@@ -1,6 +1,6 @@
from sqlalchemy.testing import eq_, assert_raises, \
- assert_raises_message, expect_warnings
+ assert_raises_message, expect_warnings, 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):