]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
accommodate all mapped_column() parameters in Annotated transfer
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 19 Sep 2023 21:57:50 +0000 (17:57 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Sep 2023 22:14:47 +0000 (18:14 -0400)
Fixed a wide range of :func:`_orm.mapped_column` parameters that were not
being transferred when using the :func:`_orm.mapped_column` object inside
of a pep-593 ``Annotated`` object, including
:paramref:`_orm.mapped_column.sort_order`,
:paramref:`_orm.mapped_column.deferred`,
:paramref:`_orm.mapped_column.autoincrement`,
:paramref:`_orm.mapped_column.system`, :paramref:`_orm.mapped_column.info`
etc.

Additionally, it remains not supported to have dataclass arguments, such as
:paramref:`_orm.mapped_column.kw_only`,
:paramref:`_orm.mapped_column.default_factory` etc. indicated within the
:func:`_orm.mapped_column` received by ``Annotated``, as this is not
supported with pep-681 Dataclass Transforms.  A warning is now emitted when
these parameters are used within ``Annotated`` in this way (and they
continue to be ignored).

Fixes: #10369
Fixes: #10046
Change-Id: Ibcfb287cba0e764db0ae15fab8049bbb9f94dd1b

doc/build/changelog/unreleased_20/10369.rst [new file with mode: 0644]
lib/sqlalchemy/orm/_orm_constructors.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/sql/schema.py
test/orm/declarative/test_tm_future_annotations_sync.py
test/orm/declarative/test_typed_mapping.py
test/sql/test_metadata.py

diff --git a/doc/build/changelog/unreleased_20/10369.rst b/doc/build/changelog/unreleased_20/10369.rst
new file mode 100644 (file)
index 0000000..9fb9e3a
--- /dev/null
@@ -0,0 +1,20 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 10369, 10046
+
+    Fixed a wide range of :func:`_orm.mapped_column` parameters that were not
+    being transferred when using the :func:`_orm.mapped_column` object inside
+    of a pep-593 ``Annotated`` object, including
+    :paramref:`_orm.mapped_column.sort_order`,
+    :paramref:`_orm.mapped_column.deferred`,
+    :paramref:`_orm.mapped_column.autoincrement`,
+    :paramref:`_orm.mapped_column.system`, :paramref:`_orm.mapped_column.info`
+    etc.
+
+    Additionally, it remains not supported to have dataclass arguments, such as
+    :paramref:`_orm.mapped_column.kw_only`,
+    :paramref:`_orm.mapped_column.default_factory` etc. indicated within the
+    :func:`_orm.mapped_column` received by ``Annotated``, as this is not
+    supported with pep-681 Dataclass Transforms.  A warning is now emitted when
+    these parameters are used within ``Annotated`` in this way (and they
+    continue to be ignored).
index 9e41874dc1acdc67a86c0790426e963724a565cb..df36c38641677b7f57334cc270fcdcf305361a09 100644 (file)
@@ -114,7 +114,7 @@ def mapped_column(
     primary_key: Optional[bool] = False,
     deferred: Union[_NoArg, bool] = _NoArg.NO_ARG,
     deferred_group: Optional[str] = None,
-    deferred_raiseload: bool = False,
+    deferred_raiseload: Optional[bool] = None,
     use_existing_column: bool = False,
     name: Optional[str] = None,
     type_: Optional[_TypeEngineArgument[Any]] = None,
@@ -132,7 +132,7 @@ def mapped_column(
     quote: Optional[bool] = None,
     system: bool = False,
     comment: Optional[str] = None,
-    sort_order: int = 0,
+    sort_order: Union[_NoArg, int] = _NoArg.NO_ARG,
     **kw: Any,
 ) -> MappedColumn[Any]:
     r"""declare a new ORM-mapped :class:`_schema.Column` construct
index 606cebc40c7271f6518b0694a35f93ac5fac317c..4bb396edc5dc401a7ba56b7336eb733230e49f12 100644 (file)
@@ -25,6 +25,7 @@ from typing import Tuple
 from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
+from typing import Union
 
 from . import attributes
 from . import strategy_options
@@ -542,7 +543,7 @@ class MappedColumn(
         "_use_existing_column",
     )
 
-    deferred: bool
+    deferred: Union[_NoArg, bool]
     deferred_raiseload: bool
     deferred_group: Optional[str]
 
@@ -557,17 +558,15 @@ class MappedColumn(
 
         self._use_existing_column = kw.pop("use_existing_column", False)
 
-        self._has_dataclass_arguments = False
-
-        if attr_opts is not None and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS:
-            if attr_opts.dataclasses_default_factory is not _NoArg.NO_ARG:
-                self._has_dataclass_arguments = True
-
-            elif (
-                attr_opts.dataclasses_init is not _NoArg.NO_ARG
-                or attr_opts.dataclasses_repr is not _NoArg.NO_ARG
-            ):
-                self._has_dataclass_arguments = True
+        self._has_dataclass_arguments = (
+            attr_opts is not None
+            and attr_opts != _DEFAULT_ATTRIBUTE_OPTIONS
+            and any(
+                attr_opts[i] is not _NoArg.NO_ARG
+                for i, attr in enumerate(attr_opts._fields)
+                if attr != "dataclasses_default"
+            )
+        )
 
         insert_default = kw.pop("insert_default", _NoArg.NO_ARG)
         self._has_insert_default = insert_default is not _NoArg.NO_ARG
@@ -580,12 +579,9 @@ class MappedColumn(
         self.deferred_group = kw.pop("deferred_group", None)
         self.deferred_raiseload = kw.pop("deferred_raiseload", None)
         self.deferred = kw.pop("deferred", _NoArg.NO_ARG)
-        if self.deferred is _NoArg.NO_ARG:
-            self.deferred = bool(
-                self.deferred_group or self.deferred_raiseload
-            )
         self.active_history = kw.pop("active_history", False)
-        self._sort_order = kw.pop("sort_order", 0)
+
+        self._sort_order = kw.pop("sort_order", _NoArg.NO_ARG)
         self.column = cast("Column[_T]", Column(*arg, **kw))
         self.foreign_keys = self.column.foreign_keys
         self._has_nullable = "nullable" in kw and kw.get("nullable") not in (
@@ -617,10 +613,16 @@ class MappedColumn(
 
     @property
     def mapper_property_to_assign(self) -> Optional[MapperProperty[_T]]:
-        if self.deferred or self.active_history:
+        effective_deferred = self.deferred
+        if effective_deferred is _NoArg.NO_ARG:
+            effective_deferred = bool(
+                self.deferred_group or self.deferred_raiseload
+            )
+
+        if effective_deferred or self.active_history:
             return ColumnProperty(
                 self.column,
-                deferred=self.deferred,
+                deferred=effective_deferred,
                 group=self.deferred_group,
                 raiseload=self.deferred_raiseload,
                 attribute_options=self._attribute_options,
@@ -631,7 +633,14 @@ class MappedColumn(
 
     @property
     def columns_to_assign(self) -> List[Tuple[Column[Any], int]]:
-        return [(self.column, self._sort_order)]
+        return [
+            (
+                self.column,
+                self._sort_order
+                if self._sort_order is not _NoArg.NO_ARG
+                else 0,
+            )
+        ]
 
     def __clause_element__(self) -> Column[_T]:
         return self.column
@@ -779,6 +788,65 @@ class MappedColumn(
             use_args_from.column._merge(self.column)
             sqltype = self.column.type
 
+            if (
+                use_args_from.deferred is not _NoArg.NO_ARG
+                and self.deferred is _NoArg.NO_ARG
+            ):
+                self.deferred = use_args_from.deferred
+
+            if (
+                use_args_from.deferred_group is not None
+                and self.deferred_group is None
+            ):
+                self.deferred_group = use_args_from.deferred_group
+
+            if (
+                use_args_from.deferred_raiseload is not None
+                and self.deferred_raiseload is None
+            ):
+                self.deferred_raiseload = use_args_from.deferred_raiseload
+
+            if (
+                use_args_from._use_existing_column
+                and not self._use_existing_column
+            ):
+                self._use_existing_column = True
+
+            if use_args_from.active_history:
+                self.active_history = use_args_from.active_history
+
+            if (
+                use_args_from._sort_order is not None
+                and self._sort_order is _NoArg.NO_ARG
+            ):
+                self._sort_order = use_args_from._sort_order
+
+            if (
+                use_args_from.column.key is not None
+                or use_args_from.column.name is not None
+            ):
+                util.warn_deprecated(
+                    "Can't use the 'key' or 'name' arguments in "
+                    "Annotated with mapped_column(); this will be ignored",
+                    "2.0.22",
+                )
+
+            if use_args_from._has_dataclass_arguments:
+                for idx, arg in enumerate(
+                    use_args_from._attribute_options._fields
+                ):
+                    if (
+                        use_args_from._attribute_options[idx]
+                        is not _NoArg.NO_ARG
+                    ):
+                        arg = arg.replace("dataclasses_", "")
+                        util.warn_deprecated(
+                            f"Argument '{arg}' is a dataclass argument and "
+                            "cannot be specified within a mapped_column() "
+                            "bundled inside of an Annotated object",
+                            "2.0.22",
+                        )
+
         if sqltype._isnull and not self.column.foreign_keys:
             new_sqltype = None
 
index 56d652dbb170f0bdb68d021be34e208de1451048..ca389a9a71a8bd1b7b1217f7cf09743c529dfba7 100644 (file)
@@ -2522,6 +2522,15 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]):
         if self.primary_key:
             other.primary_key = True
 
+        if self.autoincrement != "auto" and other.autoincrement == "auto":
+            other.autoincrement = self.autoincrement
+
+        if self.system:
+            other.system = self.system
+
+        if self.info:
+            other.info.update(self.info)
+
         type_ = self.type
         if not type_._isnull and other.type._isnull:
             if isinstance(type_, SchemaEventTarget):
@@ -2567,6 +2576,12 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]):
         if self.index and not other.index:
             other.index = True
 
+        if self.doc and other.doc is None:
+            other.doc = self.doc
+
+        if self.comment and other.comment is None:
+            other.comment = self.comment
+
         if self.unique and not other.unique:
             other.unique = True
 
index bb392ba3144ae7c3b5fd3cf09a0ffab5219710f0..3790bd0440cd29209d5ad5e3db65bdd7f8ecc3f4 100644 (file)
@@ -12,8 +12,10 @@ import dataclasses
 import datetime
 from decimal import Decimal
 import enum
+import inspect as _py_inspect
 import typing
 from typing import Any
+from typing import cast
 from typing import ClassVar
 from typing import Dict
 from typing import Generic
@@ -34,6 +36,7 @@ from sqlalchemy import BIGINT
 from sqlalchemy import BigInteger
 from sqlalchemy import Column
 from sqlalchemy import DateTime
+from sqlalchemy import exc
 from sqlalchemy import exc as sa_exc
 from sqlalchemy import ForeignKey
 from sqlalchemy import func
@@ -68,10 +71,13 @@ from sqlalchemy.orm import undefer
 from sqlalchemy.orm import WriteOnlyMapped
 from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.collections import KeyFuncDict
+from sqlalchemy.orm.properties import MappedColumn
 from sqlalchemy.schema import CreateTable
+from sqlalchemy.sql.base import _NoArg
 from sqlalchemy.sql.sqltypes import Enum
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_deprecated
 from sqlalchemy.testing import expect_raises
 from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import fixtures
@@ -126,6 +132,15 @@ class DeclarativeBaseTest(fixtures.TestBase):
             Tab.non_existent
 
 
+_annotated_names_tested = set()
+
+
+def annotated_name_test_cases(*cases, **kw):
+    _annotated_names_tested.update([case[0] for case in cases])
+
+    return testing.combinations_list(cases, **kw)
+
+
 class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
 
@@ -638,6 +653,235 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         is_true(MyClass.__table__.c.data_two.nullable)
         eq_(MyClass.__table__.c.data_three.type.length, 50)
 
+    @testing.requires.python310
+    def test_we_got_all_attrs_test_annotated(self):
+        argnames = _py_inspect.getfullargspec(mapped_column)
+        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
+            f"annotated attributes were not tested: "
+            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+        )
+
+    @annotated_name_test_cases(
+        ("sort_order", 100, lambda sort_order: sort_order == 100),
+        ("nullable", False, lambda column: column.nullable is False),
+        (
+            "active_history",
+            True,
+            lambda column_property: column_property.active_history is True,
+        ),
+        (
+            "deferred",
+            True,
+            lambda column_property: column_property.deferred is True,
+        ),
+        (
+            "deferred",
+            _NoArg.NO_ARG,
+            lambda column_property: column_property is None,
+        ),
+        (
+            "deferred_group",
+            "mygroup",
+            lambda column_property: column_property.deferred is True
+            and column_property.group == "mygroup",
+        ),
+        (
+            "deferred_raiseload",
+            True,
+            lambda column_property: column_property.deferred is True
+            and column_property.raiseload is True,
+        ),
+        (
+            "server_default",
+            "25",
+            lambda column: column.server_default.arg == "25",
+        ),
+        (
+            "server_onupdate",
+            "25",
+            lambda column: column.server_onupdate.arg == "25",
+        ),
+        (
+            "default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "insert_default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "onupdate",
+            25,
+            lambda column: column.onupdate.arg == 25,
+        ),
+        ("doc", "some doc", lambda column: column.doc == "some doc"),
+        (
+            "comment",
+            "some comment",
+            lambda column: column.comment == "some comment",
+        ),
+        ("index", True, lambda column: column.index is True),
+        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
+        ("unique", True, lambda column: column.unique is True),
+        ("autoincrement", True, lambda column: column.autoincrement is True),
+        ("system", True, lambda column: column.system is True),
+        ("primary_key", True, lambda column: column.primary_key is True),
+        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
+        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
+        (
+            "use_existing_column",
+            True,
+            lambda mc: mc._use_existing_column is True,
+        ),
+        (
+            "quote",
+            True,
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "key",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "name",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "kw_only",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'kw_only' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "compare",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'compare' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "default_factory",
+            lambda: 25,
+            exc.SADeprecationWarning(
+                "Argument 'default_factory' is a dataclass argument "
+            ),
+        ),
+        (
+            "repr",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'repr' is a dataclass argument "
+            ),
+        ),
+        (
+            "init",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'init' is a dataclass argument"
+            ),
+        ),
+        argnames="argname, argument, assertion",
+    )
+    @testing.variation("use_annotated", [True, False, "control"])
+    def test_names_encountered_for_annotated(
+        self, argname, argument, assertion, use_annotated, decl_base
+    ):
+        global myint
+
+        if argument is not _NoArg.NO_ARG:
+            kw = {argname: argument}
+
+            if argname == "quote":
+                kw["name"] = "somename"
+        else:
+            kw = {}
+
+        is_warning = isinstance(assertion, exc.SADeprecationWarning)
+        is_dataclass = argname in (
+            "kw_only",
+            "init",
+            "repr",
+            "compare",
+            "default_factory",
+        )
+
+        if is_dataclass:
+
+            class Base(MappedAsDataclass, decl_base):
+                __abstract__ = True
+
+        else:
+            Base = decl_base
+
+        if use_annotated.control:
+            # test in reverse; that kw set on the main mapped_column() takes
+            # effect when the Annotated is there also and does not have the
+            # kw
+            amc = mapped_column()
+            myint = Annotated[int, amc]
+
+            mc = mapped_column(**kw)
+
+            class User(Base):
+                __tablename__ = "user"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                myname: Mapped[myint] = mc
+
+        elif use_annotated:
+            amc = mapped_column(**kw)
+            myint = Annotated[int, amc]
+
+            mc = mapped_column()
+
+            if is_warning:
+                with expect_deprecated(assertion.args[0]):
+
+                    class User(Base):
+                        __tablename__ = "user"
+                        id: Mapped[int] = mapped_column(primary_key=True)
+                        myname: Mapped[myint] = mc
+
+            else:
+
+                class User(Base):
+                    __tablename__ = "user"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    myname: Mapped[myint] = mc
+
+        else:
+            mc = cast(MappedColumn, mapped_column(**kw))
+
+        mapper_prop = mc.mapper_property_to_assign
+        column_to_assign, sort_order = mc.columns_to_assign[0]
+
+        if not is_warning:
+            assert_result = testing.resolve_lambda(
+                assertion,
+                sort_order=sort_order,
+                column_property=mapper_prop,
+                column=column_to_assign,
+                mc=mc,
+            )
+            assert assert_result
+        elif is_dataclass and (not use_annotated or use_annotated.control):
+            eq_(
+                getattr(mc._attribute_options, f"dataclasses_{argname}"),
+                argument,
+            )
+
     def test_pep484_newtypes_as_typemap_keys(
         self, decl_base: Type[DeclarativeBase]
     ):
index de2f6f94a73a69bec401cc6003f7f4188e674fc6..7ddf2da7f883bb420b072917743caf77b86608e6 100644 (file)
@@ -3,8 +3,10 @@ import dataclasses
 import datetime
 from decimal import Decimal
 import enum
+import inspect as _py_inspect
 import typing
 from typing import Any
+from typing import cast
 from typing import ClassVar
 from typing import Dict
 from typing import Generic
@@ -25,6 +27,7 @@ from sqlalchemy import BIGINT
 from sqlalchemy import BigInteger
 from sqlalchemy import Column
 from sqlalchemy import DateTime
+from sqlalchemy import exc
 from sqlalchemy import exc as sa_exc
 from sqlalchemy import ForeignKey
 from sqlalchemy import func
@@ -59,10 +62,13 @@ from sqlalchemy.orm import undefer
 from sqlalchemy.orm import WriteOnlyMapped
 from sqlalchemy.orm.collections import attribute_keyed_dict
 from sqlalchemy.orm.collections import KeyFuncDict
+from sqlalchemy.orm.properties import MappedColumn
 from sqlalchemy.schema import CreateTable
+from sqlalchemy.sql.base import _NoArg
 from sqlalchemy.sql.sqltypes import Enum
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_deprecated
 from sqlalchemy.testing import expect_raises
 from sqlalchemy.testing import expect_raises_message
 from sqlalchemy.testing import fixtures
@@ -117,6 +123,15 @@ class DeclarativeBaseTest(fixtures.TestBase):
             Tab.non_existent
 
 
+_annotated_names_tested = set()
+
+
+def annotated_name_test_cases(*cases, **kw):
+    _annotated_names_tested.update([case[0] for case in cases])
+
+    return testing.combinations_list(cases, **kw)
+
+
 class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
     __dialect__ = "default"
 
@@ -629,6 +644,235 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
         is_true(MyClass.__table__.c.data_two.nullable)
         eq_(MyClass.__table__.c.data_three.type.length, 50)
 
+    @testing.requires.python310
+    def test_we_got_all_attrs_test_annotated(self):
+        argnames = _py_inspect.getfullargspec(mapped_column)
+        assert _annotated_names_tested.issuperset(argnames.kwonlyargs), (
+            f"annotated attributes were not tested: "
+            f"{set(argnames.kwonlyargs).difference(_annotated_names_tested)}"
+        )
+
+    @annotated_name_test_cases(
+        ("sort_order", 100, lambda sort_order: sort_order == 100),
+        ("nullable", False, lambda column: column.nullable is False),
+        (
+            "active_history",
+            True,
+            lambda column_property: column_property.active_history is True,
+        ),
+        (
+            "deferred",
+            True,
+            lambda column_property: column_property.deferred is True,
+        ),
+        (
+            "deferred",
+            _NoArg.NO_ARG,
+            lambda column_property: column_property is None,
+        ),
+        (
+            "deferred_group",
+            "mygroup",
+            lambda column_property: column_property.deferred is True
+            and column_property.group == "mygroup",
+        ),
+        (
+            "deferred_raiseload",
+            True,
+            lambda column_property: column_property.deferred is True
+            and column_property.raiseload is True,
+        ),
+        (
+            "server_default",
+            "25",
+            lambda column: column.server_default.arg == "25",
+        ),
+        (
+            "server_onupdate",
+            "25",
+            lambda column: column.server_onupdate.arg == "25",
+        ),
+        (
+            "default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "insert_default",
+            25,
+            lambda column: column.default.arg == 25,
+        ),
+        (
+            "onupdate",
+            25,
+            lambda column: column.onupdate.arg == 25,
+        ),
+        ("doc", "some doc", lambda column: column.doc == "some doc"),
+        (
+            "comment",
+            "some comment",
+            lambda column: column.comment == "some comment",
+        ),
+        ("index", True, lambda column: column.index is True),
+        ("index", _NoArg.NO_ARG, lambda column: column.index is None),
+        ("unique", True, lambda column: column.unique is True),
+        ("autoincrement", True, lambda column: column.autoincrement is True),
+        ("system", True, lambda column: column.system is True),
+        ("primary_key", True, lambda column: column.primary_key is True),
+        ("type_", BIGINT, lambda column: isinstance(column.type, BIGINT)),
+        ("info", {"foo": "bar"}, lambda column: column.info == {"foo": "bar"}),
+        (
+            "use_existing_column",
+            True,
+            lambda mc: mc._use_existing_column is True,
+        ),
+        (
+            "quote",
+            True,
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "key",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "name",
+            "mykey",
+            exc.SADeprecationWarning(
+                "Can't use the 'key' or 'name' arguments in Annotated "
+            ),
+        ),
+        (
+            "kw_only",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'kw_only' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "compare",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'compare' is a dataclass argument "
+            ),
+            testing.requires.python310,
+        ),
+        (
+            "default_factory",
+            lambda: 25,
+            exc.SADeprecationWarning(
+                "Argument 'default_factory' is a dataclass argument "
+            ),
+        ),
+        (
+            "repr",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'repr' is a dataclass argument "
+            ),
+        ),
+        (
+            "init",
+            True,
+            exc.SADeprecationWarning(
+                "Argument 'init' is a dataclass argument"
+            ),
+        ),
+        argnames="argname, argument, assertion",
+    )
+    @testing.variation("use_annotated", [True, False, "control"])
+    def test_names_encountered_for_annotated(
+        self, argname, argument, assertion, use_annotated, decl_base
+    ):
+        # anno only: global myint
+
+        if argument is not _NoArg.NO_ARG:
+            kw = {argname: argument}
+
+            if argname == "quote":
+                kw["name"] = "somename"
+        else:
+            kw = {}
+
+        is_warning = isinstance(assertion, exc.SADeprecationWarning)
+        is_dataclass = argname in (
+            "kw_only",
+            "init",
+            "repr",
+            "compare",
+            "default_factory",
+        )
+
+        if is_dataclass:
+
+            class Base(MappedAsDataclass, decl_base):
+                __abstract__ = True
+
+        else:
+            Base = decl_base
+
+        if use_annotated.control:
+            # test in reverse; that kw set on the main mapped_column() takes
+            # effect when the Annotated is there also and does not have the
+            # kw
+            amc = mapped_column()
+            myint = Annotated[int, amc]
+
+            mc = mapped_column(**kw)
+
+            class User(Base):
+                __tablename__ = "user"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                myname: Mapped[myint] = mc
+
+        elif use_annotated:
+            amc = mapped_column(**kw)
+            myint = Annotated[int, amc]
+
+            mc = mapped_column()
+
+            if is_warning:
+                with expect_deprecated(assertion.args[0]):
+
+                    class User(Base):
+                        __tablename__ = "user"
+                        id: Mapped[int] = mapped_column(primary_key=True)
+                        myname: Mapped[myint] = mc
+
+            else:
+
+                class User(Base):
+                    __tablename__ = "user"
+                    id: Mapped[int] = mapped_column(primary_key=True)
+                    myname: Mapped[myint] = mc
+
+        else:
+            mc = cast(MappedColumn, mapped_column(**kw))
+
+        mapper_prop = mc.mapper_property_to_assign
+        column_to_assign, sort_order = mc.columns_to_assign[0]
+
+        if not is_warning:
+            assert_result = testing.resolve_lambda(
+                assertion,
+                sort_order=sort_order,
+                column_property=mapper_prop,
+                column=column_to_assign,
+                mc=mc,
+            )
+            assert assert_result
+        elif is_dataclass and (not use_annotated or use_annotated.control):
+            eq_(
+                getattr(mc._attribute_options, f"dataclasses_{argname}"),
+                argument,
+            )
+
     def test_pep484_newtypes_as_typemap_keys(
         self, decl_base: Type[DeclarativeBase]
     ):
index 659409de25f6fa26bf471ede69a4135867a567ff..0b35adc1ccc9194647d858521abebc6b7784dd3b 100644 (file)
@@ -4381,6 +4381,11 @@ class ColumnDefinitionTest(AssertsCompiledSQL, fixtures.TestBase):
         ("unique", True),
         ("type", BigInteger()),
         ("type", Enum("one", "two", "three", create_constraint=True)),
+        ("doc", "some doc"),
+        ("comment", "some comment"),
+        ("system", True),
+        ("autoincrement", True),
+        ("info", {"foo": "bar"}),
         argnames="paramname, value",
     )
     def test_merge_column(
@@ -4434,6 +4439,8 @@ class ColumnDefinitionTest(AssertsCompiledSQL, fixtures.TestBase):
                 # so here it's getting mutated in place.   this is a bug
                 is_(default.column, target_copy)
 
+            elif paramname in ("info",):
+                eq_(col.info, value)
             elif paramname == "type":
                 assert type(col.type) is type(value)