Codebase list sqlalchemy-utc / f638c01
New upstream version 0.11.0 Sophie Brun 3 years ago
18 changed file(s) with 742 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 [run]
1 source = sqlalchemy_utc/
2 omit =
3 tests/*
4 setup.py
5
6 [report]
7 exclude_lines =
8 def __repr__
9 def __str__
10 if __name__ = .__main__.:
0
1 # Created by https://www.gitignore.io/api/osx,pydev,python,pycharm,visualstudiocode
2
3 ### OSX ###
4 *.DS_Store
5 .AppleDouble
6 .LSOverride
7
8 # Icon must end with two \r
9 Icon
10
11 # Thumbnails
12 ._*
13
14 # Files that might appear in the root of a volume
15 .DocumentRevisions-V100
16 .fseventsd
17 .Spotlight-V100
18 .TemporaryItems
19 .Trashes
20 .VolumeIcon.icns
21 .com.apple.timemachine.donotpresent
22
23 # Directories potentially created on remote AFP share
24 .AppleDB
25 .AppleDesktop
26 Network Trash Folder
27 Temporary Items
28 .apdisk
29
30 ### PyCharm ###
31 # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
32 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
33
34 # User-specific stuff:
35 .idea/**/workspace.xml
36 .idea/**/tasks.xml
37 .idea/dictionaries
38
39 # Sensitive or high-churn files:
40 .idea/**/dataSources/
41 .idea/**/dataSources.ids
42 .idea/**/dataSources.xml
43 .idea/**/dataSources.local.xml
44 .idea/**/sqlDataSources.xml
45 .idea/**/dynamic.xml
46 .idea/**/uiDesigner.xml
47
48 # Gradle:
49 .idea/**/gradle.xml
50 .idea/**/libraries
51
52 # CMake
53 cmake-build-debug/
54
55 # Mongo Explorer plugin:
56 .idea/**/mongoSettings.xml
57
58 ## File-based project format:
59 *.iws
60
61 ## Plugin-specific files:
62
63 # IntelliJ
64 /out/
65
66 # mpeltonen/sbt-idea plugin
67 .idea_modules/
68
69 # JIRA plugin
70 atlassian-ide-plugin.xml
71
72 # Cursive Clojure plugin
73 .idea/replstate.xml
74
75 # Ruby plugin and RubyMine
76 /.rakeTasks
77
78 # Crashlytics plugin (for Android Studio and IntelliJ)
79 com_crashlytics_export_strings.xml
80 crashlytics.properties
81 crashlytics-build.properties
82 fabric.properties
83
84 ### PyCharm Patch ###
85 # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
86
87 # *.iml
88 # modules.xml
89 # .idea/misc.xml
90 # *.ipr
91
92 # Sonarlint plugin
93 .idea/sonarlint
94
95 ### pydev ###
96 .pydevproject
97
98 ### Python ###
99 # Byte-compiled / optimized / DLL files
100 __pycache__/
101 *.py[cod]
102 *$py.class
103
104 # C extensions
105 *.so
106
107 # Distribution / packaging
108 .Python
109 build/
110 develop-eggs/
111 dist/
112 downloads/
113 eggs/
114 .eggs/
115 lib/
116 lib64/
117 parts/
118 sdist/
119 var/
120 wheels/
121 *.egg-info/
122 .installed.cfg
123 *.egg
124
125 # PyInstaller
126 # Usually these files are written by a python script from a template
127 # before PyInstaller builds the exe, so as to inject date/other infos into it.
128 *.manifest
129 *.spec
130
131 # Installer logs
132 pip-log.txt
133 pip-delete-this-directory.txt
134
135 # Unit test / coverage reports
136 htmlcov/
137 .tox/
138 .coverage
139 .coverage.*
140 .cache
141 nosetests.xml
142 coverage.xml
143 *.cover
144 .hypothesis/
145
146 # Translations
147 *.mo
148 *.pot
149
150 # Django stuff:
151 *.log
152 local_settings.py
153
154 # Flask stuff:
155 instance/
156 .webassets-cache
157
158 # Scrapy stuff:
159 .scrapy
160
161 # Sphinx documentation
162 docs/_build/
163
164 # PyBuilder
165 target/
166
167 # Jupyter Notebook
168 .ipynb_checkpoints
169
170 # pyenv
171 .python-version
172
173 # celery beat schedule file
174 celerybeat-schedule.*
175
176 # SageMath parsed files
177 *.sage.py
178
179 # Environments
180 .env
181 .venv
182 env/
183 venv/
184 ENV/
185 env.bak/
186 venv.bak/
187
188 # Spyder project settings
189 .spyderproject
190 .spyproject
191
192 # Rope project settings
193 .ropeproject
194
195 # mkdocs documentation
196 /site
197
198 # mypy
199 .mypy_cache/
200
201 ### VisualStudioCode ###
202 .vscode/*
203 !.vscode/settings.json
204 !.vscode/tasks.json
205 !.vscode/launch.json
206 !.vscode/extensions.json
207 .history
208
209
210 # End of https://www.gitignore.io/api/osx,pydev,python,pycharm,visualstudiocode
0 language: python
1 python:
2 - pypy
3 - 2.7
4 - pypy3
5 - 3.4
6 - 3.5
7 - 3.6
8
9 env:
10 - SQLALCHEMY_VER=">=0.9.0,<1.0.0" PIP_OPTS=""
11 - SQLALCHEMY_VER=">=1.0.0,<1.1.0" PIP_OPTS=""
12 - SQLALCHEMY_VER=">=1.1.0,<1.2.0" PIP_OPTS=""
13 - SQLALCHEMY_VER=">=1.2.0,<1.3.0" PIP_OPTS=""
14
15 matrix:
16 exclude:
17 # SQLAlchemy < 1.0.0 does not support psycopg2cffi as required by pypy
18 - python: pypy
19 env: SQLALCHEMY_VER=">=0.9.0,<1.0.0" PIP_OPTS=""
20 - python: pypy3
21 env: SQLALCHEMY_VER=">=0.9.0,<1.0.0" PIP_OPTS=""
22
23 services:
24 - postgresql
25 - mysql
26
27 before_install:
28 - export PY=`python -c 'import sys; print("pypy" if hasattr(sys,"pypy_version_info") else "%d.%d" % sys.version_info[:2])'`
29 - export PY_VER=`python -c 'import sys; print("%d.%d" % sys.version_info[:2])'`
30 - echo "PY='$PY'"
31 - echo "PY_VER='$PY_VER'"
32 - if [[ "$PY" = "pypy" ]]; then
33 export PG_URL="postgresql+psycopg2cffi:///utc_test";
34 export MYSQL_URL="mysql+pymysql://root@localhost/utc_test";
35 else
36 export PG_URL="postgresql+psycopg2:///utc_test";
37 export MYSQL_URL="mysql+mysqlconnector://root@localhost/utc_test";
38 fi
39 - export TEST_DATABASE_URLS="$PG_URL $MYSQL_URL"
40
41 install:
42 - if [[ "$PIP_OPTS" != "" ]]; then
43 pip install $PIP_OPTS "SQLAlchemy $SQLALCHEMY_VER";
44 else
45 pip install "SQLAlchemy $SQLALCHEMY_VER";
46 fi
47 - pip install -e .
48 - pip install pytest flake8 flake8-import-order
49 - pip install pytest-cov codecov
50 - if [[ "$PY" = "pypy" ]]; then
51 pip install psycopg2cffi pymysql;
52 else
53 pip install psycopg2 mysql-connector-python;
54 fi
55
56 before_script:
57 - createdb -E utf8 -T postgres utc_test
58 - mysql -e 'CREATE DATABASE utc_test;'
59
60 script:
61 - pytest --cov=. --durations=10 tests
62 - flake8 .
63
64 after_success:
65 - codecov
0 Changelog
1 =========
2
3 0.11.0
4 ------
5
6 Released on November 13, 2020.
7
8 - Ensured always returning the datetime with UTC timezone.
9 [`#8_` by Eduard Christian Dumitrescu]
10
11 .. _#8: https://github.com/spoqa/sqlalchemy-utc/pull/8
12
13
14 0.10.0
15 ------
16
17 Released on January 25, 2018.
18
19 - Dropped support of older Python versions: 2.6, 3.2, and 3.3.
20 [`#2`_ by George Leslie-Waksman]
21 - Added ``sqlalchemy_utc.utcnow()`` function as an alternative to
22 ``sqlalchemy.sql.functions.now()`` for generating ``UtcDateTime`` values
23 on the database server. [`#4`_ by George Leslie-Waksman]
24
25 .. _#2: https://github.com/spoqa/sqlalchemy-utc/pull/2
26 .. _#4: https://github.com/spoqa/sqlalchemy-utc/pull/4
27
28
29 0.9.0
30 -----
31
32 First version. Released on June 22, 2016.
0 Copyright (C) 2015-2016 by Hong Minhee <http://hongminhee.org/>
1
2 Permission is hereby granted, free of charge, to any person obtaining a copy
3 of this software and associated documentation files (the "Software"), to deal
4 in the Software without restriction, including without limitation the rights
5 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6 copies of the Software, and to permit persons to whom the Software is
7 furnished to do so, subject to the following conditions:
8
9 The above copyright notice and this permission notice shall be included in
10 all copies or substantial portions of the Software.
11
12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18 THE SOFTWARE.
0 SQLAlchemy-Utc
1 ==============
2
3 .. image:: https://badge.fury.io/py/SQLAlchemy-Utc.svg?
4 :target: https://pypi.python.org/pypi/SQLAlchemy-Utc
5 .. image:: https://travis-ci.org/spoqa/sqlalchemy-utc.svg?branch=master
6 :target: https://travis-ci.org/spoqa/sqlalchemy-utc
7 .. image:: https://codecov.io/github/spoqa/sqlalchemy-utc/coverage.svg?branch=master
8 :target: https://codecov.io/github/spoqa/sqlalchemy-utc?branch=master
9
10 This package provides a drop-in replacement of SQLAlchemy's built-in `DateTime`_
11 type with ``timezone=True`` option enabled. Although SQLAlchemy's built-in
12 ``DateTime`` type provides ``timezone=True`` option, since some vendors like
13 SQLite and MySQL don't provide ``timestamptz`` data type, the option doesn't
14 make any effect on these vendors.
15
16 ``UtcDateTime`` type is equivalent to the built-in ``DateTime`` with
17 ``timezone=True`` option enabled on vendors that support ``timestamptz``
18 e.g. PostgreSQL, but on SQLite or MySQL, it shifts all ``datetime.datetime``
19 values to UTC offset before store them, and returns always aware
20 ``datetime.datetime`` values through result sets.
21
22 Long story short, ``UtcDateTime`` does:
23
24 - take only aware ``datetime.datetime``,
25 - return only aware ``datetime.datetime``,
26 - never take or return naive ``datetime.datetime``,
27 - ensure timestamps in database always to be encoded in UTC, and
28 - work as you'd expect.
29
30 A SQLAlchemy helper function, ``utcnow()``, is provided as an alternative
31 to ``func.now()`` for generating ``UtcDateTime`` values on the server. For
32 example: ``Column('time', UtcDateTime(), default=utcnow())``.
33
34 Written by `Hong Minhee`_ at Spoqa_, and distributed under MIT license.
35
36 .. _DateTime: http://docs.sqlalchemy.org/en/latest/core/type_basics.html#sqlalchemy.types.DateTime
37 .. _Hong Minhee: https://hongminhee.org/
38 .. _Spoqa: http://www.spoqa.com/
0 [bdist_wheel]
1 universal = 1
0 import ast
1 import os.path
2
3 from setuptools import find_packages, setup
4
5
6 def readme():
7 try:
8 with open('README.rst') as f:
9 readme = f.read()
10 except IOError:
11 pass
12 try:
13 with open('CHANGES.rst') as f:
14 readme += '\n\n' + f.read()
15 except IOError:
16 pass
17 return readme
18
19
20 def get_version():
21 module_path = os.path.join(
22 os.path.dirname(__file__),
23 'sqlalchemy_utc',
24 'version.py')
25 module_file = open(module_path)
26 try:
27 module_code = module_file.read()
28 finally:
29 module_file.close()
30 tree = ast.parse(module_code, module_path)
31 for node in ast.iter_child_nodes(tree):
32 if not isinstance(node, ast.Assign) or len(node.targets) != 1:
33 continue
34 target, = node.targets
35 if isinstance(target, ast.Name) and target.id == '__version__':
36 return node.value.s
37
38
39 install_requires = ['setuptools', 'SQLAlchemy >= 0.9.0']
40
41
42 setup(
43 name='SQLAlchemy-Utc',
44 description='SQLAlchemy type to store aware datetime values',
45 long_description=readme(),
46 version=get_version(),
47 url='https://github.com/spoqa/sqlalchemy-utc',
48 packages=find_packages(exclude=('tests*',)),
49 author='Hong Minhee',
50 author_email='hongminhee' '@' 'member.fsf.org',
51 license='MIT License',
52 install_requires=install_requires,
53 classifiers=[
54 'Development Status :: 4 - Beta',
55 'Intended Audience :: Developers',
56 'License :: OSI Approved :: MIT License',
57 'Operating System :: OS Independent',
58 'Programming Language :: Python :: 2.7',
59 'Programming Language :: Python :: 3.4',
60 'Programming Language :: Python :: 3.5',
61 'Programming Language :: Python :: 3.6',
62 'Programming Language :: Python :: Implementation :: CPython',
63 'Programming Language :: Python :: Implementation :: PyPy',
64 'Programming Language :: Python :: Implementation :: Stackless',
65 'Programming Language :: SQL',
66 'Topic :: Database :: Front-Ends',
67 'Topic :: Software Development',
68 ]
69 )
0 from .now import utcnow
1 from .sqltypes import UtcDateTime
2 from .timezone import utc
3 from .version import __version__
4
5 __all__ = [
6 '__version__',
7 'utc',
8 'UtcDateTime',
9 'utcnow',
10 ]
0
1 from sqlalchemy.ext.compiler import compiles
2 from sqlalchemy.sql.functions import FunctionElement
3
4 from .sqltypes import UtcDateTime
5
6
7 class utcnow(FunctionElement):
8 """UTCNOW() expression for multiple dialects."""
9 type = UtcDateTime()
10
11
12 @compiles(utcnow)
13 def default_sql_utcnow(element, compiler, **kw):
14 """Assume, by default, time zones work correctly.
15
16 Note:
17 This is a valid assumption for PostgreSQL and Oracle.
18 """
19 return 'CURRENT_TIMESTAMP'
20
21
22 @compiles(utcnow, 'mysql')
23 def mysql_sql_utcnow(element, compiler, **kw):
24 """MySQL returns now as localtime, so we convert to UTC.
25
26 Warning:
27 MySQL does not support the use of functions for sqlalchemy
28 `server_default=` values. The utcnow function must be used as
29 `default=` when interacting with a MySQL server.
30 """
31 return "CONVERT_TZ(CURRENT_TIMESTAMP, @@session.time_zone, '+00:00')"
32
33
34 @compiles(utcnow, 'sqlite')
35 def sqlite_sql_utcnow(element, compiler, **kw):
36 return "(DATETIME('NOW'))"
37
38
39 @compiles(utcnow, 'mssql')
40 def mssql_sql_utcnow(element, compiler, **kw):
41 """MS SQL provides a function for the UTC datetime."""
42 return 'GETUTCDATE()'
0 import datetime
1
2 from sqlalchemy.types import DateTime, TypeDecorator
3
4 from .timezone import utc
5
6
7 class UtcDateTime(TypeDecorator):
8 """Almost equivalent to :class:`~sqlalchemy.types.DateTime` with
9 ``timezone=True`` option, but it differs from that by:
10
11 - Never silently take naive :class:`~datetime.datetime`, instead it
12 always raise :exc:`ValueError` unless time zone aware value.
13 - :class:`~datetime.datetime` value's :attr:`~datetime.datetime.tzinfo`
14 is always converted to UTC.
15 - Unlike SQLAlchemy's built-in :class:`~sqlalchemy.types.DateTime`,
16 it never return naive :class:`~datetime.datetime`, but time zone
17 aware value, even with SQLite or MySQL.
18
19 """
20
21 impl = DateTime(timezone=True)
22
23 def process_bind_param(self, value, dialect):
24 if value is not None:
25 if not isinstance(value, datetime.datetime):
26 raise TypeError('expected datetime.datetime, not ' +
27 repr(value))
28 elif value.tzinfo is None:
29 raise ValueError('naive datetime is disallowed')
30 return value.astimezone(utc)
31
32 def process_result_value(self, value, dialect):
33 if value is not None:
34 if value.tzinfo is None:
35 value = value.replace(tzinfo=utc)
36 else:
37 value = value.astimezone(utc)
38 return value
0 import datetime
1
2
3 class Utc(datetime.tzinfo):
4
5 __slots__ = ()
6
7 zero = datetime.timedelta(0)
8
9 def utcoffset(self, _):
10 return self.zero
11
12 def dst(self, _):
13 return self.zero
14
15 def tzname(self, _):
16 return 'UTC'
17
18
19 try:
20 utc = datetime.timezone.utc
21 except AttributeError:
22 utc = Utc()
0 __version__ = '0.11.0'
(New empty file)
0 import os
1
2 from pytest import fixture
3
4 from sqlalchemy.engine import create_engine
5 from sqlalchemy.pool import NullPool
6
7
8 try:
9 database_urls = os.environ['TEST_DATABASE_URLS'].split()
10 except KeyError:
11 database_urls = []
12
13
14 @fixture(scope='function', params=['sqlite://'] + database_urls)
15 def fx_engine(request):
16 url = request.param
17 engine = create_engine(url, poolclass=NullPool)
18 request.addfinalizer(engine.dispose)
19 return engine
0 import datetime
1
2 from pytest import yield_fixture
3
4 from sqlalchemy import Column, MetaData, Table, select
5
6 from sqlalchemy_utc import UtcDateTime, utc, utcnow
7
8
9 TABLE = Table(
10 'test_table', MetaData(),
11 Column('time', UtcDateTime, default=utcnow()))
12
13
14 @yield_fixture
15 def fx_connection(fx_engine):
16 connection = fx_engine.connect()
17 try:
18 transaction = connection.begin()
19 try:
20 TABLE.create(connection)
21 yield connection
22 finally:
23 transaction.rollback()
24 finally:
25 connection.close()
26
27
28 def test_utcnow_timezone(fx_connection):
29 fx_connection.execute(TABLE.insert(), [{}])
30 rows = fx_connection.execute(select([TABLE])).fetchall()
31 server_now = rows[0].time
32 local_now = datetime.datetime.now(utc)
33 assert server_now.tzinfo is not None
34 assert abs(server_now - local_now) < datetime.timedelta(seconds=30)
0 import datetime
1
2 try:
3 from psycopg2ct.compat import register
4 except ImportError:
5 pass
6 else:
7 register()
8 from pytest import mark, raises, yield_fixture
9
10 from sqlalchemy.exc import StatementError
11 from sqlalchemy.ext.declarative import declarative_base
12 from sqlalchemy.orm import sessionmaker
13 from sqlalchemy.schema import Column
14
15 from sqlalchemy_utc import UtcDateTime, utc
16
17
18 Base = declarative_base()
19 Session = sessionmaker()
20
21
22 @yield_fixture
23 def fx_connection(fx_engine):
24 connection = fx_engine.connect()
25 try:
26 transaction = connection.begin()
27 try:
28 metadata = Base.metadata
29 metadata.create_all(bind=connection)
30 yield connection
31 finally:
32 transaction.rollback()
33 finally:
34 connection.close()
35
36
37 @yield_fixture
38 def fx_session(fx_connection):
39 session = Session(bind=fx_connection)
40 try:
41 yield session
42 finally:
43 session.close()
44
45
46 class UtcDateTimeTable(Base):
47
48 time = Column(UtcDateTime, primary_key=True)
49
50 __tablename__ = 'tb_utc_datetime'
51
52
53 class FixedOffset(datetime.tzinfo):
54
55 zero = datetime.timedelta(0)
56
57 def __init__(self, offset, name):
58 self.offset = offset
59 self.name = name
60
61 def utcoffset(self, _):
62 return self.offset
63
64 def tzname(self, _):
65 return self.name
66
67 def dst(self, _):
68 return self.zero
69
70
71 @mark.parametrize('tzinfo', [
72 utc,
73 FixedOffset(datetime.timedelta(hours=9), 'KST'),
74 ])
75 def test_utc_datetime(fx_session, tzinfo):
76 aware_time = datetime.datetime.now(tzinfo).replace(microsecond=0)
77 e = UtcDateTimeTable(time=aware_time)
78 fx_session.add(e)
79 fx_session.flush()
80 saved_time, = fx_session.query(UtcDateTimeTable.time).one()
81 assert saved_time == aware_time
82 if fx_session.bind.dialect.name in ('sqlite', 'mysql'):
83 zero = datetime.timedelta(0)
84 assert saved_time.tzinfo.utcoffset(aware_time) == zero
85 assert saved_time.tzinfo.dst(aware_time) in (None, zero)
86
87
88 def test_utc_datetime_naive(fx_session):
89 with raises((ValueError, StatementError)):
90 a = UtcDateTimeTable(time=datetime.datetime.now())
91 fx_session.add(a)
92 fx_session.flush()
93
94
95 def test_utc_datetime_type(fx_session):
96 with raises((TypeError, StatementError)):
97 a = UtcDateTimeTable(time=str(datetime.datetime.now()))
98 fx_session.add(a)
99 fx_session.flush()
0 [tox]
1 envlist = pypy, py27, pypy3, py34, py35, py36
2 minversion = 1.6.0
3
4 [testenv]
5 deps =
6 pytest
7 flake8
8 flake8-import-order
9 commands =
10 pytest tests
11 flake8
12
13 [flake8]
14 max-complexity = 10
15 exclude =
16 .git,
17 .tox,
18 __pycache__