I currently define a custom column type for ensuring that dates stored to 
the DB are offset-aware UTC, and convert to to the appropriate timezone on 
retrieval, using a class something like this:

import pytz
import sqlalchemy as sa

class UTCEnforcedDateTime(sa.types.TypeDecorator):
    """DateTime type that ensures that stored datetime objects are
    offset-aware UTC. Values retrieved from the database are converted to 
the
    local_tz (if class var is set).
    
    """
    impl = sa.types.DateTime
    local_tz = None  #eg: pytz.timezone("EST")
    
    def process_bind_param(self, value, engine):
        if (value is not None) and (value.tzinfo != UTC):
            raise ValueError("only offset-aware UTC datetimes are allowed.")
        return value

    def process_result_value(self, value, engine):
        if value and self.local_tz:
            #Make the DB value UTC right away (because it is!)...
            utcDT = UTC.localize(value)
            value = utcDT.astimezone(self.local_tz)
        return value
###

This has worked extremely well to date, but I'm now finding that the 
process_result_value way of doing things is (relatively) slow for large 
numbers of records because the conversion happens in python after the data 
is retrieved.  ie: process_result_value is called for each appropriate 
value in the resulting records.  For example, a profile run showing 600 ms 
total SQLAlchemy query time, with ~500 ms spent in process_result_value 
doing tz conversions (!!).

I'm trying to figure out how to change this so that, rather than converting 
in python, the conversion happens at the database layer using AT TIME ZONE 
syntax.  I've tried using @compiles as follows:

#THIS CODE DOES NOT WORK...
@compiles(UTCEnforcedDateTime)
def compile_UTCEnforcedDateTime(element, compiler, **kwargs):
    tz = UTCEnforcedDateTime.local_tz
    if tz:
        ret = "%s AT TIMEZONE '%s'" % (element.name, str(tz))
    else:
        ret = compiler.process(element) #NOT RIGHT - what is?
    return ret

However, this is not right for a few reasons...

The first problem is that the special compilation is executed when the 
table is created that has this column type.  I only want the special 
compilation when the compilation is for rendering a query, but I can't 
figure out how to determine this.  The docs <http://goo.gl/N4igi> state 
that teh compiler argument "can be inspected for any information about the 
in-progress compilation, including compiler.dialect, compiler.statement", 
but I can't figure it out.

The second problem is that I'm not certain how to pass through to the 
default compilation that would have been done without my interception.  Is 
there a generic passthrough?  compiler.visit_type_decorator seems 
promising, but I really want "carry_on_like_I_never_intercepted_this()". 
 Or should I call visit_datetime?

How do I fix this?

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/AZ0K4D5PaFoJ.
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