diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-06-10 23:38:15 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2016-06-02 14:41:31 -0400 |
| commit | e28b44813721f258bf76fb7c85bf5559ecd219ec (patch) | |
| tree | 7929c90ba8d19b75ca9b33a04e8b65254ccb0899 /examples | |
| parent | 31699bd1866bbfc36f1501e5e1b54d3c06cf3b4c (diff) | |
| download | sqlalchemy-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__.py | 2 | ||||
| -rw-r--r-- | examples/custom_attributes/active_column_defaults.py | 108 |
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) + ) |
