Add support for a timezone type.
This commit is contained in:
3
setup.py
3
setup.py
@@ -42,7 +42,8 @@ extras_require = {
|
||||
],
|
||||
'password': ['passlib >= 1.6, < 2.0'],
|
||||
'color': ['colour>=0.0.4'],
|
||||
'ipaddress': ['ipaddr'] if not PY3 else []
|
||||
'ipaddress': ['ipaddr'] if not PY3 else [],
|
||||
'timezone': ['python-dateutil']
|
||||
}
|
||||
|
||||
|
||||
|
74
sqlalchemy_utils/types/timezone.py
Normal file
74
sqlalchemy_utils/types/timezone.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import six
|
||||
from sqlalchemy import types
|
||||
from sqlalchemy_utils import ImproperlyConfigured
|
||||
|
||||
|
||||
class TimezoneType(types.TypeDecorator):
|
||||
"""
|
||||
Changes Timezone objects to a string representation on the way in and
|
||||
changes them back to Timezone objects on the way out.
|
||||
"""
|
||||
|
||||
impl = types.CHAR(50)
|
||||
|
||||
python_type = None
|
||||
|
||||
def __init__(self, backend='dateutil'):
|
||||
"""
|
||||
:param backend: Whether to use 'dateutil' or 'pytz' for timezones.
|
||||
"""
|
||||
|
||||
self.backend = backend
|
||||
if backend == 'dateutil':
|
||||
try:
|
||||
from dateutil.tz import tzfile
|
||||
from dateutil.zoneinfo import gettz
|
||||
|
||||
self.python_type = tzfile
|
||||
self._to = gettz
|
||||
self._from = lambda x: x._filename
|
||||
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured(
|
||||
"'python-dateutil' is required to use the "
|
||||
"'dateutil' backend for 'TimezoneType'"
|
||||
)
|
||||
|
||||
elif backend == 'pytz':
|
||||
try:
|
||||
from pytz import tzfile, timezone
|
||||
|
||||
self.python_type = tzfile.DstTzInfo
|
||||
self._to = timezone
|
||||
self._from = six.text_type
|
||||
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured(
|
||||
"'pytz' is required to use the 'pytz' backend "
|
||||
"for 'TimezoneType'"
|
||||
)
|
||||
|
||||
else:
|
||||
raise ImproperlyConfigured(
|
||||
"'pytz' or 'dateutil' are the backends supported for "
|
||||
"'TimezoneType'"
|
||||
)
|
||||
|
||||
def _coerce(self, value):
|
||||
if value and not isinstance(value, self.python_type):
|
||||
obj = self._to(value)
|
||||
if obj is None:
|
||||
raise ValueError("unknown time zone '%s'" % value)
|
||||
|
||||
return obj
|
||||
|
||||
return value
|
||||
|
||||
def coercion_listener(self, target, value, oldvalue, initiator):
|
||||
return self._coerce(value)
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
return self._from(self._coerce(value)) if value else None
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
return self._to(value) if value else None
|
39
tests/test_timezone.py
Normal file
39
tests/test_timezone.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from pytest import mark
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy_utils.types import timezone
|
||||
from tests import TestCase
|
||||
|
||||
|
||||
try:
|
||||
import dateutil
|
||||
|
||||
except ImportError:
|
||||
dateutil = None
|
||||
|
||||
|
||||
@mark.skipif('dateutil is None')
|
||||
class TestIPAddressType(TestCase):
|
||||
def create_models(self):
|
||||
class Visitor(self.Base):
|
||||
__tablename__ = 'document'
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
timezone = sa.Column(timezone.TimezoneType)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Visitor(%r)' % self.id
|
||||
|
||||
self.Visitor = Visitor
|
||||
|
||||
def test_parameter_processing(self):
|
||||
visitor = self.Visitor(
|
||||
timezone=u'America/Los_Angeles'
|
||||
)
|
||||
|
||||
self.session.add(visitor)
|
||||
self.session.commit()
|
||||
|
||||
visitor = self.session.query(self.Visitor).filter_by(
|
||||
timezone='America/Los_Angeles').first()
|
||||
|
||||
assert visitor is not None
|
Reference in New Issue
Block a user