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
|
.. module:: sqlalchemy.orm
Mapping Table Columns
=====================
The default behavior of :func:`~.orm.mapper` is to assemble all the columns in
the mapped :class:`.Table` into mapped object attributes, each of which are
named according to the name of the column itself (specifically, the ``key``
attribute of :class:`.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:`.Column` as that of the mapped attribute - specifically
it matches the :attr:`.Column.key` attribute on :class:`.Column`, which
by default is the same as the :attr:`.Column.name`.
The name assigned to the Python attribute which maps to
:class:`.Column` can be different from either :attr:`.Column.name` or :attr:`.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:`.Column` object
can be referenced directly::
class User(Base):
__table__ = user_table
id = user_table.c.user_id
name = user_table.c.user_name
Or in a classical mapping, placed in the ``properties`` dictionary
with the desired key::
mapper(User, user_table, properties={
'id': user_table.c.user_id,
'name': user_table.c.user_name,
})
In the next section we'll examine the usage of ``.key`` more closely.
.. _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:`.Column` explicitly mapped to a class can have a different attribute
name than the column. But what if we aren't listing out :class:`.Column`
objects explicitly, and instead are automating the production of :class:`.Table`
objects using reflection (e.g. as described in :ref:`metadata_reflection_toplevel`)?
In this case we can make use of the :meth:`.DDLEvents.column_reflect` event
to intercept the production of :class:`.Column` objects and provide them
with the :attr:`.Column.key` of our choice::
@event.listens_for(Table, "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:`.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=True, autoload_with=some_engine)
If we want to qualify our event to only react for the specific :class:`.MetaData`
object above, we can check for it in our event::
@event.listens_for(Table, "column_reflect")
def column_reflect(inspector, table, column_info):
if table.metadata is Base.metadata:
# set column.key = "attr_<lower_case_name>"
column_info['key'] = "attr_%s" % column_info['name'].lower()
.. _column_prefix:
Naming All Columns with a Prefix
--------------------------------
A quick approach to prefix column names, typically when mapping
to an existing :class:`.Table` object, is to use ``column_prefix``::
class User(Base):
__table__ = user_table
__mapper_args__ = {'column_prefix':'_'}
The above will place attribute names such as ``_user_id``, ``_user_name``,
``_password`` etc. on the mapped ``User`` class.
This approach is uncommon in modern usage. For dealing with reflected
tables, a more flexible approach is to use that described in
:ref:`mapper_automated_reflection_schemes`.
Using column_property for column level options
-----------------------------------------------
Options can be specified when mapping a :class:`.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:`.Column`; normally, the
:func:`.mapper` creates this automatically. Using :func:`.column_property`,
we can pass additional arguments about how we'd like the :class:`.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
.. _include_exclude_cols:
Mapping a Subset of Table Columns
---------------------------------
Sometimes, a :class:`.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`, 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:`.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:`.Column` objects, i.e. those described at :ref:`metadata_defaults`
including those configured by the ``default``, ``update``,
``server_default`` and ``server_onupdate`` arguments, will continue to
function normally even if those :class:`.Column` objects are not mapped.
This is because in the case of ``default`` and ``update``, the
:class:`.Column` object is still present on the underlying
:class:`.Table`, thus allowing the default functions to take place when
the ORM emits an INSERT or UPDATE, and in the case of ``server_default``
and ``server_onupdate``, the relational database itself maintains these
functions.
|