From: Mike Bayer Date: Thu, 6 Sep 2018 14:44:09 +0000 (-0400) Subject: Clarify init_scalar event use case X-Git-Tag: rel_1_3_0b1~84 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71b01adc7b31baab7bbcf40123633b87ee53bf64;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Clarify init_scalar event use case Since I didn't even realize what this was for when reading the docs, make it clearer that this is to mirror a Column default and remove the extra verbiage about the mechanics of INSERTs. Change-Id: Id2c6a29800f7b723573610e4707aec7e6ea38f5f --- diff --git a/examples/custom_attributes/active_column_defaults.py b/examples/custom_attributes/active_column_defaults.py index e24fcc0697..dd823e814e 100644 --- a/examples/custom_attributes/active_column_defaults.py +++ b/examples/custom_attributes/active_column_defaults.py @@ -90,6 +90,11 @@ if __name__ == '__main__': # not persisted at all, default values are present the moment # we access them assert w1.radius == 30 + + # this line will invoke the datetime.now() function, and establish + # its return value upon the w1 instance, such that the + # Column-level default for the "timestamp" column will no longer fire + # off. current_time = w1.timestamp assert ( current_time > datetime.datetime.now() - datetime.timedelta(seconds=5) diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index cdd7621124..f27849e153 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -2036,19 +2036,39 @@ class AttributeEvents(event.Events): """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. + attribute is accessed, e.g. read:: - 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:: + x = my_object.some_attribute + + The ORM's default behavior when this occurs for an un-initialized + attribute is to return the value ``None``; note this differs from + Python's usual behavior of raising ``AttributeError``. The + event here can be used to customize what value is actually returned, + with the assumption that the event listener would be mirroring + a default generator that is configured on the Core :class:`.Column` + object as well. + + Since a default generator on a :class:`.Column` might also produce + a changing value such as a timestamp, the + :meth:`.AttributeEvents.init_scalar` + event handler can also be used to **set** the newly returned value, so + that a Core-level default generation function effecively fires off + only once, but at the moment the attribute is accessed on the + non-persisted object. Normally, no change to the object's state + is made when an uninitialized attribute is accessed (much older + SQLAlchemy versions did in fact change the object's state). + + If a default generator on a column returned a particular constant, + a handler might be used as follows:: SOME_CONSTANT = 3.1415926 + class MyClass(Base): + # ... + + some_attribute = Column(Numeric, default=SOME_CONSTANT) + @event.listens_for( MyClass.some_attribute, "init_scalar", retval=True, propagate=True) @@ -2061,11 +2081,14 @@ class AttributeEvents(event.Events): 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. + we indicate that this value is to be persisted to the database. + This supersedes the use of ``SOME_CONSTANT`` in the default generator + for the :class:`.Column`. The ``active_column_defaults.py`` + example given at :ref:`examples_instrumentation` illustrates using + the same approach for a changing default, e.g. a timestamp + generator. In this particular example, it is not strictly + necessary to do this since ``SOME_CONSTANT`` would be part of the + INSERT statement in either case. * By establishing the ``retval=True`` flag, the value we return from the function will be returned by the attribute getter. @@ -2077,24 +2100,12 @@ class AttributeEvents(event.Events): 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:: + In the above example, 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 @@ -2111,11 +2122,6 @@ class AttributeEvents(event.Events): 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.