在SQLAlchemy中,有没有一种方法可以象征性地创建实际上创建两个列的列?

时间:2020-08-13 11:47:51

标签: python sqlalchemy

考虑以下代码:

class Appointment(Base):
    scheduled_date_utc = Column(DateTime)               # Naive UTC 
    scheduled_date_timezone = Column(TimezoneType())    # TimezoneType is from sqlalchemy-utils

    @property
    def scheduled_date(self) -> datetime:
        ... (assembles scheduled_date_utc and
             scheduled_date_timezone into a unified object)

    @scheduled_date.setter
    def scheduled_date(self, value: datetime):
        ... (splits up tz-aware datetime into naive UTC time,
             and timezone column, and sets them separately)

不必太担心属性方法,但要了解它们采用一个Python值,然后必须将该Python值分成两个数据库列。

自然,我更愿意创建自己的列类型:

class Appointment(Base):
    scheduled_date = Column(MyDatetimeAware())

问题在于scheduled_date不仅是一列,还需要是多列。有没有办法我可以在SQLAlchemy中概括多列“数据类型”?

2 个答案:

答案 0 :(得分:0)

您正在考虑hybrid properties(请参阅docs)。这些可用于在SQL和python设置中表现出不同的行为,但也可用于预定义某些转换。我经常使用它们将UTC时间戳转换为本地时区。请注意,您定义属性1-3次。一次作为python属性,一次用于表示SQL的运行方式,另一次用于设置程序。

import pytz
from sqlalchemy.ext.hybrid import hybrid_property

class Appointment(Base):
    scheduled_date_utc = Column(DateTime)               # Naive UTC 
    scheduled_date_timezone = Column(TimezoneType())    # TimezoneType is from sqlalchemy-utils

    @property
    def scheduled_date(self) -> datetime:
        # see https://stackoverflow.com/a/18646797/5015356
        return self.scheduled_date_utc\
          .replace(tzinfo=pytz.utc)\
          .astimezone(pytz.timezone(self.scheduled_date_timezone))

    @scheduled_date.expr
    def scheduled_date(cls):
        return func.timezone(cls.scheduled_date_timezone, cls.scheduled_date_utc)

要使该解决方案可重用,可以在__setattr__周围用包装器编写一个mixin:

import pytz

class TimeZoneMixin:
    def is_timezone_aware_attr(self, attr):
        return hasattr(self, attr + '_utc') and hasattr(self, attr + '_timezone')

    def __getattr__(self, attr):
        """
        __getattr__ is only called as a last resort, if no other
        matching columns exist
        """
        if self.is_timezone_aware_attr(attr):
            return func.timezone(getattr(self, attr + '_utc'),
                                 getattr(self, attr + '_timezone')) 
        raise AttributeError()

    def __setattr__(self, attr, value):
        if self.is_timezone_aware_attr(attr):
            setattr(self, attr + '_utc', value.astimezone(tzinfo=pytz.utc))
            setattr(self, attr + '_utc', value.tzinfo)
        raise AttributeError()

或使其仅使用一个共享的timezone对象:

import pytz

class TimeZoneMixin:
    timezone = Column(TimezoneType())

    def is_timezone_aware_attr(self, attr):
        return hasattr(self, attr + '_utc')

    def __getattr__(self, attr):
        """
        __getattr__ is only called as a last resort, if no other
        matching columns exist
        """
        if self.is_timezone_aware_attr(attr):
            return func.timezone(getattr(self, attr + '_utc'), self.timezone) 
        raise AttributeError()

    def __setattr__(self, attr, value):
        if self.is_timezone_aware_attr(attr):
            setattr(self, attr + '_utc', value.astimezone(tzinfo=pytz.utc))
            self.timezone = value.tzinfo
        raise AttributeError()

答案 1 :(得分:0)

在特定于时区的日期时间列中,可以将TIMESTAMP列类型与受支持的后端一起使用。通常,如果要使用实际代表多个列的字段,则可以使用composite column type。但是,这确实需要您分别指定列。