mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-27 02:52:53 -04:00
1e1a38e780
This is a straight reformat run using black as is, with no edits applied at all. The black run will format code consistently, however in some cases that are prevalent in SQLAlchemy code it produces too-long lines. The too-long lines will be resolved in the following commit that will resolve all remaining flake8 issues including shadowed builtins, long lines, import order, unused imports, duplicate imports, and docstring issues. Change-Id: I7eda77fed3d8e73df84b3651fd6cfcfe858d4dc9
116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""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
|
|
|
|
# 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
|
|
)
|
|
|
|
# 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,
|
|
)
|