]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Add timezone option to config
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Apr 2017 17:55:35 +0000 (13:55 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 6 Apr 2017 18:35:41 +0000 (14:35 -0400)
Using dateutil.tz to link string names to tzinfo objects,
the create_date can now generate using a named timezone
rather than datetime.now().

Change-Id: I9f151cb9e11da3d68be63d7141f60e7eccb9812c
Fixes: #425
alembic/script/base.py
alembic/templates/generic/alembic.ini.mako
alembic/templates/multidb/alembic.ini.mako
alembic/templates/pylons/alembic.ini.mako
docs/build/changelog.rst
docs/build/tutorial.rst
setup.py
tests/test_script_production.py

index a79ec09c2a615914ed40020c3554e9f6ed4eebb9..17cb3de3fbd9a96f15fd509a59d012bb510088f4 100644 (file)
@@ -1,4 +1,5 @@
 import datetime
+from dateutil import tz
 import os
 import re
 import shutil
@@ -42,7 +43,8 @@ class ScriptDirectory(object):
     def __init__(self, dir, file_template=_default_file_template,
                  truncate_slug_length=40,
                  version_locations=None,
-                 sourceless=False, output_encoding="utf-8"):
+                 sourceless=False, output_encoding="utf-8",
+                 timezone=None):
         self.dir = dir
         self.file_template = file_template
         self.version_locations = version_locations
@@ -50,6 +52,7 @@ class ScriptDirectory(object):
         self.sourceless = sourceless
         self.output_encoding = output_encoding
         self.revision_map = revision.RevisionMap(self._load_revisions)
+        self.timezone = timezone
 
         if not os.access(dir, os.F_OK):
             raise util.CommandError("Path doesn't exist: %r.  Please use "
@@ -118,6 +121,7 @@ class ScriptDirectory(object):
         version_locations = config.get_main_option("version_locations")
         if version_locations:
             version_locations = _split_on_space_comma.split(version_locations)
+
         return ScriptDirectory(
             util.coerce_resource_to_filename(script_location),
             file_template=config.get_main_option(
@@ -126,7 +130,8 @@ class ScriptDirectory(object):
             truncate_slug_length=truncate_slug_length,
             sourceless=config.get_main_option("sourceless") == "true",
             output_encoding=config.get_main_option("output_encoding", "utf-8"),
-            version_locations=version_locations
+            version_locations=version_locations,
+            timezone=config.get_main_option("timezone")
         )
 
     @contextmanager
@@ -440,6 +445,18 @@ class ScriptDirectory(object):
                 "Creating directory %s" % path,
                 os.makedirs, path)
 
+    def _generate_create_date(self):
+        if self.timezone is not None:
+            tzinfo = tz.gettz(self.timezone.upper())
+            if tzinfo is None:
+                raise util.CommandError(
+                    "Can't locate timezone: %s" % self.timezone)
+            create_date = datetime.datetime.utcnow().replace(
+                tzinfo=tz.tzutc()).astimezone(tzinfo)
+        else:
+            create_date = datetime.datetime.now()
+        return create_date
+
     def generate_revision(
             self, revid, message, head=None,
             refresh=False, splice=False, branch_labels=None,
@@ -478,7 +495,7 @@ class ScriptDirectory(object):
         if len(set(heads)) != len(heads):
             raise util.CommandError("Duplicate head revisions specified")
 
-        create_date = datetime.datetime.now()
+        create_date = self._generate_create_date()
 
         if version_path is None:
             if len(self._version_locations) > 1:
index 4d3bf6eade42b68f467d203be2bb76ec59476f83..9ee59dbfb5e0dfdfc6fb2e0ccb6ca82f926d1e9a 100644 (file)
@@ -7,6 +7,12 @@ script_location = ${script_location}
 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# timezone to use when rendering the date
+# within the migration file as well as the filename.
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
 # max length of characters to apply to the
 # "slug" field
 #truncate_slug_length = 40
index 929a4be67d275b02b02bb2e8a7bebac4cbc3b9e1..a0708ff1ee479108c00e27fc83054c4b722d68bf 100644 (file)
@@ -7,6 +7,12 @@ script_location = ${script_location}
 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# timezone to use when rendering the date
+# within the migration file as well as the filename.
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
 # max length of characters to apply to the
 # "slug" field
 #truncate_slug_length = 40
index 62191e044158ccc1cd3b97b21f075002fe93ec23..c5cc4133a1ef38d87cadc03789c4f0d1076705fc 100644 (file)
@@ -7,6 +7,12 @@ script_location = ${script_location}
 # template used to generate migration files
 # file_template = %%(rev)s_%%(slug)s
 
+# timezone to use when rendering the date
+# within the migration file as well as the filename.
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
 # max length of characters to apply to the
 # "slug" field
 #truncate_slug_length = 40
index 78358837daa1c4d06134f0868936468a74a87e78..926556b09c500f987a15421f64e2e2a6665bae30 100644 (file)
@@ -7,6 +7,16 @@ Changelog
     :version: 0.9.2
     :released:
 
+    .. change:: 425
+      :tags: feature, commands
+      :tickets: 425
+
+      Added a new configuration option ``timezone``, a string timezone name
+      that will be applied to the create date timestamp rendered
+      inside the revision file as made availble to the ``file_template`` used
+      to generate the revision filename.  Note this change adds the
+      ``python-dateutil`` package as a dependency.
+
     .. change:: 421
       :tags: bug, autogenerate
       :tickets: 421
index fbbc5a4e120ae9dda8994638b3057e389e1caf54..2f71b0227d2fdad60464e79b631c0522bab83f04 100644 (file)
@@ -119,6 +119,12 @@ The file generated with the "generic" configuration looks like::
     # template used to generate migration files
     # file_template = %%(rev)s_%%(slug)s
 
+    # timezone to use when rendering the date
+    # within the migration file as well as the filename.
+    # string value is passed to dateutil.tz.gettz()
+    # leave blank for localtime
+    # timezone =
+
     # max length of characters to apply to the
     # "slug" field
     #truncate_slug_length = 40
@@ -212,8 +218,23 @@ This file contains the following features:
     * ``%%(rev)s`` - revision id
     * ``%%(slug)s`` - a truncated string derived from the revision message
     * ``%%(year)d``, ``%%(month).2d``, ``%%(day).2d``, ``%%(hour).2d``,
-      ``%%(minute).2d``, ``%%(second).2d`` - components of the create date
-      as returned by ``datetime.datetime.now()``
+      ``%%(minute).2d``, ``%%(second).2d`` - components of the create date,
+      by default ``datetime.datetime.now()`` unless the ``timezone``
+      configuration option is also used.
+
+* ``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.  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>)
+      )
+
+  .. versionadded:: 0.9.2
 
 * ``truncate_slug_length`` - defaults to 40, the max number of characters
   to include in the "slug" field.
index 445e3356fb14af317ff637d60c9e0f21392f6ff6..e424eb034eb784d0b264d01ab3a7cd4c33e838d7 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -16,6 +16,7 @@ requires = [
     'SQLAlchemy>=0.7.6',
     'Mako',
     'python-editor>=0.3',
+    'python-dateutil'
 ]
 
 try:
index 37030147add8c9dd8c59334f5a10b70987a5b80d..3364d566bcecd0cc785dc28b6e3285a98c472627 100644 (file)
@@ -18,6 +18,7 @@ import sqlalchemy as sa
 from sqlalchemy.engine.reflection import Inspector
 from alembic.util import CommandError
 import re
+from dateutil import tz
 
 env, abc, def_ = None, None, None
 
@@ -153,6 +154,72 @@ class ScriptNamingTest(TestBase):
                 "message_2012_7_25_15_8_5.py" % _get_staging_directory())
         )
 
+    def _test_tz(self, timezone_arg, given, expected):
+        script = ScriptDirectory(
+            _get_staging_directory(),
+            file_template="%(rev)s_%(slug)s_"
+            "%(year)s_%(month)s_"
+            "%(day)s_%(hour)s_"
+            "%(minute)s_%(second)s",
+            timezone=timezone_arg
+        )
+
+        with mock.patch(
+                "alembic.script.base.datetime",
+                mock.Mock(
+                    datetime=mock.Mock(
+                        utcnow=lambda: given,
+                        now=lambda: given
+                    )
+                )
+        ):
+            create_date = script._generate_create_date()
+        eq_(
+            create_date,
+            expected
+        )
+
+    def test_custom_tz(self):
+        self._test_tz(
+            'EST5EDT',
+            datetime.datetime(2012, 7, 25, 15, 8, 5),
+            datetime.datetime(
+                2012, 7, 25, 11, 8, 5, tzinfo=tz.gettz('EST5EDT'))
+        )
+
+    def test_custom_tz_lowercase(self):
+        self._test_tz(
+            'est5edt',
+            datetime.datetime(2012, 7, 25, 15, 8, 5),
+            datetime.datetime(
+                2012, 7, 25, 11, 8, 5, tzinfo=tz.gettz('EST5EDT'))
+        )
+
+    def test_custom_tz_utc(self):
+        self._test_tz(
+            'utc',
+            datetime.datetime(2012, 7, 25, 15, 8, 5),
+            datetime.datetime(
+                2012, 7, 25, 15, 8, 5, tzinfo=tz.gettz('UTC'))
+        )
+
+    def test_default_tz(self):
+        self._test_tz(
+            None,
+            datetime.datetime(2012, 7, 25, 15, 8, 5),
+            datetime.datetime(2012, 7, 25, 15, 8, 5)
+        )
+
+    def test_tz_cant_locate(self):
+        assert_raises_message(
+            CommandError,
+            "Can't locate timezone: fake",
+            self._test_tz,
+            "fake",
+            datetime.datetime(2012, 7, 25, 15, 8, 5),
+            datetime.datetime(2012, 7, 25, 15, 8, 5)
+        )
+
 
 class RevisionCommandTest(TestBase):
     def setUp(self):