Add timezone option to config
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
This commit is contained in:
parent
4cdb25bf5d
commit
fbbb669b8c
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
from dateutil import tz
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
@ -42,7 +43,8 @@ class ScriptDirectory(object):
|
||||||
def __init__(self, dir, file_template=_default_file_template,
|
def __init__(self, dir, file_template=_default_file_template,
|
||||||
truncate_slug_length=40,
|
truncate_slug_length=40,
|
||||||
version_locations=None,
|
version_locations=None,
|
||||||
sourceless=False, output_encoding="utf-8"):
|
sourceless=False, output_encoding="utf-8",
|
||||||
|
timezone=None):
|
||||||
self.dir = dir
|
self.dir = dir
|
||||||
self.file_template = file_template
|
self.file_template = file_template
|
||||||
self.version_locations = version_locations
|
self.version_locations = version_locations
|
||||||
|
@ -50,6 +52,7 @@ class ScriptDirectory(object):
|
||||||
self.sourceless = sourceless
|
self.sourceless = sourceless
|
||||||
self.output_encoding = output_encoding
|
self.output_encoding = output_encoding
|
||||||
self.revision_map = revision.RevisionMap(self._load_revisions)
|
self.revision_map = revision.RevisionMap(self._load_revisions)
|
||||||
|
self.timezone = timezone
|
||||||
|
|
||||||
if not os.access(dir, os.F_OK):
|
if not os.access(dir, os.F_OK):
|
||||||
raise util.CommandError("Path doesn't exist: %r. Please use "
|
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")
|
version_locations = config.get_main_option("version_locations")
|
||||||
if version_locations:
|
if version_locations:
|
||||||
version_locations = _split_on_space_comma.split(version_locations)
|
version_locations = _split_on_space_comma.split(version_locations)
|
||||||
|
|
||||||
return ScriptDirectory(
|
return ScriptDirectory(
|
||||||
util.coerce_resource_to_filename(script_location),
|
util.coerce_resource_to_filename(script_location),
|
||||||
file_template=config.get_main_option(
|
file_template=config.get_main_option(
|
||||||
|
@ -126,7 +130,8 @@ class ScriptDirectory(object):
|
||||||
truncate_slug_length=truncate_slug_length,
|
truncate_slug_length=truncate_slug_length,
|
||||||
sourceless=config.get_main_option("sourceless") == "true",
|
sourceless=config.get_main_option("sourceless") == "true",
|
||||||
output_encoding=config.get_main_option("output_encoding", "utf-8"),
|
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
|
@contextmanager
|
||||||
|
@ -440,6 +445,18 @@ class ScriptDirectory(object):
|
||||||
"Creating directory %s" % path,
|
"Creating directory %s" % path,
|
||||||
os.makedirs, 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(
|
def generate_revision(
|
||||||
self, revid, message, head=None,
|
self, revid, message, head=None,
|
||||||
refresh=False, splice=False, branch_labels=None,
|
refresh=False, splice=False, branch_labels=None,
|
||||||
|
@ -478,7 +495,7 @@ class ScriptDirectory(object):
|
||||||
if len(set(heads)) != len(heads):
|
if len(set(heads)) != len(heads):
|
||||||
raise util.CommandError("Duplicate head revisions specified")
|
raise util.CommandError("Duplicate head revisions specified")
|
||||||
|
|
||||||
create_date = datetime.datetime.now()
|
create_date = self._generate_create_date()
|
||||||
|
|
||||||
if version_path is None:
|
if version_path is None:
|
||||||
if len(self._version_locations) > 1:
|
if len(self._version_locations) > 1:
|
||||||
|
|
|
@ -7,6 +7,12 @@ script_location = ${script_location}
|
||||||
# template used to generate migration files
|
# template used to generate migration files
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
# 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
|
# max length of characters to apply to the
|
||||||
# "slug" field
|
# "slug" field
|
||||||
#truncate_slug_length = 40
|
#truncate_slug_length = 40
|
||||||
|
|
|
@ -7,6 +7,12 @@ script_location = ${script_location}
|
||||||
# template used to generate migration files
|
# template used to generate migration files
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
# 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
|
# max length of characters to apply to the
|
||||||
# "slug" field
|
# "slug" field
|
||||||
#truncate_slug_length = 40
|
#truncate_slug_length = 40
|
||||||
|
|
|
@ -7,6 +7,12 @@ script_location = ${script_location}
|
||||||
# template used to generate migration files
|
# template used to generate migration files
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
# 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
|
# max length of characters to apply to the
|
||||||
# "slug" field
|
# "slug" field
|
||||||
#truncate_slug_length = 40
|
#truncate_slug_length = 40
|
||||||
|
|
|
@ -7,6 +7,16 @@ Changelog
|
||||||
:version: 0.9.2
|
:version: 0.9.2
|
||||||
:released:
|
: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
|
.. change:: 421
|
||||||
:tags: bug, autogenerate
|
:tags: bug, autogenerate
|
||||||
:tickets: 421
|
:tickets: 421
|
||||||
|
|
|
@ -119,6 +119,12 @@ The file generated with the "generic" configuration looks like::
|
||||||
# template used to generate migration files
|
# template used to generate migration files
|
||||||
# file_template = %%(rev)s_%%(slug)s
|
# 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
|
# max length of characters to apply to the
|
||||||
# "slug" field
|
# "slug" field
|
||||||
#truncate_slug_length = 40
|
#truncate_slug_length = 40
|
||||||
|
@ -212,8 +218,23 @@ This file contains the following features:
|
||||||
* ``%%(rev)s`` - revision id
|
* ``%%(rev)s`` - revision id
|
||||||
* ``%%(slug)s`` - a truncated string derived from the revision message
|
* ``%%(slug)s`` - a truncated string derived from the revision message
|
||||||
* ``%%(year)d``, ``%%(month).2d``, ``%%(day).2d``, ``%%(hour).2d``,
|
* ``%%(year)d``, ``%%(month).2d``, ``%%(day).2d``, ``%%(hour).2d``,
|
||||||
``%%(minute).2d``, ``%%(second).2d`` - components of the create date
|
``%%(minute).2d``, ``%%(second).2d`` - components of the create date,
|
||||||
as returned by ``datetime.datetime.now()``
|
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
|
* ``truncate_slug_length`` - defaults to 40, the max number of characters
|
||||||
to include in the "slug" field.
|
to include in the "slug" field.
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -16,6 +16,7 @@ requires = [
|
||||||
'SQLAlchemy>=0.7.6',
|
'SQLAlchemy>=0.7.6',
|
||||||
'Mako',
|
'Mako',
|
||||||
'python-editor>=0.3',
|
'python-editor>=0.3',
|
||||||
|
'python-dateutil'
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -18,6 +18,7 @@ import sqlalchemy as sa
|
||||||
from sqlalchemy.engine.reflection import Inspector
|
from sqlalchemy.engine.reflection import Inspector
|
||||||
from alembic.util import CommandError
|
from alembic.util import CommandError
|
||||||
import re
|
import re
|
||||||
|
from dateutil import tz
|
||||||
|
|
||||||
env, abc, def_ = None, None, None
|
env, abc, def_ = None, None, None
|
||||||
|
|
||||||
|
@ -153,6 +154,72 @@ class ScriptNamingTest(TestBase):
|
||||||
"message_2012_7_25_15_8_5.py" % _get_staging_directory())
|
"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):
|
class RevisionCommandTest(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Loading…
Reference in New Issue