File: timezone.py

package info (click to toggle)
python-sqlalchemy-utils 0.41.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,252 kB
  • sloc: python: 13,566; makefile: 141
file content (102 lines) | stat: -rw-r--r-- 3,408 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
from sqlalchemy import types

from ..exceptions import ImproperlyConfigured
from .scalar_coercible import ScalarCoercible


class TimezoneType(ScalarCoercible, types.TypeDecorator):
    """
    TimezoneType provides a way for saving timezones objects into database.
    TimezoneType saves timezone objects as strings on the way in and converts
    them back to objects when querying the database.

    ::

        from sqlalchemy_utils import TimezoneType

        class User(Base):
            __tablename__ = 'user'

            # Pass backend='pytz' to change it to use pytz. Other values:
            # 'dateutil' (default), and 'zoneinfo'.
            timezone = sa.Column(TimezoneType(backend='pytz'))

    :param backend: Whether to use 'dateutil', 'pytz' or 'zoneinfo' for
        timezones. 'zoneinfo' uses the standard library module in Python 3.9+,
        but requires the external 'backports.zoneinfo' package for older
        Python versions.

    """

    impl = types.Unicode(50)
    python_type = None
    cache_ok = True

    def __init__(self, backend='dateutil'):
        self.backend = backend
        if backend == 'dateutil':
            try:
                from dateutil.tz import tzfile
                from dateutil.zoneinfo import get_zonefile_instance

                self.python_type = tzfile
                self._to = get_zonefile_instance().zones.get
                self._from = lambda x: str(x._filename)

            except ImportError:
                raise ImproperlyConfigured(
                    "'python-dateutil' is required to use the "
                    "'dateutil' backend for 'TimezoneType'"
                )

        elif backend == 'pytz':
            try:
                from pytz import timezone
                from pytz.tzinfo import BaseTzInfo

                self.python_type = BaseTzInfo
                self._to = timezone
                self._from = str

            except ImportError:
                raise ImproperlyConfigured(
                    "'pytz' is required to use the 'pytz' backend "
                    "for 'TimezoneType'"
                )

        elif backend == "zoneinfo":
            try:
                import zoneinfo
            except ImportError:
                try:
                    from backports import zoneinfo
                except ImportError:
                    raise ImproperlyConfigured(
                        "'backports.zoneinfo' is required to use "
                        "the 'zoneinfo' backend for 'TimezoneType'"
                        "on Python version < 3.9"
                    )

            self.python_type = zoneinfo.ZoneInfo
            self._to = zoneinfo.ZoneInfo
            self._from = str

        else:
            raise ImproperlyConfigured(
                "'pytz', 'dateutil' or 'zoneinfo' are the backends "
                "supported for 'TimezoneType'"
            )

    def _coerce(self, value):
        if value is not None 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 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