from . import write_hooks
from .. import util
from ..runtime import migration
+from ..util import compat
from ..util import not_none
if TYPE_CHECKING:
from ..runtime.migration import StampStep
try:
- from dateutil import tz
+ if compat.py39:
+ from zoneinfo import ZoneInfo
+ from zoneinfo import ZoneInfoNotFoundError
+ else:
+ from backports.zoneinfo import ZoneInfo # type: ignore[import-not-found,no-redef] # noqa: E501
+ from backports.zoneinfo import ZoneInfoNotFoundError # type: ignore[import-not-found,no-redef] # noqa: E501
except ImportError:
- tz = None # type: ignore[assignment]
+ ZoneInfo = None # type: ignore[assignment, misc]
_sourceless_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)(c|o)?$")
_only_source_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)$")
def _generate_create_date(self) -> datetime.datetime:
if self.timezone is not None:
- if tz is None:
+ if ZoneInfo is None:
raise util.CommandError(
- "The library 'python-dateutil' is required "
- "for timezone support"
+ "Python >= 3.9 is required for timezone support or"
+ "the 'backports.zoneinfo' package must be installed."
)
# First, assume correct capitalization
- tzinfo = tz.gettz(self.timezone)
- if tzinfo is None:
- # Fall back to uppercase
- tzinfo = tz.gettz(self.timezone.upper())
+ try:
+ tzinfo = ZoneInfo(self.timezone)
+ except ZoneInfoNotFoundError:
+ tzinfo = None
if tzinfo is None:
- raise util.CommandError(
- "Can't locate timezone: %s" % self.timezone
- )
+ try:
+ tzinfo = ZoneInfo(self.timezone.upper())
+ except ZoneInfoNotFoundError:
+ raise util.CommandError(
+ "Can't locate timezone: %s" % self.timezone
+ ) from None
create_date = (
datetime.datetime.utcnow()
- .replace(tzinfo=tz.tzutc())
+ .replace(tzinfo=datetime.timezone.utc)
.astimezone(tzinfo)
)
else:
# timezone to use when rendering the date within the migration file
# as well as the filename.
-# If specified, requires the python-dateutil library that can be
-# installed by adding `alembic[tz]` to the pip requirements
-# string value is passed to dateutil.tz.gettz()
+# If specified, requires the python>=3.9 or backports.zoneinfo library.
+# Any requied deps can installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# timezone to use when rendering the date within the migration file
# as well as the filename.
-# If specified, requires the python-dateutil library that can be
-# installed by adding `alembic[tz]` to the pip requirements
-# string value is passed to dateutil.tz.gettz()
+# If specified, requires the python>=3.9 or backports.zoneinfo library.
+# Any requied deps can installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# timezone to use when rendering the date within the migration file
# as well as the filename.
-# If specified, requires the python-dateutil library that can be
-# installed by adding `alembic[tz]` to the pip requirements
-# string value is passed to dateutil.tz.gettz()
+# If specified, requires the python>=3.9 or backports.zoneinfo library.
+# Any requied deps can installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
git+https://github.com/sqlalchemyorg/changelog.git#egg=changelog
git+https://github.com/sqlalchemyorg/sphinx-paramlinks.git#egg=sphinx-paramlinks
git+https://github.com/sqlalchemy/sqlalchemy.git
-python-dateutil
+backports.zoneinfo;python_version<"3.9"
# because there's a dependency in pyfiles.py
Mako
importlib-metadata;python_version<"3.9"
# timezone to use when rendering the date within the migration file
# as well as the filename.
- # If specified, requires the python-dateutil library that can be
- # installed by adding `alembic[tz]` to the pip requirements
- # string value is passed to dateutil.tz.gettz()
+ # If specified, requires the python>=3.9 or backports.zoneinfo library.
+ # Any requied deps can installed by adding `alembic[tz]` to the pip requirements
+ # string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
* ``timezone`` - an optional timezone name (e.g. ``UTC``, ``EST5EDT``, etc.)
that will be applied to the timestamp which renders inside the migration
- file's comment as well as within the filename. This option requires installing
- the ``python-dateutil`` library. If ``timezone`` is specified,
+ file's comment as well as within the filename. This option requires Python>=3.9
+ or installing the ``backports.zoneinfo`` library. If ``timezone`` is specified,
the create date object is no longer derived from ``datetime.datetime.now()``
and is instead generated as::
datetime.datetime.utcnow().replace(
- tzinfo=dateutil.tz.tzutc()
- ).astimezone(
- dateutil.tz.gettz(<timezone>)
- )
+ tzinfo=datetime.timezone.utc
+ ).astimezone(ZoneInfo(<timezone>))
+
+ .. versionchanged:: 1.13.0 Python standard library ``zoneinfo`` is now used
+ for timezone rendering in migrations; previously ``python-dateutil``
+ was used.
* ``truncate_slug_length`` - defaults to 40, the max number of characters
to include in the "slug" field.
--- /dev/null
+.. change::
+ :tags: usecase
+ :tickets: 1339
+
+ Replaced ``python-dateutil`` with the standard library module
+ `zoneinfo <https://docs.python.org/3.11/library/zoneinfo.html#module-zoneinfo>`.
+ This module was added in Python 3.9, so previous version will been
+ to install the backport of it, available by installing the ``backports.zoneinfo``
+ library. The ``alembic[tz]`` option has been updated accordingly.
[options.extras_require]
tz =
- python-dateutil
+ backports.zoneinfo;python_version<"3.9"
[options.package_data]
alembic = *.pyi, py.typed
import re
from unittest.mock import patch
-from dateutil import tz
import sqlalchemy as sa
from sqlalchemy import Column
from sqlalchemy import inspect
from alembic.testing.fixtures import TestBase
from alembic.util import CommandError
+try:
+ from zoneinfo import ZoneInfo
+except ImportError:
+ from backports.zoneinfo import ZoneInfo
+
env, abc, def_ = None, None, None
@testing.combinations(
(
- datetime.datetime(2012, 7, 25, 15, 8, 5, tzinfo=tz.gettz("UTC")),
+ datetime.datetime(
+ 2012, 7, 25, 15, 8, 5, tzinfo=datetime.timezone.utc
+ ),
"%s/versions/1343228885_12345_this_is_a_"
"message_2012_7_25_15_8_5.py",
),
(
- datetime.datetime(2012, 7, 25, 15, 8, 6, tzinfo=tz.gettz("UTC")),
+ datetime.datetime(
+ 2012, 7, 25, 15, 8, 6, tzinfo=datetime.timezone.utc
+ ),
"%s/versions/1343228886_12345_this_is_a_"
"message_2012_7_25_15_8_6.py",
),
with mock.patch(
"alembic.script.base.datetime",
mock.Mock(
- datetime=mock.Mock(utcnow=lambda: given, now=lambda: given)
+ datetime=mock.Mock(utcnow=lambda: given, now=lambda: given),
+ timezone=datetime.timezone,
),
):
create_date = script._generate_create_date()
"EST5EDT",
datetime.datetime(2012, 7, 25, 15, 8, 5),
datetime.datetime(
- 2012, 7, 25, 11, 8, 5, tzinfo=tz.gettz("EST5EDT")
+ 2012, 7, 25, 11, 8, 5, tzinfo=ZoneInfo("EST5EDT")
),
)
"est5edt",
datetime.datetime(2012, 7, 25, 15, 8, 5),
datetime.datetime(
- 2012, 7, 25, 11, 8, 5, tzinfo=tz.gettz("EST5EDT")
+ 2012, 7, 25, 11, 8, 5, tzinfo=ZoneInfo("EST5EDT")
),
)
self._test_tz(
"utc",
datetime.datetime(2012, 7, 25, 15, 8, 5),
- datetime.datetime(2012, 7, 25, 15, 8, 5, tzinfo=tz.gettz("UTC")),
+ datetime.datetime(2012, 7, 25, 15, 8, 5, tzinfo=ZoneInfo("UTC")),
)
def test_custom_tzdata_tz(self):
"Europe/Berlin",
datetime.datetime(2012, 7, 25, 15, 8, 5),
datetime.datetime(
- 2012, 7, 25, 17, 8, 5, tzinfo=tz.gettz("Europe/Berlin")
+ 2012, 7, 25, 17, 8, 5, tzinfo=ZoneInfo("Europe/Berlin")
),
)
datetime.datetime(2012, 7, 25, 15, 8, 5),
)
- def test_no_dateutil_module(self):
- with patch("alembic.script.base.tz", new=None):
+ def test_no_zoneinfo_module(self):
+ with patch("alembic.script.base.ZoneInfo", new=None):
with expect_raises_message(
- CommandError, "The library 'python-dateutil' is required"
+ CommandError,
+ "Python >= 3.9 is required for timezone support or"
+ "the 'backports.zoneinfo' package must be installed.",
):
self._test_tz(
"utc",
cov: pytest-cov
sqlalchemy: sqlalchemy>=1.3.0
mako
- python-dateutil
+ backports.zoneinfo;python_version<"3.9"
zimports
black==23.3.0
greenlet>=1
sqlalchemy>=2
mako
types-pkg-resources
- types-python-dateutil
# is imported in alembic/testing and mypy complains if it's not installed.
pytest
commands = mypy ./alembic/ --exclude alembic/templates