--- /dev/null
+.. change::
+ :tags: schema, usecase
+ :tickets: 11374
+
+ Added :paramref:`_schema.Column.insert_default` as an alias of
+ :paramref:`_schema.Column.default` for compatibility with
+ :func:`_orm.mapped_column`.
.. seealso::
:ref:`subquery_eager_loading`
+
+.. _defaults_default_factory_insert_default:
+
+What are ``default``, ``default_factory`` and ``insert_default`` and what should I use?
+---------------------------------------------------------------------------------------
+
+There's a bit of a clash in SQLAlchemy's API here due to the addition of PEP-681
+dataclass transforms, which is strict about its naming conventions. PEP-681 comes
+into play if you are using :class:`_orm.MappedAsDataclass` as shown in :ref:`orm_declarative_native_dataclasses`.
+If you are not using MappedAsDataclass, then it does not apply.
+
+Part One - Classic SQLAlchemy that is not using dataclasses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When **not** using :class:`_orm.MappedAsDataclass`, as has been the case for many years
+in SQLAlchemy, the :func:`_orm.mapped_column` (and :class:`_schema.Column`)
+construct supports a parameter :paramref:`_orm.mapped_column.default`.
+This indicates a Python-side default (as opposed to a server side default that
+would be part of your database's schema definition) that will take place when
+an ``INSERT`` statement is emitted. This default can be **any** of a static Python value
+like a string, **or** a Python callable function, **or** a SQLAlchemy SQL construct.
+Full documentation for :paramref:`_orm.mapped_column.default` is at
+:ref:`defaults_client_invoked_sql`.
+
+When using :paramref:`_orm.mapped_column.default` with an ORM mapping that is **not**
+using :class:`_orm.MappedAsDataclass`, this default value /callable **does not show
+up on your object when you first construct it**. It only takes place when SQLAlchemy
+works up an ``INSERT`` statement for your object.
+
+A very important thing to note is that when using :func:`_orm.mapped_column`
+(and :class:`_schema.Column`), the classic :paramref:`_orm.mapped_column.default`
+parameter is also available under a new name, called
+:paramref:`_orm.mapped_column.insert_default`. If you build a
+:func:`_orm.mapped_column` and you are **not** using :class:`_orm.MappedAsDataclass`, the
+:paramref:`_orm.mapped_column.default` and :paramref:`_orm.mapped_column.insert_default`
+parameters are **synonymous**.
+
+Part Two - Using Dataclasses support with MappedAsDataclass
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you **are** using :class:`_orm.MappedAsDataclass`, that is, the specific form
+of mapping used at :ref:`orm_declarative_native_dataclasses`, the meaning of the
+:paramref:`_orm.mapped_column.default` keyword changes. We recognize that it's not
+ideal that this name changes its behavior, however there was no alternative as
+PEP-681 requires :paramref:`_orm.mapped_column.default` to take on this meaning.
+
+When dataclasses are used, the :paramref:`_orm.mapped_column.default` parameter must
+be used the way it's described at
+`Python Dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ - it refers
+to a constant value like a string or a number, and **is applied to your object
+immediately when constructed**. It is also at the moment also applied to the
+:paramref:`_orm.mapped_column.default` parameter of :class:`_schema.Column` where
+it would be used in an ``INSERT`` statement automatically even if not present
+on the object. If you instead want to use a callable for your dataclass,
+which will be applied to the object when constructed, you would use
+:paramref:`_orm.mapped_column.default_factory`.
+
+To get access to the ``INSERT``-only behavior of :paramref:`_orm.mapped_column.default`
+that is described in part one above, you would use the
+:paramref:`_orm.mapped_column.insert_default` parameter instead.
+:paramref:`_orm.mapped_column.insert_default` when dataclasses are used continues
+to be a direct route to the Core-level "default" process where the parameter can
+be a static value or callable.
+
+.. list-table:: Summary Chart
+ :header-rows: 1
+
+ * - Construct
+ - Works with dataclasses?
+ - Works without dataclasses?
+ - Accepts scalar?
+ - Accepts callable?
+ - Populates object immediately?
+ * - :paramref:`_orm.mapped_column.default`
+ - ✔
+ - ✔
+ - ✔
+ - Only if no dataclasses
+ - Only if dataclasses
+ * - :paramref:`_orm.mapped_column.insert_default`
+ - ✔
+ - ✔
+ - ✔
+ - ✔
+ - ✖
+ * - :paramref:`_orm.mapped_column.default_factory`
+ - ✔
+ - ✖
+ - ✖
+ - ✔
+ - Only if dataclasses
.. _orm_declarative_native_dataclasses:
Declarative Dataclass Mapping
--------------------------------
+-----------------------------
SQLAlchemy :ref:`Annotated Declarative Table <orm_declarative_mapped_column>`
mappings may be augmented with an additional
be used instead**. This is necessary to disambiguate the callable from
being interpreted as a dataclass level default.
+ .. seealso::
+
+ :ref:`defaults_default_factory_insert_default`
+
+ :paramref:`_orm.mapped_column.insert_default`
+
+ :paramref:`_orm.mapped_column.default_factory`
+
:param insert_default: Passed directly to the
:paramref:`_schema.Column.default` parameter; will supersede the value
of :paramref:`_orm.mapped_column.default` when present, however
:paramref:`_orm.mapped_column.default` will always apply to the
constructor default for a dataclasses mapping.
+ .. seealso::
+
+ :ref:`defaults_default_factory_insert_default`
+
+ :paramref:`_orm.mapped_column.default`
+
+ :paramref:`_orm.mapped_column.default_factory`
+
:param sort_order: An integer that indicates how this mapped column
should be sorted compared to the others when the ORM is creating a
:class:`_schema.Table`. Among mapped columns that have the same
specifies a default-value generation function that will take place
as part of the ``__init__()``
method as generated by the dataclass process.
+
+ .. seealso::
+
+ :ref:`defaults_default_factory_insert_default`
+
+ :paramref:`_orm.mapped_column.default`
+
+ :paramref:`_orm.mapped_column.insert_default`
+
:param compare: Specific to
:ref:`orm_declarative_native_dataclasses`, indicates if this field
should be included in comparison operations when generating the
from . import type_api
from . import visitors
from .base import _DefaultDescriptionTuple
+from .base import _NoArg
from .base import _NoneName
from .base import _SentinelColumnCharacterization
from .base import _SentinelDefaultCharacterization
name: Optional[str] = None,
type_: Optional[_TypeEngineArgument[_T]] = None,
autoincrement: _AutoIncrementType = "auto",
- default: Optional[Any] = None,
+ default: Optional[Any] = _NoArg.NO_ARG,
+ insert_default: Optional[Any] = _NoArg.NO_ARG,
doc: Optional[str] = None,
key: Optional[str] = None,
index: Optional[bool] = None,
:ref:`metadata_defaults_toplevel`
+ :param insert_default: An alias of :paramref:`.Column.default`
+ for compatibility with :func:`_orm.mapped_column`.
+
+ .. versionadded: 2.0.31
+
:param doc: optional String that can be used by the ORM or similar
to document attributes on the Python side. This attribute does
**not** render SQL comments; use the
# otherwise, add DDL-related events
self._set_type(self.type)
- if default is not None:
- if not isinstance(default, (ColumnDefault, Sequence)):
- default = ColumnDefault(default)
+ if insert_default is not _NoArg.NO_ARG:
+ resolved_default = insert_default
+ elif default is not _NoArg.NO_ARG:
+ resolved_default = default
+ else:
+ resolved_default = None
+
+ if resolved_default is not None:
+ if not isinstance(resolved_default, (ColumnDefault, Sequence)):
+ resolved_default = ColumnDefault(resolved_default)
- self.default = default
- l_args.append(default)
+ self.default = resolved_default
+ l_args.append(resolved_default)
else:
self.default = None
comment="foo",
),
"Column('foo', Integer(), table=None, primary_key=True, "
- "nullable=False, onupdate=%s, default=%s, server_default=%s, "
- "comment='foo')"
- % (
- ColumnDefault(1),
- ColumnDefault(42),
- DefaultClause("42"),
+ f"nullable=False, onupdate={ColumnDefault(1)}, default="
+ f"{ColumnDefault(42)}, server_default={DefaultClause('42')}, "
+ "comment='foo')",
+ ),
+ (
+ Column(
+ "foo",
+ Integer,
+ primary_key=True,
+ nullable=False,
+ onupdate=1,
+ insert_default=42,
+ server_default="42",
+ comment="foo",
),
+ "Column('foo', Integer(), table=None, primary_key=True, "
+ f"nullable=False, onupdate={ColumnDefault(1)}, default="
+ f"{ColumnDefault(42)}, server_default={DefaultClause('42')}, "
+ "comment='foo')",
),
(
Table("bar", MetaData(), Column("x", String)),
assert c.onupdate.arg == target
assert c.onupdate.column is c
+ def test_column_insert_default(self):
+ c = self._fixture(insert_default="y")
+ assert c.default.arg == "y"
+
+ def test_column_insert_default_predecende_on_default(self):
+ c = self._fixture(insert_default="x", default="y")
+ assert c.default.arg == "x"
+ c = self._fixture(default="y", insert_default="x")
+ assert c.default.arg == "x"
+
class ColumnOptionsTest(fixtures.TestBase):
def test_default_generators(self):