File: declarative_config.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 (507 lines) | stat: -rw-r--r-- 17,697 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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
.. _orm_declarative_mapper_config_toplevel:

=============================================
Mapper Configuration with Declarative
=============================================

The section :ref:`orm_mapper_configuration_overview` discusses the general
configurational elements of a :class:`_orm.Mapper` construct, which is the
structure that defines how a particular user defined class is mapped to a
database table or other SQL construct.    The following sections describe
specific details about how the declarative system goes about constructing
the :class:`_orm.Mapper`.

.. _orm_declarative_properties:

Defining Mapped Properties with Declarative
--------------------------------------------

The examples given at :ref:`orm_declarative_table_config_toplevel`
illustrate mappings against table-bound columns;
the mapping of an individual column to an ORM class attribute is represented
internally by the :class:`_orm.ColumnProperty` construct.   There are many
other varieties of mapper properties, the most common being the
:func:`_orm.relationship` construct.  Other kinds of properties include
synonyms to columns which are defined using the :func:`_orm.synonym`
construct, SQL expressions that are defined using the :func:`_orm.column_property`
construct, and deferred columns and SQL expressions which load only when
accessed, defined using the :func:`_orm.deferred` construct.

While an :ref:`imperative mapping <orm_imperative_mapping>` makes use of
the :ref:`properties <orm_mapping_properties>` dictionary to establish
all the mapped class attributes, in the declarative
mapping, these properties are all specified inline with the class definition,
which in the case of a declarative table mapping are inline with the
:class:`_schema.Column` objects that will be used to generate a
:class:`_schema.Table` object.

Working with the example mapping of ``User`` and ``Address``, we may illustrate
a declarative table mapping that includes not just :class:`_schema.Column`
objects but also relationships and SQL expressions::

    # mapping attributes using declarative with declarative table
    # i.e. __tablename__

    from sqlalchemy import Column, ForeignKey, Integer, String, Text
    from sqlalchemy.orm import (
        column_property,
        declarative_base,
        deferred,
        relationship,
    )

    Base = declarative_base()


    class User(Base):
        __tablename__ = "user"

        id = Column(Integer, primary_key=True)
        name = Column(String)
        firstname = Column(String(50))
        lastname = Column(String(50))

        fullname = column_property(firstname + " " + lastname)

        addresses = relationship("Address", back_populates="user")


    class Address(Base):
        __tablename__ = "address"

        id = Column(Integer, primary_key=True)
        user_id = Column(ForeignKey("user.id"))
        email_address = Column(String)
        address_statistics = deferred(Column(Text))

        user = relationship("User", back_populates="addresses")

The above declarative table mapping features two tables, each with a
:func:`_orm.relationship` referring to the other, as well as a simple
SQL expression mapped by :func:`_orm.column_property`, and an additional
:class:`_schema.Column` that will be loaded on a "deferred" basis as defined
by the :func:`_orm.deferred` construct.    More documentation
on these particular concepts may be found at :ref:`relationship_patterns`,
:ref:`mapper_column_property_sql_expressions`, and :ref:`deferred`.

Properties may be specified with a declarative mapping as above using
"hybrid table" style as well; the :class:`_schema.Column` objects that
are directly part of a table move into the :class:`_schema.Table` definition
but everything else, including composed SQL expressions, would still be
inline with the class definition.  Constructs that need to refer to a
:class:`_schema.Column` directly would reference it in terms of the
:class:`_schema.Table` object.  To illustrate the above mapping using
hybrid table style::

    # mapping attributes using declarative with imperative table
    # i.e. __table__

    from sqlalchemy import Column, ForeignKey, Integer, String, Table, Text
    from sqlalchemy.orm import (
        column_property,
        declarative_base,
        deferred,
        relationship,
    )

    Base = declarative_base()


    class User(Base):
        __table__ = Table(
            "user",
            Base.metadata,
            Column("id", Integer, primary_key=True),
            Column("name", String),
            Column("firstname", String(50)),
            Column("lastname", String(50)),
        )

        fullname = column_property(__table__.c.firstname + " " + __table__.c.lastname)

        addresses = relationship("Address", back_populates="user")


    class Address(Base):
        __table__ = Table(
            "address",
            Base.metadata,
            Column("id", Integer, primary_key=True),
            Column("user_id", ForeignKey("user.id")),
            Column("email_address", String),
            Column("address_statistics", Text),
        )

        address_statistics = deferred(__table__.c.address_statistics)

        user = relationship("User", back_populates="addresses")

Things to note above:

* The address :class:`_schema.Table` contains a column called ``address_statistics``,
  however we re-map this column under the same attribute name to be under
  the control of a :func:`_orm.deferred` construct.

* With both declararative table and hybrid table mappings, when we define a
  :class:`_schema.ForeignKey` construct, we always name the target table
  using the **table name**, and not the mapped class name.

* When we define :func:`_orm.relationship` constructs, as these constructs
  create a linkage between two mapped classes where one necessarily is defined
  before the other, we can refer to the remote class using its string name.
  This functionality also extends into the area of other arguments specified
  on the :func:`_orm.relationship` such as the "primary join" and "order by"
  arguments.   See the section :ref:`orm_declarative_relationship_eval` for
  details on this.


.. _orm_declarative_mapper_options:

Mapper Configuration Options with Declarative
----------------------------------------------

With all mapping forms, the mapping of the class is configured through
parameters that become part of the :class:`_orm.Mapper` object.
The function which ultimately receives these arguments is the
:func:`_orm.mapper` function, and are delivered to it from one of
the front-facing mapping functions defined on the :class:`_orm.registry`
object.

For the declarative form of mapping, mapper arguments are specified
using the ``__mapper_args__`` declarative class variable, which is a dictionary
that is passed as keyword arguments to the :func:`_orm.mapper` function.
Some examples:

**Map Specific Primary Key Columns**

The example below illustrates Declarative-level settings for the
:paramref:`_orm.mapper.primary_key` parameter, which establishes
particular columns as part of what the ORM should consider to be a primary
key for the class, independently of schema-level primary key constraints::

    class GroupUsers(Base):
        __tablename__ = "group_users"

        user_id = Column(String(40))
        group_id = Column(String(40))

        __mapper_args__ = {"primary_key": [user_id, group_id]}

.. seealso::

    :ref:`mapper_primary_key` - further background on ORM mapping of explicit
    columns as primary key columns

**Version ID Column**

The example below illustrates Declarative-level settings for the
:paramref:`_orm.mapper.version_id_col` and
:paramref:`_orm.mapper.version_id_generator` parameters, which configure
an ORM-maintained version counter that is updated and checked within the
:term:`unit of work` flush process::

    from datetime import datetime


    class Widget(Base):
        __tablename__ = "widgets"

        id = Column(Integer, primary_key=True)
        timestamp = Column(DateTime, nullable=False)

        __mapper_args__ = {
            "version_id_col": timestamp,
            "version_id_generator": lambda v: datetime.now(),
        }

.. seealso::

    :ref:`mapper_version_counter` - background on the ORM version counter feature

**Single Table Inheritance**

The example below illustrates Declarative-level settings for the
:paramref:`_orm.mapper.polymorphic_on` and
:paramref:`_orm.mapper.polymorphic_identity` parameters, which are used when
configuring a single-table inheritance mapping::

    class Person(Base):
        __tablename__ = "person"

        person_id = Column(Integer, primary_key=True)
        type = Column(String, nullable=False)

        __mapper_args__ = dict(
            polymorphic_on=type,
            polymorphic_identity="person",
        )


    class Employee(Person):
        __mapper_args__ = dict(
            polymorphic_identity="employee",
        )

.. seealso::

    :ref:`single_inheritance` - background on the ORM single table inheritance
    mapping feature.

Constructing mapper arguments dynamically
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``__mapper_args__`` dictionary may be generated from a class-bound
descriptor method rather than from a fixed dictionary by making use of the
:func:`_orm.declared_attr` construct.    This is useful to create arguments
for mappers that are programmatically derived from the table configuration
or other aspects of the mapped class.    A dynamic ``__mapper_args__``
attribute will typically be useful when using a Declarative Mixin or
abstract base class.

For example, to omit from the mapping
any columns that have a special :attr:`.Column.info` value, a mixin
can use a ``__mapper_args__`` method that scans for these columns from the
``cls.__table__`` attribute and passes them to the :paramref:`_orm.mapper.exclude_properties`
collection::

    from sqlalchemy import Column
    from sqlalchemy import Integer
    from sqlalchemy import select
    from sqlalchemy import String
    from sqlalchemy.orm import declarative_base
    from sqlalchemy.orm import declared_attr


    class ExcludeColsWFlag:
        @declared_attr
        def __mapper_args__(cls):
            return {
                "exclude_properties": [
                    column.key
                    for column in cls.__table__.c
                    if column.info.get("exclude", False)
                ]
            }


    Base = declarative_base()


    class SomeClass(ExcludeColsWFlag, Base):
        __tablename__ = "some_table"

        id = Column(Integer, primary_key=True)
        data = Column(String)
        not_needed = Column(String, info={"exclude": True})

Above, the ``ExcludeColsWFlag`` mixin provides a per-class ``__mapper_args__``
hook that will scan for :class:`.Column` objects that include the key/value
``'exclude': True`` passed to the :paramref:`.Column.info` parameter, and then
add their string "key" name to the :paramref:`_orm.mapper.exclude_properties`
collection which will prevent the resulting :class:`.Mapper` from considering
these columns for any SQL operations.

.. seealso::

    :ref:`orm_mixins_toplevel`


Other Declarative Mapping Directives
--------------------------------------

``__declare_last__()``
~~~~~~~~~~~~~~~~~~~~~~

The ``__declare_last__()`` hook allows definition of
a class level function that is automatically called by the
:meth:`.MapperEvents.after_configured` event, which occurs after mappings are
assumed to be completed and the 'configure' step has finished::

    class MyClass(Base):
        @classmethod
        def __declare_last__(cls):
            """ """
            # do something with mappings

``__declare_first__()``
~~~~~~~~~~~~~~~~~~~~~~~

Like ``__declare_last__()``, but is called at the beginning of mapper
configuration via the :meth:`.MapperEvents.before_configured` event::

    class MyClass(Base):
        @classmethod
        def __declare_first__(cls):
            """ """
            # do something before mappings are configured

.. versionadded:: 0.9.3


.. _declarative_metadata:

``metadata``
~~~~~~~~~~~~

The :class:`_schema.MetaData` collection normally used to assign a new
:class:`_schema.Table` is the :attr:`_orm.registry.metadata` attribute
associated with the :class:`_orm.registry` object in use. When using a
declarative base class such as that generated by :func:`_orm.declarative_base`
as well as :meth:`_orm.registry.generate_base`, this :class:`_schema.MetaData`
is also normally present also as an attribute named ``.metadata`` that's
directly on the base class, and thus also on the mapped class via
inheritance.    Declarative uses this attribute, when present, in order to
determine the target :class:`_schema.MetaData` collection, or if not
present, uses the :class:`_schema.MetaData` associated directly with the
:class:`_orm.registry`.

This attribute may also be assigned towards in order to affect the
:class:`_schema.MetaData` collection to be used on a per-mapped-hierarchy basis
for a single base and/or :class:`_orm.registry`. This takes effect whether a
declarative base class is used or if the :meth:`_orm.registry.mapped` decorator
is used directly, thus allowing patterns such as the metadata-per-abstract base
example in the next section, :ref:`declarative_abstract`. A similar pattern can
be illustrated using :meth:`_orm.registry.mapped` as follows::

    reg = registry()


    class BaseOne:
        metadata = MetaData()


    class BaseTwo:
        metadata = MetaData()


    @reg.mapped
    class ClassOne:
        __tablename__ = "t1"  # will use reg.metadata

        id = Column(Integer, primary_key=True)


    @reg.mapped
    class ClassTwo(BaseOne):
        __tablename__ = "t1"  # will use BaseOne.metadata

        id = Column(Integer, primary_key=True)


    @reg.mapped
    class ClassThree(BaseTwo):
        __tablename__ = "t1"  # will use BaseTwo.metadata

        id = Column(Integer, primary_key=True)

.. versionchanged:: 1.4.3  The :meth:`_orm.registry.mapped` decorator will
   honor an attribute named ``.metadata`` on the class as an alternate
   :class:`_schema.MetaData` collection to be used in place of the
   :class:`_schema.MetaData` that's on the :class:`_orm.registry` itself.
   This matches the behavior of the base class returned by the
   :meth:`_orm.registry.generate_base` and :meth:`_orm.declarative_base`
   method/function.  Note this feature was broken due to a regression in
   1.4.0, 1.4.1 and 1.4.2, even when using :func:`_orm.declarative_base`;
   1.4.3 is needed to restore the behavior.


.. seealso::

    :ref:`declarative_abstract`

.. _declarative_abstract:

``__abstract__``
~~~~~~~~~~~~~~~~

``__abstract__`` causes declarative to skip the production
of a table or mapper for the class entirely.  A class can be added within a
hierarchy in the same way as mixin (see :ref:`declarative_mixins`), allowing
subclasses to extend just from the special class::

    class SomeAbstractBase(Base):
        __abstract__ = True

        def some_helpful_method(self):
            """ """

        @declared_attr
        def __mapper_args__(cls):
            return {"helpful mapper arguments": True}


    class MyMappedClass(SomeAbstractBase):
        pass

One possible use of ``__abstract__`` is to use a distinct
:class:`_schema.MetaData` for different bases::

    Base = declarative_base()


    class DefaultBase(Base):
        __abstract__ = True
        metadata = MetaData()


    class OtherBase(Base):
        __abstract__ = True
        metadata = MetaData()

Above, classes which inherit from ``DefaultBase`` will use one
:class:`_schema.MetaData` as the registry of tables, and those which inherit from
``OtherBase`` will use a different one. The tables themselves can then be
created perhaps within distinct databases::

    DefaultBase.metadata.create_all(some_engine)
    OtherBase.metadata.create_all(some_other_engine)

``__table_cls__``
~~~~~~~~~~~~~~~~~

Allows the callable / class used to generate a :class:`_schema.Table` to be customized.
This is a very open-ended hook that can allow special customizations
to a :class:`_schema.Table` that one generates here::

    class MyMixin(object):
        @classmethod
        def __table_cls__(cls, name, metadata_obj, *arg, **kw):
            return Table(f"my_{name}", metadata_obj, *arg, **kw)

The above mixin would cause all :class:`_schema.Table` objects generated to include
the prefix ``"my_"``, followed by the name normally specified using the
``__tablename__`` attribute.

``__table_cls__`` also supports the case of returning ``None``, which
causes the class to be considered as single-table inheritance vs. its subclass.
This may be useful in some customization schemes to determine that single-table
inheritance should take place based on the arguments for the table itself,
such as, define as single-inheritance if there is no primary key present::

    class AutoTable(object):
        @declared_attr
        def __tablename__(cls):
            return cls.__name__

        @classmethod
        def __table_cls__(cls, *arg, **kw):
            for obj in arg[1:]:
                if (isinstance(obj, Column) and obj.primary_key) or isinstance(
                    obj, PrimaryKeyConstraint
                ):
                    return Table(*arg, **kw)

            return None


    class Person(AutoTable, Base):
        id = Column(Integer, primary_key=True)


    class Employee(Person):
        employee_name = Column(String)

The above ``Employee`` class would be mapped as single-table inheritance
against ``Person``; the ``employee_name`` column would be added as a member
of the ``Person`` table.