From: Kai Mueller <15907922+kasium@users.noreply.github.com> Date: Tue, 21 Dec 2021 20:00:30 +0000 (-0500) Subject: Fix missing class attributes when using __class_getitem__ X-Git-Tag: rel_2_0_0b1~586 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1c73759353cdf009c21aa1a56caed574c7d5cf50;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fix missing class attributes when using __class_getitem__ Fixed issue where the ``__class_getitem__()`` method of the generated declarative base class by :func:`_orm.as_declarative` would lead to inaccessible class attributes such as ``__table__``, for cases where a ``Generic[T]`` style typing declaration were used in the class hierarchy. This is in continuation from the basic addition of ``__class_getitem__()`` in :ticket:`7368`. Pull request courtesy Kai Mueller. Fixes: #7462 Closes: #7470 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7470 Pull-request-sha: d5e5765e0e5542149f116ed9ccff1b3e2e32dee5 Change-Id: I6418af6d34532ff181343884bd419d9c2684e617 --- diff --git a/doc/build/changelog/unreleased_14/7462.rst b/doc/build/changelog/unreleased_14/7462.rst new file mode 100644 index 0000000000..fa71e1448f --- /dev/null +++ b/doc/build/changelog/unreleased_14/7462.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, orm, mypy + :tickets: 7462, 7368 + + Fixed issue where the ``__class_getitem__()`` method of the generated + declarative base class by :func:`_orm.as_declarative` would lead to + inaccessible class attributes such as ``__table__``, for cases where a + ``Generic[T]`` style typing declaration were used in the class hierarchy. + This is in continuation from the basic addition of ``__class_getitem__()`` + in :ticket:`7368`. Pull request courtesy Kai Mueller. diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index ed8617d52f..7b6814863a 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -787,8 +787,14 @@ class registry: class_dict["__abstract__"] = True if mapper: class_dict["__mapper_cls__"] = mapper + if hasattr(cls, "__class_getitem__"): - class_dict["__class_getitem__"] = cls.__class_getitem__ + + def __class_getitem__(cls, key): + # allow generic classes in py3.9+ + return cls + + class_dict["__class_getitem__"] = __class_getitem__ return metaclass(name, bases, class_dict) diff --git a/test/orm/declarative/test_typing_py3k.py b/test/orm/declarative/test_typing_py3k.py index 823fe54f10..0be91a509f 100644 --- a/test/orm/declarative/test_typing_py3k.py +++ b/test/orm/declarative/test_typing_py3k.py @@ -3,9 +3,13 @@ from typing import Type from typing import TypeVar from sqlalchemy import Column +from sqlalchemy import inspect from sqlalchemy import Integer from sqlalchemy.orm import as_declarative +from sqlalchemy.testing import eq_ from sqlalchemy.testing import fixtures +from sqlalchemy.testing import is_ +from sqlalchemy.testing.assertions import expect_raises class DeclarativeBaseTest(fixtures.TestBase): @@ -17,9 +21,22 @@ class DeclarativeBaseTest(fixtures.TestBase): def boring(cls: Type[T]) -> Type[T]: return cls + @classmethod + def more_boring(cls: Type[T]) -> int: + return 27 + @as_declarative() class Base(CommonBase[T]): - pass + foo = 1 class Tab(Base["Tab"]): + __tablename__ = "foo" a = Column(Integer, primary_key=True) + + eq_(Tab.foo, 1) + is_(Tab.__table__, inspect(Tab).local_table) + eq_(Tab.boring(), Tab) + eq_(Tab.more_boring(), 27) + + with expect_raises(AttributeError): + Tab.non_existent