From 4a7a2182c05f377758d276b1136bfd7e4e9fa87f Mon Sep 17 00:00:00 2001 From: RmStorm Date: Tue, 12 Nov 2019 09:33:24 +0100 Subject: [PATCH 1/3] Fixed bug when using a declarative function for the __tablename__ When specifying a function to dynamically define a tablename as a declared attribute it became impossible to overwrite it using a manually defined name. Look at the test to see the issue, with the original code this test would fail. --- gino/declarative.py | 5 +++-- tests/test_declarative.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/gino/declarative.py b/gino/declarative.py index ecc9bc4c..d709e81f 100644 --- a/gino/declarative.py +++ b/gino/declarative.py @@ -166,8 +166,9 @@ def _init_table(cls, sub_cls): updates[k] = sub_cls.__attr_factory__(k, v) elif isinstance(v, (sa.Index, sa.Constraint)): inspected_args.append(v) - if table_name is None: - table_name = getattr(sub_cls, '__tablename__', None) + if getattr(sub_cls, '__tablename__', None) and \ + not callable(getattr(sub_cls, '__tablename__')): + table_name = getattr(sub_cls, '__tablename__') if table_name is None: return sub_cls._column_name_map = column_name_map diff --git a/tests/test_declarative.py b/tests/test_declarative.py index 7403bb2a..75ccb474 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -260,3 +260,20 @@ class Model(db.Model): select_col = db.Column(name=db.quoted_name('select', False)) assert select_col.name == 'select' assert not select_col.name.quote + + +async def test_overwrite_declared_table_name(): + class MyTableNameMixin: + @db.declared_attr + def __tablename__(cls): + return cls.__name__.lower() + + class MyTableWithoutName(MyTableNameMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + + class MyTableWithName(MyTableNameMixin, db.Model): + __tablename__ = 'manually_overwritten_name' + id = db.Column(db.Integer, primary_key=True) + + assert MyTableWithoutName.__table__.name == 'mytablewithoutname' + assert MyTableWithName.__table__.name == 'manually_overwritten_name' From 4e052b98a8d5e7c6d07f856ae4f6a4560a27b777 Mon Sep 17 00:00:00 2001 From: RmStorm Date: Wed, 13 Nov 2019 09:12:59 +0100 Subject: [PATCH 2/3] Moved __tablename__ check into main loop for attribute checking --- gino/declarative.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/gino/declarative.py b/gino/declarative.py index d709e81f..080d1361 100644 --- a/gino/declarative.py +++ b/gino/declarative.py @@ -152,11 +152,12 @@ def _init_table(cls, sub_cls): for each_cls in sub_cls.__mro__[::-1]: for k, v in getattr(each_cls, '__namespace__', each_cls.__dict__).items(): - if callable(v) and getattr(v, '__declared_attr__', False): - if k == '__tablename__': - table_name = v(sub_cls) - continue + declared_callable_attr = callable(v) and \ + getattr(v, '__declared_attr__', False) + if k != '__tablename__' and declared_callable_attr: v = updates[k] = v(sub_cls) + elif k == '__tablename__': + table_name = v(sub_cls) if declared_callable_attr else v if isinstance(v, sa.Column): v = v.copy() if not v.name: @@ -166,9 +167,6 @@ def _init_table(cls, sub_cls): updates[k] = sub_cls.__attr_factory__(k, v) elif isinstance(v, (sa.Index, sa.Constraint)): inspected_args.append(v) - if getattr(sub_cls, '__tablename__', None) and \ - not callable(getattr(sub_cls, '__tablename__')): - table_name = getattr(sub_cls, '__tablename__') if table_name is None: return sub_cls._column_name_map = column_name_map From 0bae7cdec10ba2f59245b1852b5535b12652f920 Mon Sep 17 00:00:00 2001 From: RmStorm Date: Fri, 15 Nov 2019 13:56:08 +0100 Subject: [PATCH 3/3] Added another test for multiple inheritance This test ensures that the __tablename__ is set correctly according to the inheritance order of the mixins. --- tests/test_declarative.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_declarative.py b/tests/test_declarative.py index 75ccb474..eb70a9ec 100644 --- a/tests/test_declarative.py +++ b/tests/test_declarative.py @@ -277,3 +277,22 @@ class MyTableWithName(MyTableNameMixin, db.Model): assert MyTableWithoutName.__table__.name == 'mytablewithoutname' assert MyTableWithName.__table__.name == 'manually_overwritten_name' + + +async def test_multiple_inheritance_overwrite_declared_table_name(): + class MyTableNameMixin: + @db.declared_attr + def __tablename__(cls): + return cls.__name__.lower() + + class AnotherTableNameMixin: + __tablename__ = "static_table_name" + + class MyTableWithoutName(AnotherTableNameMixin, MyTableNameMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + + class MyOtherTableWithoutName(MyTableNameMixin, AnotherTableNameMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + + assert MyTableWithoutName.__table__.name == 'static_table_name' + assert MyOtherTableWithoutName.__table__.name == 'myothertablewithoutname'