> I've added an example for you as well as Jason who asked almost the same 
> question earlier, which illustrates the TypeDecorator in conjunction with an 
> attribute listener that is applied to all occurrences of the target type, an 
> approach also used by the mutable attributes extension, and which we may look 
> into adding as more of a "built in" feature in the future although the recipe 
> should be straightforward.
>
> http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ValidateAllOccurrenc...

Thanks!!  I would *never* have associated Jason's question with mine,
but no matter... the end result is perfect.

I had actually gotten it completely working with my messy
__sa_validators__ hackery (and a heck of a lot of questionable
introspection), but I now have it working with the event system as you
have suggested.  It is MUCH cleaner and I like it a lot.

Here is my final implementation:
http://pastebin.com/33Zkfz1h

The only thing I'm still unsure of in the code is why mapper.columns
is a collection and it required checking columns[0], but I can either
just live with that or look into it later.

Also - prior to your suggestion I was still on SQLAlchemy 0.6.6 and
this prompted me to make the leap to 0.7.1 ... all code now working
fine after that transition with only minor hiccups.

That was an excellent introduction to the new event system as well...
thanks again!

Russ

--
Code is reproduced below as well, in case the pastebin ever fails:

from pytz import UTC
import sqlalchemy as sa
import sqlalchemy.orm as orm
import globalenv

class UTCEnforcedDateTime(sa.types.TypeDecorator):
    """DateTime type that ensures datetime objects are offset-aware
UTC."""
    impl = sa.types.DateTime

    def process_bind_param(self, value, engine):
        if (value is not None) and (value.tzinfo != UTC):
            raise Exception("Data MUST be offset-aware UTC!")
        return value

    def process_result_value(self, value, engine):
        if value is not None:
            return value.replace(tzinfo = UTC)
        return value

def _EnsureUTC(target, value, oldvalue, initiator):
    """'Set' Event handler for all UTCEnforcedDateTime columns.

    This handler simply ensures that the provided 'value' is an offset-
aware
    UTC datetime.

    SQLAlchemy validator (for @validates) for use with
UTCEnforcedDateTime.

    Use of this validator will convert times to UTC on assignment (so
that
    the UTCEnforcedDateTime implementation doesn't throw an exception
on
    commit).

    """
    dt = value
    if dt == None:
        return dt
    if dt.tzinfo == UTC:
        return dt
    tz = globalenv.LocalTZ  #pytz timezone that naive datetimes are in
    #Convert naive time to local time...
    # - normalize is needed to deal with DST funkiness
    dt_tz = tz.normalize(tz.localize(dt))
    return dt_tz.astimezone(UTC)

@sa.event.listens_for(orm.mapper, "mapper_configured")
def _Configure_UTCEnforcedDateTime_Setter(mapper, class_):
    """A mapper-configured listener that is triggered every time an
ORM_ class
    mapper is registered (once per class).

    This event handler makes sure that any defined UTCEnforcedDateTime
are
    always receiving data with properly determined UTC offset-aware
values
    (with the use of the _EnsureUTC handler).

    Adapted from sample code here:
    
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ValidateAllOccurrencesOfType

    """
    for prop in mapper.iterate_properties:
        if hasattr(prop, 'columns'):  #it is a column (not a relation)
            if isinstance(prop.columns[0].type, UTCEnforcedDateTime):
                #Set up a listener for datetime setting events...
                classAttr = getattr(class_, prop.key) #the attr of the
mapped class
                sa.event.listen(
                    classAttr,     #We want to listen for when
classAttr...
                    "set",         #has a set event (like a property
setter)...
                    _EnsureUTC,    #and use _EnsureUTC as the
handler...
                    retval = True) #and allow _EnsureUTC to change the
attr with it's return




-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to