File: mapping_columns.rst

package info (click to toggle)
sqlalchemy 1.4.46%2Bds1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 22,444 kB
  • sloc: python: 341,434; ansic: 1,760; makefile: 226; xml: 17; sh: 7
file content (302 lines) | stat: -rw-r--r-- 12,840 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
.. _mapping_columns_toplevel:

.. currentmodule:: sqlalchemy.orm

Mapping Table Columns
=====================

Introductory background on mapping to columns falls under the subject of
:class:`.Table` configuration; the general form falls under one of three
forms:

* :ref:`orm_declarative_table` - :class:`.Column` objects are associated with a
  :class:`.Table` as well as with an ORM mapping in one step by declaring
  them inline as class attributes.
* :ref:`orm_imperative_table_configuration` - :class:`.Column` objects are
  associated directly with their :class:`.Table` object, as detailed at
  :ref:`metadata_describing_toplevel`; the columns are then mapped by
  the Declarative process by associating the :class:`.Table` with the
  class to be mapped via the ``__table__`` attribute.
* :ref:`orm_imperative_mapping` - like "Imperative Table", :class:`.Column`
  objects are associated directly with their :class:`.Table` object; the
  columns are then mapped by the Imperative process using
  :meth:`_orm.registry.map_imperatively`.

In all of the above cases, the :class:`_orm.mapper` constructor is ultimately
invoked with a completed :class:`.Table` object passed as the selectable unit
to be mapped. The behavior of :class:`_orm.mapper` then is to assemble all the
columns in the mapped :class:`_schema.Table` into mapped object attributes,
each of which are named according to the name of the column itself
(specifically, the ``key`` attribute of :class:`_schema.Column`). This behavior
can be modified in several ways.

.. _mapper_column_distinct_names:

Naming Columns Distinctly from Attribute Names
----------------------------------------------

A mapping by default shares the same name for a
:class:`_schema.Column` as that of the mapped attribute - specifically
it matches the :attr:`_schema.Column.key` attribute on :class:`_schema.Column`, which
by default is the same as the :attr:`_schema.Column.name`.

The name assigned to the Python attribute which maps to
:class:`_schema.Column` can be different from either
:attr:`_schema.Column.name` or :attr:`_schema.Column.key` just by assigning
it that way, as we illustrate here in a Declarative mapping::

    class User(Base):
        __tablename__ = "user"
        id = Column("user_id", Integer, primary_key=True)
        name = Column("user_name", String(50))

Where above ``User.id`` resolves to a column named ``user_id``
and ``User.name`` resolves to a column named ``user_name``.

When mapping to an existing table, the :class:`_schema.Column` object
can be referenced directly::

    class User(Base):
        __table__ = user_table
        id = user_table.c.user_id
        name = user_table.c.user_name

The corresponding technique for an :term:`imperative` mapping is
to place the desired key in the :paramref:`_orm.mapper.properties`
dictionary with the desired key::

    mapper_registry.map_imperatively(
        User,
        user_table,
        properties={
            "id": user_table.c.user_id,
            "name": user_table.c.user_name,
        },
    )

.. _mapper_automated_reflection_schemes:

Automating Column Naming Schemes from Reflected Tables
------------------------------------------------------

In the previous section :ref:`mapper_column_distinct_names`, we showed how
a :class:`_schema.Column` explicitly mapped to a class can have a different attribute
name than the column.  But what if we aren't listing out :class:`_schema.Column`
objects explicitly, and instead are automating the production of :class:`_schema.Table`
objects using reflection (i.e. as described in :ref:`metadata_reflection_toplevel`)?
In this case we can make use of the :meth:`_events.DDLEvents.column_reflect` event
to intercept the production of :class:`_schema.Column` objects and provide them
with the :attr:`_schema.Column.key` of our choice.   The event is most easily
associated with the :class:`_schema.MetaData` object that's in use,
such as below we use the one linked to the :class:`_orm.declarative_base`
instance::

    @event.listens_for(Base.metadata, "column_reflect")
    def column_reflect(inspector, table, column_info):
        # set column.key = "attr_<lower_case_name>"
        column_info["key"] = "attr_%s" % column_info["name"].lower()

With the above event, the reflection of :class:`_schema.Column` objects will be intercepted
with our event that adds a new ".key" element, such as in a mapping as below::

    class MyClass(Base):
        __table__ = Table("some_table", Base.metadata, autoload_with=some_engine)

The approach also works with both the :class:`.DeferredReflection` base class
as well as with the :ref:`automap_toplevel` extension.   For automap
specifically, see the section :ref:`automap_intercepting_columns` for
background.

.. seealso::

    :ref:`orm_declarative_reflected`

    :meth:`_events.DDLEvents.column_reflect`

    :ref:`automap_intercepting_columns` - in the :ref:`automap_toplevel` documentation


.. _column_property_options:

Using column_property for column level options
----------------------------------------------

Options can be specified when mapping a :class:`_schema.Column` using the
:func:`.column_property` function.  This function
explicitly creates the :class:`.ColumnProperty` used by the
:func:`.mapper` to keep track of the :class:`_schema.Column`; normally, the
:func:`.mapper` creates this automatically.   Using :func:`.column_property`,
we can pass additional arguments about how we'd like the :class:`_schema.Column`
to be mapped.   Below, we pass an option ``active_history``,
which specifies that a change to this column's value should
result in the former value being loaded first::

    from sqlalchemy.orm import column_property


    class User(Base):
        __tablename__ = "user"

        id = Column(Integer, primary_key=True)
        name = column_property(Column(String(50)), active_history=True)

:func:`.column_property` is also used to map a single attribute to
multiple columns.  This use case arises when mapping to a :func:`_expression.join`
which has attributes which are equated to each other::

    class User(Base):
        __table__ = user.join(address)

        # assign "user.id", "address.user_id" to the
        # "id" attribute
        id = column_property(user_table.c.id, address_table.c.user_id)

For more examples featuring this usage, see :ref:`maptojoin`.

Another place where :func:`.column_property` is needed is to specify SQL expressions as
mapped attributes, such as below where we create an attribute ``fullname``
that is the string concatenation of the ``firstname`` and ``lastname``
columns::

    class User(Base):
        __tablename__ = "user"
        id = Column(Integer, primary_key=True)
        firstname = Column(String(50))
        lastname = Column(String(50))
        fullname = column_property(firstname + " " + lastname)

See examples of this usage at :ref:`mapper_sql_expressions`.

.. autofunction:: column_property

.. _mapper_primary_key:

Mapping to an Explicit Set of Primary Key Columns
-------------------------------------------------

The :class:`.Mapper` construct in order to successfully map a table always
requires that at least one column be identified as the "primary key" for
that selectable.   This is so that when an ORM object is loaded or persisted,
it can be placed in the :term:`identity map` with an appropriate
:term:`identity key`.

To support this use case, all :class:`.FromClause` objects (where
:class:`.FromClause` is the common base for objects such as :class:`.Table`,
:class:`.Join`, :class:`.Subquery`, etc.) have an attribute
:attr:`.FromClause.primary_key` which returns a collection of those
:class:`.Column` objects that indicate they are part of a "primary key",
which is derived from each :class:`.Column` object being a member of a
:class:`.PrimaryKeyConstraint` collection that's associated with the
:class:`.Table` from which they ultimately derive.

In those cases where the selectable being mapped does not include columns
that are explicitly part of the primary key constraint on their parent table,
a user-defined set of primary key columns must be defined.  The
:paramref:`.mapper.primary_key` parameter is used for this purpose.

Given the following example of a :ref:`Imperative Table <orm_imperative_table_configuration>`
mapping against an existing :class:`.Table` object, as would occur in a scenario
such as when the :class:`.Table` were :term:`reflected` from an existing
database, where the table does not have any declared primary key, we may
map such a table as in the following example::

    from sqlalchemy import Column
    from sqlalchemy import MetaData
    from sqlalchemy import String
    from sqlalchemy import Table
    from sqlalchemy import UniqueConstraint
    from sqlalchemy.orm import declarative_base


    metadata = MetaData()
    group_users = Table(
        "group_users",
        metadata,
        Column("user_id", String(40), nullable=False),
        Column("group_id", String(40), nullable=False),
        UniqueConstraint("user_id", "group_id"),
    )


    Base = declarative_base()


    class GroupUsers(Base):
        __table__ = group_users
        __mapper_args__ = {"primary_key": [group_users.c.user_id, group_users.c.group_id]}

Above, the ``group_users`` table is an association table of some kind
with string columns ``user_id`` and ``group_id``, but no primary key is set up;
instead, there is only a :class:`.UniqueConstraint` establishing that the
two columns represent a unique key.   The :class:`.Mapper` does not automatically
inspect unique constraints for primary keys; instead, we make use of the
:paramref:`.mapper.primary_key` parameter, passing a collection of
``[group_users.c.user_id, group_users.c.group_id]``, indicating that these two
columns should be used in order to construct the identity key for instances
of the ``GroupUsers`` class.


.. _include_exclude_cols:

Mapping a Subset of Table Columns
---------------------------------

Sometimes, a :class:`_schema.Table` object was made available using the
reflection process described at :ref:`metadata_reflection` to load
the table's structure from the database.
For such a table that has lots of columns that don't need to be referenced
in the application, the ``include_properties`` or ``exclude_properties``
arguments can specify that only a subset of columns should be mapped.
For example::

    class User(Base):
        __table__ = user_table
        __mapper_args__ = {"include_properties": ["user_id", "user_name"]}

...will map the ``User`` class to the ``user_table`` table, only including
the ``user_id`` and ``user_name`` columns - the rest are not referenced.
Similarly::

    class Address(Base):
        __table__ = address_table
        __mapper_args__ = {"exclude_properties": ["street", "city", "state", "zip"]}

...will map the ``Address`` class to the ``address_table`` table, including
all columns present except ``street``, ``city``, ``state``, and ``zip``.

When this mapping is used, the columns that are not included will not be
referenced in any SELECT statements emitted by :class:`_query.Query`, nor will there
be any mapped attribute on the mapped class which represents the column;
assigning an attribute of that name will have no effect beyond that of
a normal Python attribute assignment.

In some cases, multiple columns may have the same name, such as when
mapping to a join of two or more tables that share some column name.
``include_properties`` and ``exclude_properties`` can also accommodate
:class:`_schema.Column` objects to more accurately describe which columns
should be included or excluded::

    class UserAddress(Base):
        __table__ = user_table.join(addresses_table)
        __mapper_args__ = {
            "exclude_properties": [address_table.c.id],
            "primary_key": [user_table.c.id],
        }

.. note::

    insert and update defaults configured on individual :class:`_schema.Column`
    objects, i.e. those described at :ref:`metadata_defaults` including those
    configured by the :paramref:`_schema.Column.default`,
    :paramref:`_schema.Column.onupdate`, :paramref:`_schema.Column.server_default` and
    :paramref:`_schema.Column.server_onupdate` parameters, will continue to function
    normally even if those :class:`_schema.Column` objects are not mapped. This is
    because in the case of :paramref:`_schema.Column.default` and
    :paramref:`_schema.Column.onupdate`, the :class:`_schema.Column` object is still present
    on the underlying :class:`_schema.Table`, thus allowing the default functions to
    take place when the ORM emits an INSERT or UPDATE, and in the case of
    :paramref:`_schema.Column.server_default` and :paramref:`_schema.Column.server_onupdate`,
    the relational database itself emits these defaults as a server side
    behavior.