summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-06-10 23:38:15 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2016-06-02 14:41:31 -0400
commite28b44813721f258bf76fb7c85bf5559ecd219ec (patch)
tree7929c90ba8d19b75ca9b33a04e8b65254ccb0899 /lib/sqlalchemy/orm
parent31699bd1866bbfc36f1501e5e1b54d3c06cf3b4c (diff)
downloadsqlalchemy-e28b44813721f258bf76fb7c85bf5559ecd219ec.tar.gz
Add an init_scalar event for attributes
This allows us to build default-setting recipes such as one that allows us to actively read column-level defaults. An example suite is also added. Change-Id: I7b022d52cc89526132d5bc4201ac27fea4cf088d Fixes: #1311
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/attributes.py11
-rw-r--r--lib/sqlalchemy/orm/events.py108
2 files changed, 114 insertions, 5 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 017ad0300..7239d41f2 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -551,12 +551,13 @@ class AttributeImpl(object):
def initialize(self, state, dict_):
"""Initialize the given state's attribute with an empty value."""
- # As of 1.0, we don't actually set a value in
- # dict_. This is so that the state of the object does not get
- # modified without emitting the appropriate events.
+ value = None
+ for fn in self.dispatch.init_scalar:
+ ret = fn(state, value, dict_)
+ if ret is not ATTR_EMPTY:
+ value = ret
-
- return None
+ return value
def get(self, state, dict_, passive=PASSIVE_OFF):
"""Retrieve a value from the given object.
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index d05fdc9cb..fa0d84f31 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -1934,6 +1934,114 @@ class AttributeEvents(event.Events):
"""
+ def init_scalar(self, target, value, dict_):
+ """Receive a scalar "init" event.
+
+ This event is invoked when an uninitialized, unpersisted scalar
+ attribute is accessed. A value of ``None`` is typically returned
+ in this case; no changes are made to the object's state.
+
+ The event handler can alter this behavior in two ways.
+ One is that a value other than ``None`` may be returned. The other
+ is that the value may be established as part of the object's state,
+ which will also have the effect that it is persisted.
+
+ Typical use is to establish a specific default value of an attribute
+ upon access::
+
+ SOME_CONSTANT = 3.1415926
+
+ @event.listens_for(
+ MyClass.some_attribute, "init_scalar",
+ retval=True, propagate=True)
+ def _init_some_attribute(target, dict_, value):
+ dict_['some_attribute'] = SOME_CONSTANT
+ return SOME_CONSTANT
+
+ Above, we initialize the attribute ``MyClass.some_attribute`` to the
+ value of ``SOME_CONSTANT``. The above code includes the following
+ features:
+
+ * By setting the value ``SOME_CONSTANT`` in the given ``dict_``,
+ we indicate that the value is to be persisted to the database.
+ **The given value is only persisted to the database if we
+ explicitly associate it with the object**. The ``dict_`` given
+ is the ``__dict__`` element of the mapped object, assuming the
+ default attribute instrumentation system is in place.
+
+ * By establishing the ``retval=True`` flag, the value we return
+ from the function will be returned by the attribute getter.
+ Without this flag, the event is assumed to be a passive observer
+ and the return value of our function is ignored.
+
+ * The ``propagate=True`` flag is significant if the mapped class
+ includes inheriting subclasses, which would also make use of this
+ event listener. Without this flag, an inheriting subclass will
+ not use our event handler.
+
+ When we establish the value in the given dictionary, the value will
+ be used in the INSERT statement established by the unit of work.
+ Normally, the default returned value of ``None`` is not established as
+ part of the object, to avoid the issue of mutations occurring to the
+ object in response to a normally passive "get" operation, and also
+ sidesteps the issue of whether or not the :meth:`.AttributeEvents.set`
+ event should be awkwardly fired off during an attribute access
+ operation. This does not impact the INSERT operation since the
+ ``None`` value matches the value of ``NULL`` that goes into the
+ database in any case; note that ``None`` is skipped during the INSERT
+ to ensure that column and SQL-level default functions can fire off.
+
+ The attribute set event :meth:`.AttributeEvents.set` as well as the
+ related validation feature provided by :obj:`.orm.validates` is
+ **not** invoked when we apply our value to the given ``dict_``. To
+ have these events to invoke in response to our newly generated
+ value, apply the value to the given object as a normal attribute
+ set operation::
+
+ SOME_CONSTANT = 3.1415926
+
+ @event.listens_for(
+ MyClass.some_attribute, "init_scalar",
+ retval=True, propagate=True)
+ def _init_some_attribute(target, dict_, value):
+ # will also fire off attribute set events
+ target.some_attribute = SOME_CONSTANT
+ return SOME_CONSTANT
+
+ When multiple listeners are set up, the generation of the value
+ is "chained" from one listener to the next by passing the value
+ returned by the previous listener that specifies ``retval=True``
+ as the ``value`` argument of the next listener.
+
+ The :meth:`.AttributeEvents.init_scalar` event may be used to
+ extract values from the default values and/or callables established on
+ mapped :class:`.Column` objects. See the "active column defaults"
+ example in :ref:`examples_instrumentation` for an example of this.
+
+ .. versionadded:: 1.1
+
+ :param target: the object instance receiving the event.
+ If the listener is registered with ``raw=True``, this will
+ be the :class:`.InstanceState` object.
+ :param value: the value that is to be returned before this event
+ listener were invoked. This value begins as the value ``None``,
+ however will be the return value of the previous event handler
+ function if multiple listeners are present.
+ :param dict_: the attribute dictionary of this mapped object.
+ This is normally the ``__dict__`` of the object, but in all cases
+ represents the destination that the attribute system uses to get
+ at the actual value of this attribute. Placing the value in this
+ dictionary has the effect that the value will be used in the
+ INSERT statement generated by the unit of work.
+
+
+ .. seealso::
+
+ :ref:`examples_instrumentation` - see the
+ ``active_column_defaults.py`` example.
+
+ """
+
def init_collection(self, target, collection, collection_adapter):
"""Receive a 'collection init' event.