diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-09-02 17:57:35 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-09-02 17:57:35 +0000 |
| commit | 3e25e6e6b05c39b15deda65921d411ec8cb341ae (patch) | |
| tree | 1b148b1597c6c63c5d2b987acac45032e8388edf /examples/custom_attributes | |
| parent | d578d67035c519d4205e757c926392896e6a57e7 (diff) | |
| download | sqlalchemy-3e25e6e6b05c39b15deda65921d411ec8cb341ae.tar.gz | |
- AttributeListener has been refined such that the event
is fired before the mutation actually occurs. Addtionally,
the append() and set() methods must now return the given value,
which is used as the value to be used in the mutation operation.
This allows creation of validating AttributeListeners which
raise before the action actually occurs, and which can change
the given value into something else before its used.
A new example "validate_attributes.py" shows one such recipe
for doing this. AttributeListener helper functions are
also on the way.
Diffstat (limited to 'examples/custom_attributes')
| -rw-r--r-- | examples/custom_attributes/listen_for_events.py | 6 | ||||
| -rw-r--r-- | examples/custom_attributes/validate_attributes.py | 117 |
2 files changed, 121 insertions, 2 deletions
diff --git a/examples/custom_attributes/listen_for_events.py b/examples/custom_attributes/listen_for_events.py index 71f8bbade..e980e61ed 100644 --- a/examples/custom_attributes/listen_for_events.py +++ b/examples/custom_attributes/listen_for_events.py @@ -7,9 +7,9 @@ from sqlalchemy.orm.interfaces import AttributeExtension, InstrumentationManager class InstallListeners(InstrumentationManager): def instrument_attribute(self, class_, key, inst): - """Add an event listener to all InstrumentedAttributes.""" + """Add an event listener to an InstrumentedAttribute.""" - inst.impl.extensions.append(AttributeListener(key)) + inst.impl.extensions.insert(0, AttributeListener(key)) return super(InstallListeners, self).instrument_attribute(class_, key, inst) class AttributeListener(AttributeExtension): @@ -25,12 +25,14 @@ class AttributeListener(AttributeExtension): def append(self, state, value, initiator): self._report(state, value, None, "appended") + return value def remove(self, state, value, initiator): self._report(state, value, None, "removed") def set(self, state, value, oldvalue, initiator): self._report(state, value, oldvalue, "set") + return value def _report(self, state, value, oldvalue, verb): state.obj().receive_change_event(verb, self.key, value, oldvalue) diff --git a/examples/custom_attributes/validate_attributes.py b/examples/custom_attributes/validate_attributes.py new file mode 100644 index 000000000..63b2529fd --- /dev/null +++ b/examples/custom_attributes/validate_attributes.py @@ -0,0 +1,117 @@ +""" +Illustrates how to use AttributeExtension to create attribute validators. + +""" + +from sqlalchemy.orm.interfaces import AttributeExtension, InstrumentationManager + +class InstallValidators(InstrumentationManager): + """Searches a class for methods with a '_validates' attribute and assembles Validators.""" + + def __init__(self, cls): + self.validators = {} + for k in dir(cls): + item = getattr(cls, k) + if hasattr(item, '_validates'): + self.validators[item._validates] = item + + def instrument_attribute(self, class_, key, inst): + """Add an event listener to an InstrumentedAttribute.""" + + if key in self.validators: + inst.impl.extensions.insert(0, Validator(key, self.validators[key])) + return super(InstallValidators, self).instrument_attribute(class_, key, inst) + +class Validator(AttributeExtension): + """Validates an attribute, given the key and a validation function.""" + + def __init__(self, key, validator): + self.key = key + self.validator = validator + + def append(self, state, value, initiator): + return self.validator(state.obj(), value) + + def set(self, state, value, oldvalue, initiator): + return self.validator(state.obj(), value) + +def validates(key): + """Mark a method as validating a named attribute.""" + + def wrap(fn): + fn._validates = key + return fn + return wrap + +if __name__ == '__main__': + + from sqlalchemy import * + from sqlalchemy.orm import * + from sqlalchemy.ext.declarative import declarative_base + import datetime + + Base = declarative_base(engine=create_engine('sqlite://', echo=True)) + Base.__sa_instrumentation_manager__ = InstallValidators + + class MyMappedClass(Base): + __tablename__ = "mytable" + + id = Column(Integer, primary_key=True) + date = Column(Date) + related_id = Column(Integer, ForeignKey("related.id")) + related = relation("Related", backref="mapped") + + @validates('date') + def check_date(self, value): + if isinstance(value, str): + m, d, y = [int(x) for x in value.split('/')] + return datetime.date(y, m, d) + else: + assert isinstance(value, datetime.date) + return value + + @validates('related') + def check_related(self, value): + assert value.data == 'r1' + return value + + def __str__(self): + return "MyMappedClass(date=%r)" % self.date + + class Related(Base): + __tablename__ = "related" + + id = Column(Integer, primary_key=True) + data = Column(String(50)) + + def __str__(self): + return "Related(data=%r)" % self.data + + Base.metadata.create_all() + session = sessionmaker()() + + r1 = Related(data='r1') + r2 = Related(data='r2') + m1 = MyMappedClass(date='5/2/2005', related=r1) + m2 = MyMappedClass(date=datetime.date(2008, 10, 15)) + r1.mapped.append(m2) + + try: + m1.date = "this is not a date" + except: + pass + assert m1.date == datetime.date(2005, 5, 2) + + try: + m2.related = r2 + except: + pass + assert m2.related is r1 + + session.add(m1) + session.commit() + assert session.query(MyMappedClass.date).order_by(MyMappedClass.date).all() == [ + (datetime.date(2005, 5, 2),), + (datetime.date(2008, 10, 15),) + ] +
\ No newline at end of file |
