summaryrefslogtreecommitdiff
path: root/examples
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 /examples
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 'examples')
-rw-r--r--examples/custom_attributes/__init__.py2
-rw-r--r--examples/custom_attributes/active_column_defaults.py108
2 files changed, 109 insertions, 1 deletions
diff --git a/examples/custom_attributes/__init__.py b/examples/custom_attributes/__init__.py
index 2072c051f..cbc65dfed 100644
--- a/examples/custom_attributes/__init__.py
+++ b/examples/custom_attributes/__init__.py
@@ -1,5 +1,5 @@
"""
-Two examples illustrating modifications to SQLAlchemy's attribute management
+Examples illustrating modifications to SQLAlchemy's attribute management
system.
.. autosource::
diff --git a/examples/custom_attributes/active_column_defaults.py b/examples/custom_attributes/active_column_defaults.py
new file mode 100644
index 000000000..e24fcc069
--- /dev/null
+++ b/examples/custom_attributes/active_column_defaults.py
@@ -0,0 +1,108 @@
+"""Illustrates use of the :meth:`.AttributeEvents.init_scalar`
+event, in conjunction with Core column defaults to provide
+ORM objects that automatically produce the default value
+when an un-set attribute is accessed.
+
+"""
+
+from sqlalchemy import event
+
+
+def configure_listener(mapper, class_):
+ """Establish attribute setters for every default-holding column on the
+ given mapper."""
+
+ # iterate through ColumnProperty objects
+ for col_attr in mapper.column_attrs:
+
+ # look at the Column mapped by the ColumnProperty
+ # (we look at the first column in the less common case
+ # of a property mapped to multiple columns at once)
+ column = col_attr.columns[0]
+
+ # if the Column has a "default", set up a listener
+ if column.default is not None:
+ default_listener(col_attr, column.default)
+
+
+def default_listener(col_attr, default):
+ """Establish a default-setting listener.
+
+ Given a class_, attrname, and a :class:`.DefaultGenerator` instance.
+ The default generator should be a :class:`.ColumnDefault` object with a
+ plain Python value or callable default; otherwise, the appropriate behavior
+ for SQL functions and defaults should be determined here by the
+ user integrating this feature.
+
+ """
+ @event.listens_for(col_attr, "init_scalar", retval=True, propagate=True)
+ def init_scalar(target, value, dict_):
+
+ if default.is_callable:
+ # the callable of ColumnDefault always accepts a context
+ # argument; we can pass it as None here.
+ value = default.arg(None)
+ elif default.is_scalar:
+ value = default.arg
+ else:
+ # default is a Sequence, a SQL expression, server
+ # side default generator, or other non-Python-evaluable
+ # object. The feature here can't easily support this. This
+ # can be made to return None, rather than raising,
+ # or can procure a connection from an Engine
+ # or Session and actually run the SQL, if desired.
+ raise NotImplementedError(
+ "Can't invoke pre-default for a SQL-level column default")
+
+ # set the value in the given dict_; this won't emit any further
+ # attribute set events or create attribute "history", but the value
+ # will be used in the INSERT statement
+ dict_[col_attr.key] = value
+
+ # return the value as well
+ return value
+
+
+if __name__ == '__main__':
+
+ from sqlalchemy import Column, Integer, DateTime, create_engine
+ from sqlalchemy.orm import Session
+ from sqlalchemy.ext.declarative import declarative_base
+ import datetime
+
+ Base = declarative_base()
+
+ event.listen(Base, 'mapper_configured', configure_listener, propagate=True)
+
+ class Widget(Base):
+ __tablename__ = 'widget'
+
+ id = Column(Integer, primary_key=True)
+
+ radius = Column(Integer, default=30)
+ timestamp = Column(DateTime, default=datetime.datetime.now)
+
+ e = create_engine("sqlite://", echo=True)
+ Base.metadata.create_all(e)
+
+ w1 = Widget()
+
+ # not persisted at all, default values are present the moment
+ # we access them
+ assert w1.radius == 30
+ current_time = w1.timestamp
+ assert (
+ current_time > datetime.datetime.now() - datetime.timedelta(seconds=5)
+ )
+
+ # persist
+ sess = Session(e)
+ sess.add(w1)
+ sess.commit()
+
+ # data is persisted. The timestamp is also the one we generated above;
+ # e.g. the default wasn't re-invoked later.
+ assert (
+ sess.query(Widget.radius, Widget.timestamp).first() ==
+ (30, current_time)
+ )