diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-09-25 20:00:20 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2017-09-26 11:38:18 -0400 |
commit | 04bbad660bcbb7b920f3e75110a7b1187d9ddc38 (patch) | |
tree | 570a52f98b23ece6c49ed1db9a584535065c490c | |
parent | 1b0b35f254f545dbeb3ad6e2215ba24ae1c02894 (diff) | |
download | sqlalchemy-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.rst | 53 | ||||
-rw-r--r-- | test/ext/declarative/test_basic.py | 41 |
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): |